はじめに
Dify には有償の Enterprise 版があります。動かすにはライセンスが必要ですし、OSS ではないのでソースコードも見られないのですが、一方で、Enterprise 版のドキュメントや、デプロイに必要なアセット一式は、インタネットで公開されています。
で、Enterprise 版で動く Dify のバージョンが 0.x だった頃に、そういう公開されているいろいろを探索してフムフムと技術的な好奇心を満たしていたのですが、最近、Enterprise 版の Dify が 1.x に引き上げられ、プラグインに対応しました。そんなわけで、改めてちょっとだけ中身を探ったので、簡単に紹介です。Community 版へのコントリビューションを続けるなかで、とくに Enterprise 版でのプラグイン周辺のアーキテクチャが気になっていたのでした。
ドキュメントでも細かくは説明されていない部分なので、Enterprise 版の導入や、導入済みの Enterprise 版のアップグレード(プラグインアーキテクチャへのマイグレーション)を検討している場合は、参考になる…… かも…… しれません。
目次
おことわり
Enterprise 版の利用にはライセンスが必要です。前述のとおり必要なアセットはインタネットで公開されているので、デプロイ作業それ自体は実施できます。が、ライセンスがないとアクティベーションできない ので、デプロイできても実際には使えません。
で、ぼくはコントリビューションを 業務ではなくプライベートの趣味 でやっている 個人 なので、当然、Enterprise 版のライセンスは 持っていません。したがって、本エントリで扱う内容は、Enterprise 版の実環境を触って調べたもの ではなく、インタネットで 公開されているリソースのみ を元に確認できた範囲のものです。Enterprise 版に固有のコンポーネントの大部分はソースコードが公開されていないので、実際に動かせもしないしソースコードからの推測もできないし、な部分がたくさんあり、細かいところはだいぶふわっとしています。
そのため、実装の細かい解説というよりは、全体像と処理の流れをふんわりつかめるくらいの荒い粒度で書いています。また、Kubernetes 環境にデプロイした場合の構成しか確認していないので、AWS や Docker Compose など別のプラットフォームに構成した場合には、本エントリの内容は必ずしも適合しません。さらに、Community 版の構成は充分に知っている前提で書いています。
なお、本エントリ公開日時点で最新のリリースを基にしています。
全体的な構成
昔: Dify 0.x 時代の構成(プラグイン機能なし)
昔、Enterprise 版に含まれる Dify が 0.x だった頃の、Kubernetes 上に構成した場合の全体像はこんな感じでした。
--- config: theme: 'base' themeVariables: primaryColor: '#0033ff' primaryTextColor: '#fff' secondaryColor: '#888' lineColor: '#333' tertiaryColor: '#e5eaff' tertiaryBorderColor: '#888' fontSize: '24px' --- flowchart LR A(Administrators) U(Users) EW(Enterprise Web) EA(Enterprise API) CW(Web) CA(API<br> with enterprise features) CO(Worker) CDB(DB) A --> EW U --> CW EA <--> CA subgraph Dify Enterprise EW --> EA subgraph Dify Community CW --> CA CA <--> CO CA --> CDB CO --> CDB CA --> CV(Vector DB) CA --> CS(Sandbox) CS --> CSSRF(SSRF Proxy) end end
Community 版と同じ一式がそのまま動いていて、横に Enterprise 版に固有の管理機能を提供するコンテナ(とそれ用のフロントエンドを提供するコンテナ)が追加で生えてくる感じです。Community 版の API コンテナは Enterprise 版で使える追加機能が有効化されていて、Enterprise 用の API コンテナと連携して動作します。
今: Dify 1.x 時代の構成(プラグイン機能あり)
プラグインに対応した最近のバージョンでは、Enterprise 版は Kubernetes 版ではこんな感じになっています。これは初期状態で、後述しますがプラグインをインストールするとプラグインごとに新しい Pod が増えていきます。また、環境の外に何らかのコンテナレジストリが別途必要です。
--- config: theme: 'base' themeVariables: primaryColor: '#0033ff' primaryTextColor: '#fff' secondaryColor: '#888' lineColor: '#333' tertiaryColor: '#e5eaff' tertiaryBorderColor: '#888' fontSize: '24px' --- flowchart LR A(Administrators) U(Users) CR((Container<br>Registry)) EW(Enterprise Web) EA(Enterprise API) EAudit(Enterprise Audit) ECRD(CRD Controller) EM(MinIO) CW(Web) CA(API<br> with enterprise features) CP(Plugin Daemon) CO(Worker) CDB(DB) A --> EG --> EW U --> EG --> CW EA <--> CA subgraph Dify Enterprise EG(Gateway) ECP(Plugin Connector) ECR((Custom<br>Resource)) EW --> EA EG --> EAudit EA --> EAudit CP --> ECP ECP --> ECR ECP --> EM ECRD --> ECR EAudit --> CDB subgraph Dify Community CW --> CA CA <--> CO CA --> CV(Vector DB) CA --> CS(Sandbox) CS --> CSSRF(SSRF Proxy) CA --> CP CA --> CDB CO --> CDB CP --> CDB end end
構成要素をふんわりと仕分けるとこんな感じです。
- Dify 一式のコンテナ群
- Community 版とほぼ同じ構成で動作する
- API コンテナでは Enterprise 版で使える追加機能が有効化されている
- Plugin Daemon は、Community 版の Local Runtime とは異なり、Serverless Runtime として動作している(後述)
- 管理機能用のコンテナ群
- Enterprise 版に固有の管理機能を提供する
- Web UI(フロントエンド)と API(バックエンド)のコンテナで構成される
- 監査ログ用のコンテナ群
- Enterprise 版の監査ログ機能を提供する
- ゲートウェイとログ管理機能のコンテナで構成される
- プラグイン管理用のコンテナ群
- Enterprise 版のプラグインのインストールや実行を担う
- ちょっと複雑なので後述
管理機能まわり
Enterprise 版に固有のダッシュボードやら管理機能やらを提供する部分です。ここは Dify 0.x 時代の構成と変わっていません。説明は省略。
--- config: theme: 'base' themeVariables: primaryColor: '#0033ff' primaryTextColor: '#fff' secondaryColor: '#888' lineColor: '#333' tertiaryColor: '#e5eaff' tertiaryBorderColor: '#888' fontSize: '24px' --- flowchart TB EW(Enterprise Web) EA(Enterprise API) CW(Web) CA(API<br> with enterprise features) CP(Plugin Daemon) CO(Worker) CDB(DB) EA <--> CA subgraph Dify Enterprise EW --> EA subgraph Dify Community CW --> CA CA <--> CO CA --> CV(Vector DB) CA --> CS(Sandbox) CS --> CSSRF(SSRF Proxy) CA --> CP CA --> CDB CO --> CDB CP --> CDB end end
監査ログまわり
Enterprise 版の監査ログ機能を提供する部分です。
管理者やユーザの操作は技術的には Web UI や API への HTTP リクエストなので、全部とにかくゲートウェイ的な役割のコンテナを経由させればまとめてログが取れるよね、な発想の実装に見えます。
--- config: theme: 'base' themeVariables: primaryColor: '#0033ff' primaryTextColor: '#fff' secondaryColor: '#888' lineColor: '#333' tertiaryColor: '#e5eaff' tertiaryBorderColor: '#888' fontSize: '24px' --- flowchart LR A(Administrators) U(Users) EW(Enterprise Web) EA(Enterprise API) EAudit(Enterprise Audit) CW(Web) CA(API<br> with enterprise features) CP(Plugin Daemon) CO(Worker) CDB(DB) A --> EG --> EW U --> EG --> CW EA <--> CA subgraph Dify Enterprise EG(Gateway) EW --> EA EG --> EAudit EA --> EAudit EAudit --> CDB subgraph Dify Community CW --> CA CA <--> CO CA --> CV(Vector DB) CA --> CS(Sandbox) CS --> CSSRF(SSRF Proxy) CA --> CP CA --> CDB CO --> CDB CP --> CDB end end
このあたりのソースコードはまるっと非公開なので詳しいことはわかりませんが、Kubernetes 的には Enterprise 版で用意される Ingress のバックエンドはすべてこの Gateway コンテナであり、このコンテナの中で動作する Caddy がログ情報の Redis への記録と HTTP リクエストの本来の宛先への転送を担っているように見えました。Caddy の中では専用に開発されたと思われるログ取得用ミドルウェアが構成されています。
さらに、Redis に置かれたログ情報は、Audit コンテナが吸い上げてデータベースに永続化していそうな気配でした。この Audit コンテナは、監査ログを管理する API も提供していそうです。
プラグイン管理まわり
プラグインをインストールしたり、インストール済みのプラグインを実行したりする部分です。個人的にいちばん気になっていたところです。
おおまかな構成と動き
前提として、Community 版では、プラグインに関連する実装は Plugin Daemon ひとつだけで、インストールも実行もすべてが単一のコンテナの中で完結しています。実装上は、これは Plugin Daemon の Local Runtime と呼ばれる動作モードです。
一方で、Enterprise 版で動作する Plugin Daemon は、最近ちょこっとだけ情報が公開された Serverless Runtime と呼ばれる動作モードで構成されます。このモードの動きを概要レベルでまとめるとこんな感じです。
- プラグインをインストールするとき
- 専用のベースイメージ上にプラグインが展開され、新しいコンテナイメージ としてビルドされます
- ビルドされたイメージは コンテナレジストリにプッシュ されます
- コンテナイメージが 新しい Pod として起動され、以降、常駐します(AWS 上で動いている場合は AWS Lambda として準備されます)
- プラグインを実行するとき
- Plugin Daemon から、インストール時に起動された Pod に実行指示が送られます(AWS 上で動いている場合は AWS Lambda が叩かれます)
もうちょっとくわしく
これだけだとまだ漠然としているので、ここから、これをさらにもう一段階踏み込んで読み解きます。理解の前提となるポイントは以下の通りです。
- Enterprise 版をデプロイするときに、DifyPlugin という名前の Custom Resource Definition(CRD)が Kubernetes クラスタに 追加 されます
- Kubernetes の文脈で、DifyPlugin という CR に対応する Custom Controller として、CRD Controller コンテナ が動作します
- 何らかのコンテナレジストリ が別途必要です
この前提で、プラグインをインストールするとき は、全体としては以下の流れで動作します。数字は図中のそれと対応しています。
--- config: theme: 'base' themeVariables: primaryColor: '#0033ff' primaryTextColor: '#fff' secondaryColor: '#888' lineColor: '#333' tertiaryColor: '#e5eaff' tertiaryBorderColor: '#888' fontSize: '24px' --- flowchart ECRD(CRD Controller) EM(MinIO) CA(API<br> with enterprise features) CP(Plugin Daemon) CDB(DB) EA <.-> CA subgraph Dify Enterprise EA(Enterprise API) ECP(Plugin Connector) ECR((Custom<br>Resource)) EPP(Plugin Runner) EBUILD(Kaniko Executor) ECREG((Container<br>Registry)) CP -- [ 2 ] --> ECP ECP -- [ 8 ] --> CP ECP -- [ 3 ] --> ECR ECP -- [ 3 ] --> EM ECRD -- [ 4 ] --> ECR ECRD -- [ 7 ] --> EPP ECRD -- [ 5 ] --> EBUILD -- [ 6 ] --> ECREG -- [ 7 ] --> EPP EBUILD -- [ 5 ] --> EM CP .-> EPP EPP .-> CP subgraph Dify Community CA -- [ 1 ] --> CP CP -- [ 9 ] --> CA CA .-> CDB CP .-> CDB end end
- プラグインをインストールするとき
- API コンテナから Plugin Daemon に、プラグインのインストールが指示されます
- Plugin Daemon から Plugin Connector に、プラグインのインストールが指示されます
- Plugin Connector は、プラグインごとにインストールに必要なアセット一式を準備して MinIO に格納し、DifyPlugin(CR)を Kubernetes クラスタに作成します
- CRD Controller は、Dify Plugin(CR)が作成されたことを認識します
- CRD Controller は、CR の構成に基づき、コンテナイメージを Kaniko でビルドします(Executor の Pod が起動され、コンテキストは MinIO から取得されます)
- ビルドされたイメージがコンテナレジストリにプッシュされます
- CRD Controller は、CR の構成に基づき、前項のステップでビルドされたコンテナイメージを使う Pod(Plugin Runner)と、実行時のエンドポイントとなる Service を作成します
- Plugin Runner の起動完了後、Plugin Connector は Pod(Plugin Runner)のエンドポイント情報を Plugin Daemon に返します
- API コンテナに処理の完了が伝達され、インストールが完了します
こうしてインストールされたプラグインは、実行するとき は次の流れで動作します。いちどインストールされてしまえば、実行時には Plugin Connector や CRD Controller は(たぶん)介さないので、とてもシンプルです。
--- config: theme: 'base' themeVariables: primaryColor: '#0033ff' primaryTextColor: '#fff' secondaryColor: '#888' lineColor: '#333' tertiaryColor: '#e5eaff' tertiaryBorderColor: '#888' fontSize: '24px' --- flowchart ECRD(CRD Controller) EM(MinIO) CA(API<br> with enterprise features) CP(Plugin Daemon) CDB(DB) EA <.-> CA subgraph Dify Enterprise EA(Enterprise API) ECP(Plugin Connector) ECR((Custom<br>Resource)) EPP(Plugin Runner) EBUILD(Kaniko Executor) ECREG((Container<br>Registry)) CP .-> ECP ECP .-> CP ECP .-> ECR ECP .-> EM ECRD .-> ECR ECRD .-> EPP ECRD .-> EBUILD .-> ECREG .-> EPP EBUILD .-> EM CP -- [ 2 ] --> EPP EPP -- [ 3 ] --> CP subgraph Dify Community CA -- [ 1 ] --> CP CP -- [ 4 ] --> CA CA .-> CDB CP .-> CDB end end
- プラグインを実行するとき
- API コンテナから Plugin Daemon に、プラグインの実行が指示されます
- 前項のステップで起動状態になった Pod(Plugin Runner)のエンドポイントに、Plugin Daemon からリクエストが投げられます
- Pod(Plugin Runner)から結果が Plugin Daemon に返ります
- Plugin Daemon から結果が API コンテナに返ります
とくにインストール時の処理の流れは複雑ですが、俯瞰してみると、つまり、すべてのプラグインをプラグインごとに完全に独立した Pod(AWS では AWS Lambda)で動作させるアーキテクチャであることがわかります。
プラグインの実処理にかかる負荷を Plugin Daemon からは完全に切り離せているほか、プラグイン同士の影響も完全に排除できていることになり、プラグインが大量にインストールされた環境でも、Kubernetes クラスタ側の計算資源が潤沢であれば、全体としてはうまく負荷が分散されて安定しそうです。やや富豪的な印象もありますが、可用性や拡張性などの面でもメリットがありそうですね。
イメージのビルドや Pod の作成が、Plugin Connector ではなくあくまで Custom Resource とそのための Custom Controller を介して行われるあたりもおもしろい構成です。責任範囲を限定して、非同期的に処理でき、超大規模なデプロイにも耐えられるような工夫でしょうか。
Plugin Connector も CRD Controller も、ソースコードが非公開なのが惜しいですね……。中を見たい……。あと、Kaniko、もうメンテナンスされなくなっているようですが、いつまで使うのかなあというのも気になるポイントです。
おわりに
公開情報をもとに、Dify の Enterprise 版のアーキテクチャをちょっとだけ(かつ推測交じりで)紹介しました。
プラグインまわりの動きについては、インストールのたびにコンテナイメージが新しくできてプッシュされるっぽいなあというのは推測できていたものの、Kubernetes 上に Custom Resource と Custom Controller ができあがるのは予想外でした。よくできていますね。
一方で、それを実現するために、CRD をクラスタに突っ込む権限がないと動作要件を満たせなくなる点だったり、コンテナレジストリが必要だったり、プラグインの数だけ Pod が増えていったりと、権限、環境、リソースなどの面で、本番環境に導入するにはちょこちょこと要注意なポイントがあるのかもしれません。
なにはともあれ、こういう探求には自由研究的なおもしろさがあってよいものですね。引き続き、興味と好奇心の続く限り、今まで通り趣味の範囲でコントリビューションを続けていきます。