AKSクラスター上にHelmでDifyコミュニティ版を構築する

こんにちは。tubone24です。

Difyにはセルフホステッド版が存在しております。

github.com

こちらをAzure Kubernetes Service (AKS)上に構築する手順についてまとめていきます。

手順の概略

本手順はdouban/charts difyを用いてDifyの動作に必要なサービス郡をAKS上に作ります。

基本的にHelmを用いて一式が立ち上がりますが、パブリックアクセスでDifyにアクセスしたいため、 パブリックアクセス用にイングレスをLoadBarancerタイプに紐づける形で新規作成する手順が追加されております。

事前準備

開発環境整備

Macなどのローカル環境から構築手順を実施する場合はこちらの手順を実施し、環境を作成してください。

Azure Cloud Shellを使って構築も可能ですが、その場合はAzure CLI, Helm, kubectlがすでにインストール済みのため、こちらの手順は不要となります。

Homebrewがインストール済みのMacの場合、次のコマンドでインストール可能です。

# 開発ツール一式をインストール
brew install helm
brew install azure-cli
brew install kubectl

# Azureにコマンドラインでログイン
az login
az account set --subscription ${サブスクリプションID}

Azureの設定更新

下記コマンドはサブスクリプションの操作権限以上がないと実行できないので、リソースグループの共同所有者では実施できません。

Azureのアカウント管理者にて実施をお願いします。

  • Aレコードが登録可能なドメインを取得しておく
    • このあとの手順で作成するAKSのLoadbarancerのExternal-IPをAレコードに設定する手順を実施するため

AKSクラスターの作成

douban/charts difyのHelmをそのまま実行しただけでは、デプロイしたサービス一式が外部(パブリック)からできるイングレスを持たない形で立ち上がってきてしまうのでパブリックアクセス可能な http_application_routing を組み込んだAKSクラスターを事前に作ることでパブリックアクセスが可能となります。

AKSクラスター作成は次のコマンドを実施します。

az aks create --resource-group ${リソースグループID} --name ${クラスター名} --node-count 1 --enable-addons http_application_routing --generate-ssh-keys

AKSクラスターの作成には数分かかります。 また、後の手順でkubectlが打てるようにAKSが作成できたタイミングでget-credentialsを実行します。

az aks get-credentials --resource-group ${リソースグループID} --name ${クラスター名}

ACRの作成・インポート

各サービスが使うDifyのDockerイメージをAzure Container Registryにインポートしておきます。

あらかじめ https://github.com/langgenius/dify/releasesを参照し、最新のイメージのタグを控えておきます。(本手順作成の2024/9/16時点ではv0.8.2でした)

# ACRの作成
az acr create --resource-group ${リソースグループID} --name ${acr名} --sku Basic

# ACRに各イメージをインポート
az acr import --name ${acr名} --source docker.io/langgenius/dify-web --image dify-web:latest
az acr import --name ${acr名} --source docker.io/langgenius/dify-api--image dify-api:latest

Helmでサービスをデプロイ

douban/charts difyを使いDifyのサービスをデプロイしていきます。

基本的にはdouban公式の手順に則って進めていきます。

values.ymlを作成

Helmコマンドに渡すvalues.yamlを作成します。 FixMeの箇所を実際の値に置き換えて使用してください。

global:
  host: "dify.example.com"  # FixMe: 実際に利用可能なドメインを指定
  enableTLS: false
  image:
    tag: "0.8.2"  # FixMe最新バージョンに更新
  extraBackendEnvs:
    - name: SECRET_KEY
      value: "testdify" # FixMe: 必ず推測困難な文字列にしてください
    - name: LOG_LEVEL
      value: "DEBUG"
    - name: VECTOR_STORE
      value: "milvus"
ingress:
  enabled: true
  className: "nginx"
minio:
  embedded: true

Helmレポジトリの取得と更新

Helmコマンドを用いて、doubanのHelmレポジトリを取得します。

helm repo add douban https://douban.github.io/charts/
helm update

Helmでサービスをデプロイ

先ほど作成したvalues.yamlをhelmコマンドで指定して実行します。

helm upgrade dify douban/dify -f values.yaml --install --debug

しばらくすると、AKS上Defaultのnamespaceに次のサービスが立ち上がってきます。

(AzureポータルのKubernetes サービスのサービスとイングレスから確認可能です。)

kubectlでも確認が可能です。 すべてのサービスが登録されていることを確認します。

kubectl get services -n default  
NAME                  TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)             AGE
dify-api-svc          ClusterIP   10.0.234.25    <none>        80/TCP              96m
dify-frontend         ClusterIP   10.0.115.89    <none>        80/TCP              96m
dify-minio            ClusterIP   10.0.186.110   <none>        9000/TCP,9001/TCP   96m
dify-postgresql       ClusterIP   10.0.219.74    <none>        5432/TCP            96m
dify-postgresql-hl    ClusterIP   None           <none>        5432/TCP            96m
dify-redis-headless   ClusterIP   None           <none>        6379/TCP            96m
dify-redis-master     ClusterIP   10.0.226.40    <none>        6379/TCP            96m
dify-sandbox          ClusterIP   10.0.35.3      <none>        80/TCP              96m
kubernetes            ClusterIP   10.0.0.1       <none>        443/TCP             102m

また、各podの稼働状況も確認しておきます。

手順が問題なく実行できていれば、すべてのpodがRunningになっているはずです。

もしRunningになっていない場合は、サブスクリプションのリソースプロバイダーの登録状況や、ACRイメージのアクセス周りが問題と思われますのでご確認ください。

kubectl get pods -n default                                                                                                                                          
NAME                             READY   STATUS    RESTARTS   AGE
dify-api-6d6d5bbbb5-zlbdg        1/1     Running   0          61m
dify-frontend-5f5d7fdddd-qcpvc   1/1     Running   0          61m
dify-minio-56b7d4779d-wzd6t      1/1     Running   0          97m
dify-postgresql-0                1/1     Running   0          97m
dify-redis-master-0              1/1     Running   0          97m
dify-sandbox-7bf987566c-f664s    1/1     Running   0          97m
dify-worker-68476c7dcc-7tmzc     1/1     Running   0          61m

doubanのHelm chartを実行すると、nginxでイングレスも作成されます。

ただし、こちらのイングレスは利用せず、別途AKSクラスターを作成したときに用意したhttp_application_routing のLoadBalancerと紐づける別のイングレスを利用します。

DBマイグレーション

Difyが利用するPostgreSQLにDBマイグレーションを実施します。

APIサーバーが立っているPodでexecでDBマイグレーションコマンドを実行すればOKです。

あらかじめAPIサーバーのpod名を確認し、execコマンドを実行しましょう。

kubectl get pods -n default
NAME                             READY   STATUS    RESTARTS   AGE
dify-api-6d6d5bbbb5-zlbdg        1/1     Running   0          61m
dify-frontend-5f5d7fdddd-qcpvc   1/1     Running   0          61m
dify-minio-56b7d4779d-wzd6t      1/1     Running   0          97m
dify-postgresql-0                1/1     Running   0          97m
dify-redis-master-0              1/1     Running   0          97m
dify-sandbox-7bf987566c-f664s    1/1     Running   0          97m
dify-worker-68476c7dcc-7tmzc     1/1     Running   0          61m

ここではdify-api-6d6d5bbbb5-zlbdgという名前のPodなのでこちらのpod上でDBのマイグレーションスクリプトを実行します。

実際の手順ではpod名はget podsコマンドの結果を下に変更してください。

kubectl exec -it dify-api-6d6d5bbbb5-zlbdg -- flask db upgrade

Load barancerの更新

先ほどAKSクラスターを作成した際にhttp_application_routingで作成済みのLoadBarancerをdifyのイングレスとして組み込みます。

dify-ingress.yamlを作成し、先ほど作成したhttp_application_routingに各サービスを紐づけて更新します。

(もちろん、Helmコマンドで作成済みのイングレスを更新する形でも構いませんが、その場合kubectl applyが打てるように kubectl.kubernetes.io/last-applied-configuration を追加したYAMLを作成済みのイングレスのYAML定義に追加する形で作成します。)

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: dify-ingress
  annotations:
    kubernetes.io/ingress.class: addon-http-application-routing
spec:
  rules:
  - http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: dify-frontend
            port:
              number: 80
      - path: /console/api
        pathType: Prefix
        backend:
          service:
            name: dify-api-svc
            port:
              number: 80
      - path: /api
        pathType: Prefix
        backend:
          service:
            name: dify-api-svc
            port:
              number: 80
      - path: /v1
        pathType: Prefix
        backend:
          service:
            name: dify-api-svc
            port:
              number: 80
      - path: /files
        pathType: Prefix
        backend:
          service:
            name: dify-api-svc
            port:
              number: 80

kubectl applyでイングレスを更新します。

 kubectl apply -f dify-ingress.yaml

暫く待つと、LoadBarancerのExternal-IPアドレス経由でDifyにアクセスできるようになります。

kubectl get ingress -n default

を実行し、dify-ingressのAddressにアクセスすると、Difyのログイン画面が表示されるはずです。

ただし、この時点でサインインやインストールはできません。

なぜならDifyのフロントエンド(Next.js)がAPIサーバーへアクセスするドメインを上記のIPアドレスでなく、values.yamlに指定したglobal.hostの値を利用するためです。

DNSの更新

利用しているDNSサービスでAレコードを作成します。 作成したAレコードの値にはLoadBarancerのExternal-IPを指定します。

再度ドメインでアクセスする

無事全部の設定が問題なく完了していればこの時点で http://ドメイン/install にアクセスすることで管理者アカウントを作成可能になります。

もし、/installにアクセスした際にInternal server errorなどが出るようであれば、 作成したイングレスの設定もしくは、DBマイグレーションが正しくできてない可能性があるので、再度確認してください。