【完全自動化】DifyのバージョンアップをAzure×GitHub Actions×Terraform×Ansibleで実現する方法

はじめに 

Difyは、ローコード・ノーコードでAIを活用したアプリケーションを構築・運用できるプラットフォームです。RAG(Retrieval-Augmented Generation)やエージェント機能を駆使した検証実験が行えます。Difyは日々更新されるオープンソースソフトウェアであり、バージョンアップ頻度が高いため、手動でのアップデート作業に時間を要していました。そこで、バージョンアップのプロセスを自動化し、作業時間の短縮を図ることにしました。

Azure上のDify環境におけるCI/CD構成と自動アップデートのゴールとは

本手順では、Azure VM上で#attach(ゴールイメージ.png:437309)稼働するオープンソース版Difyのバージョンアップを対象とし、GitHub Actions、Terraform、およびAnsibleを使用して自動化を構成します。以下にゴールのイメージを示します。

※Difyのバージョンは指定できず、最新バージョンにアップグレードされます。

AzureとGitHub ActionsでDify自動化を行う前の準備ステップ【サービスプリンシパル/資格情報設定】

本手順を実行する前に、以下の準備を行います。

Azureのサービスプリンシパルの作成

  • Azure CLIを用いて、サービスプリンシパルを作成し、必要なアクセス許可を設定します。
  • 作成時に表示されるアプリケーションIDやパスワード、テナントIDはメモしてください。
# AzureCLIを使用してアカウントのアクセス許可をローカルに設定
$ az login --use-device-code

# リソースグループにサービスプリンシパルを作成
$ az ad sp create-for-rbac --role="Contributor" --display-name="${サービスプリンシパル名}" --scopes="/subscriptions/${サブスクリプションID}/resourceGroups/${リソースグループ名}"

# サービスプリンシパル作成時に出力される情報をメモしておく
Creating 'Contributor' role assignment under scope '/subscriptions/${サブスクリプションID}/resourceGroups/${リソースグループ名}'
The output includes credentials that you must protect. Be sure that you do not include these credentials in your code or check the credentials into your source control. For more information, see https://aka.ms/azadsp-cli
{
  "appId": "${アプリケーションID}",
  "displayName": "${サービスプリンシパル名}",
  "password": "${サービスプリンシパルのパスワード}",
  "tenant": "${テナントID}"
}

フェデレーション資格情報の追加
GitHub ActionsからAzureに接続するためのフェデレーション資格情報を設定します。
# 以下の公式サイトを元にAzure PortalまたはAzure CLIでフェデレーション資格情報を登録する。

※Azure Portal上からも登録できますが、秘密鍵の改行が抜けてしまい認証が通らないことがあるため、Azure CLI上から登録することをおすすめします。

# Azure CLIでフェデレーション資格情報を登録する場合はディレクトリ構成にある「dify_version_up/.github/credential.json」ファイルを作成し、コマンドを実行。
# <APPLICATION-OBJECT-ID>は「Azureのサービスプリンシパルの作成」で作成した時の「${アプリケーションID}」を入力
$ az ad app federated-credential create --id <APPLICATION-OBJECT-ID> --parameters credential.json

# Azure Key Vaultのシークレット登録
# サーバの秘密鍵やサービスプリンシパル情報をAzure Key Vaultに登録します。
# サーバの秘密鍵をAzure Key Vaultシークレットに登録
$ az keyvault secret set --vault-name <your-vault-name> --name ssh-key --value "$(cat <your-private-key-file>)"

# サービスプリンシパルの${アプリケーションID}をAzure Key Vaultシークレットに登録
$ az keyvault secret set --vault-name <your-vault-name> --name client-id --value "${アプリケーションID}"

# サービスプリンシパルの${サービスプリンシパルのパスワード}をAzure Key Vaultシークレットに登録
$ az keyvault secret set --vault-name <your-vault-name> --name client-secret --value "${サービスプリンシパルのパスワード}"

# ${サブスクリプションID}をAzure Key Vaultシークレットに登録
$ az keyvault secret set --vault-name <your-vault-name> --name subscription-id --value "${サブスクリプションID}"

# ${テナントID}をAzure Key Vaultシークレットに登録
$ az keyvault secret set --vault-name <your-vault-name> --name tenant-id --value "${テナントID}"

GitHub Actionsへのシークレット登録

  • GitHubリポジトリのSettings > Secrets and variables > Actions に移動します。
  • "New repository secret" をクリックします。
  • シークレット名と値を入力し、"Add secret" をクリックします。
  • 登録するシークレットは以下です。

シークレット名

シークレット値

AZURE_CLIENT_ID

サービスプリンシパルの${アプリケーションID}

AZURE_SUBSCRIPTION_ID

${サブスクリプションID}

AZURE_TENANT_ID

${テナントID}

CLIENT_SECRET

${サービスプリンシパルのパスワード}

GitHub Actions実行に必要な情報を取得
Azure Portalなどで確認しておく必要があります。以下の情報をメモしておきます。

  • リソースグループ名
  • Azure VM名
  • OSディスクの完全修飾リソースID
  • Azure Key Vaultのリソース名
  • NSG名

Terraform × Ansible × GitHub Actionsによる自動化構成ディレクトリの全体像

以下が自動化で使用するディレクトリ構成です。

dify_version_up/
├── .github
│   ├── credential.json
│   └── workflows
│       └── deploy.yml
├── ansible
│   └── playbook.yml
├── requirements.txt
└── terraform
    ├── main.tf
    ├── rg.tf
    ├── variables.tf
    └── vm.tf

Terraform/Ansible/GitHub Actionsの各設定ファイルの詳細解説付きサンプル

各ファイルの中身について説明します。

terraform/main.tf
Terraformのtfファイル。OSディスクのスナップショット作成、VMへの接続情報を記述します。

terraform {
  required_providers {
    azurerm = {
      source  = "hashicorp/azurerm"
      version = "~> 3.0.2"
    }
  }
  required_version = ">= 1.1.0"
}

provider "azurerm" {
  skip_provider_registration = true
  features {}
  subscription_id = var.subscription_id
  tenant_id       = var.tenant_id
  client_id       = var.client_id
  client_secret   = var.client_secret
}

locals {
  now_utc  = timestamp()
  now_jst  = timeadd(local.now_utc, "9h")
  datetime = formatdate("YYYYMMDDhhmmss", local.now_jst)
}

# OSディスクのスナップショットを作成
resource "azurerm_snapshot" "example" {
  name                = "${var.vm_name}-snap_${local.datetime}"
  location            = var.location
  resource_group_name = data.azurerm_resource_group.rg.name
  create_option       = "Copy"
  source_resource_id  = var.os_disk_source_resource_id
}

resource "null_resource" "generate_inventory" {
  provisioner "local-exec" {
    command = <<EOF
     echo "[azure_vms]" > ../ansible/hosts.ini
      echo "${data.azurerm_virtual_machine.vm.public_ip_address} ansible_ssh_user=azureuser ansible_ssh_private_key_file=/tmp/ssh_key" >> ../ansible/hosts.ini
EOF
  }
}

terraform/rg.tf
Terraformのtfファイル。既存のリソースグループの定義を記述します。

#-------------------------------
# Resource Group
#-------------------------------

# 既存のリソースグループの情報を取得
data "azurerm_resource_group" "rg" {
  name = var.resource_group_name
}

terraform/vm.tf
Terraformのtfファイル。既存のVMの定義を記述します。

#-------------------------------
# Azure VM
#-------------------------------
# 既存の仮想マシンの情報を取得
data "azurerm_virtual_machine" "vm" {
  resource_group_name = data.azurerm_resource_group.rg.name
  name                = var.vm_name
}

terraform/variables.tf
Terraformの変数定義のtfファイル。

#-------------------------------
# 基本変数定義
#-------------------------------
variable "tenant_id" {}
variable "subscription_id" {}
variable "client_id" {}
variable "client_secret" {}
variable "resource_group_name" {}
variable "os_disk_source_resource_id" {}
variable "vm_name" {}

# Region
variable "location" {
  default = "japaneast"
}

ansible/playbook.yml
AnsibleのPlaybookファイル。Difyのバージョンアップタスクを定義します。

---
- name: Get SSH Key from Azure Key Vault
  hosts: localhost
  connection: local
  collections:
    - azure.azcollection

  vars:
    secret_name_ssh_key: "ssh-key"  # SSH鍵のシークレット名を指定
    temp_ssh_key_path: "/tmp/ssh_key"  # 一時的なSSH鍵の保存先
    ansible_python_interpreter: /usr/bin/python3

  tasks:
  - name: Get Key Vault by name
    azure_rm_keyvault_info:
      resource_group: "{{ resource_group }}"  # コマンドラインから渡された変数を使用
      name: "{{ vault_name }}"                  # コマンドラインから渡された変数を使用
    register: keyvault

  - name: Set key vault URI fact
    set_fact:
      keyvaulturi: "{{ keyvault['keyvaults'][0]['vault_uri'] }}"

  - name: Get secret value for SSH key
    azure_rm_keyvaultsecret_info:
      vault_uri: "{{ keyvaulturi }}"
      name: "{{ secret_name_ssh_key }}"
    register: kvSecretSSHKey

  - name: Set secret fact for SSH key
    set_fact:
      secretValueSSHKey: "{{ kvSecretSSHKey['secrets'][0]['secret'] }}"

  - name: Save SSH Key to a temporary file
    copy:
      content: "{{ secretValueSSHKey }}"
      dest: "{{ temp_ssh_key_path }}"
      mode: '0600'  # SSH鍵の権限を600に設定

  - name: Output key vault secret (for debugging)
    debug:
      msg: "SSH Key saved temporarily to {{ temp_ssh_key_path }}"

- name: Upgrade application on Azure VM
  hosts: azure_vms  # この部分でAzure VMに接続
  collections:
    - azure.azcollection

  tasks:
  - name: Upgrade application on Azure VM
    ansible.builtin.shell: |
      cd /opt/www/dify/docker
      cp docker-compose.yaml docker-compose.yaml.$(date +%s).bak
      git checkout main
      git pull origin main
      sudo docker compose down
      tar -cvf volumes-$(date +%s).tgz volumes
      sudo docker compose up -d

requirements.txt
Github Actionsを実行するコンテナの環境設定ファイルを定義します。

ansible[azure]
azure-cli-core==2.66.0
azure-cli-nspkg==3.0.2
azure-cli-telemetry==1.1.0
azure-common==1.1.28
azure-core==1.32.0
azure-graphrbac==0.40.0
azure-identity==1.20.0
azure-keyvault==4.2.0
azure-keyvault-certificates==4.9.0
azure-keyvault-keys==4.10.0
azure-keyvault-secrets==4.9.0
azure-mgmt-authorization==0.51.1
azure-mgmt-automation==0.1.1
azure-mgmt-batch==5.0.1
azure-mgmt-cdn==3.0.0
azure-mgmt-compute==34.0.0
azure-mgmt-containerinstance==1.4.0
azure-mgmt-containerregistry==2.0.0
azure-mgmt-containerservice==4.4.0
azure-mgmt-core==1.5.0
azure-mgmt-cosmosdb==0.5.2
azure-mgmt-datafactory==9.1.0
azure-mgmt-devtestlabs==3.0.0
azure-mgmt-dns==2.1.0
azure-mgmt-eventhub==11.2.0
azure-mgmt-hdinsight==0.1.0
azure-mgmt-iothub==0.7.0
azure-mgmt-keyvault==10.3.1
azure-mgmt-loganalytics==0.2.0
azure-mgmt-managementgroups==1.0.0
azure-mgmt-marketplaceordering==0.1.0
azure-mgmt-monitor==0.5.2
azure-mgmt-network==28.1.0
azure-mgmt-notificationhubs==8.0.0
azure-mgmt-nspkg==2.0.0
azure-mgmt-privatedns==1.2.0
azure-mgmt-rdbms==10.1.0
azure-mgmt-recoveryservicesbackup==9.1.0
azure-mgmt-redis==5.0.0
azure-mgmt-resource==23.2.0
azure-mgmt-search==9.1.0
azure-mgmt-servicebus==0.5.3
azure-mgmt-sql==3.0.1
azure-mgmt-storage==22.0.0
azure-mgmt-trafficmanager==0.50.0
azure-mgmt-web==0.41.0
azure-nspkg==2.0.0
azure-storage==0.35.1
azure-storage-blob==12.24.1
msgraph-core==1.1.8
msgraph-sdk==1.16.0

.github/credential.json
GitHub ActionsからAzureに接続するためのフェデレーション資格情報を定義します。

{
    "name": "terraform-azurerm",
    "issuer": "https://token.actions.githubusercontent.com",
    "subject": "repo:${Githubの組織名}/${リポジトリ名}:ref:refs/heads/main",
    "description": "フェデレーション資格情報の登録",
    "audiences": [
        "api://AzureADTokenExchange"
    ]
}

.github/workflows/deploy.yml
GitHub Actionsのワークフローファイル。TerraformとAnsibleの実行をトリガーします。

name: Difyバージョンアップ
permissions:
      id-token: write
      contents: read
on:
  workflow_dispatch:
    inputs:
      resource_group_name:
        description: 'リソースグループ名'
        required: true
      vm_name:
        description: 'VM名'
        required: true
      os_disk_source_resource_id:
        description: 'OSディスクの完全修飾リソースID'
        required: true
      vault_name:
        description: 'Azure Key Vaultのリソース名'
        required: true
      nsg_name:
        description: 'NSG名'
        required: true

jobs:
  upgrade-vm:
    runs-on: ubuntu-latest

    steps:
    - name: Checkout repository
      uses: actions/checkout@v2
      
    - name: 'Az CLI login'
      uses: azure/login@v1
      with:
          client-id: ${{ secrets.AZURE_CLIENT_ID }}
          tenant-id: ${{ secrets.AZURE_TENANT_ID }}
          subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}

    - name: Set up Terraform
      uses: hashicorp/setup-terraform@v1
      with:
        terraform_version: 1.3.0

    - name: Initialize Terraform
      run: terraform init
      working-directory: dify_version_up/terraform

    - name: TerraformのLintを実行
      run: terraform fmt
      working-directory: dify_version_up/terraform

    - name: Apply Terraform
      env:
        TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
        SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
        CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
        CLIENT_SECRET: ${{ secrets.CLIENT_SECRET }}
      run: |
        terraform apply -auto-approve \
                        -var="tenant_id=${{ secrets.AZURE_TENANT_ID }}" \
                        -var="subscription_id=${{ secrets.AZURE_SUBSCRIPTION_ID }}" \
                        -var="client_id=${{ secrets.AZURE_CLIENT_ID }}" \
                        -var="client_secret=${{ secrets.CLIENT_SECRET }}" \
                        -var="resource_group_name=${{ github.event.inputs.resource_group_name }}" \
                        -var="vm_name=${{ github.event.inputs.vm_name }}" \
                        -var="os_disk_source_resource_id=${{ github.event.inputs.os_disk_source_resource_id }}"
      working-directory: dify_version_up/terraform

    # Azure Key Vaultに実行時のIPアドレスを穴あけする
    - name: Add IP Address For Key Vault
      uses: azure/CLI@v1
      id: get_ip # IPアドレスを取得するステップにIDを付与
      with:
        azcliversion: 2.36.0
        inlineScript: |
          IP=$(curl -s https://checkip.amazonaws.com/)
          echo "::set-output name=IP::$IP" # outputs を設定する推奨形式
          az keyvault network-rule add \
            --resource-group "${{ github.event.inputs.resource_group_name }}" \
            --name "${{ github.event.inputs.vault_name }}" \
            --ip-address "${IP}/32"

          az network nsg rule create \
            --resource-group "${{ github.event.inputs.resource_group_name }}" \
            --nsg-name "${{ github.event.inputs.nsg_name }}" \
            --name SSH-Temp \
            --priority 1000 \
            --source-address-prefixes "${IP}" \
            --destination-port-ranges 22 \
            --access Allow \
            --protocol Tcp \
            --direction Inbound

    - name: Set up Ansible
      run: |
        sudo apt-get update
        sudo apt-get install -y ansible python3-pip
        pip3 install -r dify_version_up/requirements.txt
        ansible-galaxy collection install azure.azcollection

    - name: Run Ansible Playbook
      run: |
        ansible-playbook -i ./hosts.ini \
                         -e 'ansible_ssh_common_args="-o StrictHostKeyChecking=no"' \
                         -e "resource_group=${{ github.event.inputs.resource_group_name }}" \
                         -e "vault_name=${{ github.event.inputs.vault_name }}" \
                         playbook.yml
      working-directory: dify_version_up/ansible

    # Azure Key Vaultに実行時に穴あけしたIPアドレスを削除する
    - name: Remove IP Adress For Key Vault
      uses: azure/CLI@v1
      with:
        azcliversion: 2.36.0
        inlineScript: |
          IP="${{ steps.get_ip.outputs.IP }}" # 穴あけ処理で取得したIPアドレスを使用
          az keyvault network-rule remove \
            --resource-group "${{ github.event.inputs.resource_group_name }}" \
            --name "${{ github.event.inputs.vault_name }}" \
            --ip-address "${IP}/32"

          az network nsg rule delete \
            --resource-group "${{ github.event.inputs.resource_group_name }}" \
            --nsg-name "${{ github.event.inputs.nsg_name }}" \
            --name SSH-Temp

Difyのバージョンアップを自動化するGitHub Actionsの実行手順【Azure環境】

  1. GitHubリポジトリのActions > 対象のワークフロー(例では「Difyバージョンアップ」)   に移動します。
  2. Run workflowに「前準備」の「GitHub Actions実行に必要な情報」を入力し、Run workflowを実行します。
  3. バージョンアップ後のDifyの動作確認を行います。
    • DifyのWeb UIにアクセスし、正常に動作することを確認。
    • Difyの各種機能が正常に動作することを確認。

Dify自動化で学んだ2つの落とし穴とAzure環境特有のハマりポイント

  1. 設定変更のタイムラグ

    ネットワーク設定を変更した際、その変更がシステムに反映されるまでの時間を考慮することは、エンジニアにとって非常に重要です。
    具体的には、以下のようなハマりポイントがありました。

    • ハマりポイント: GitHub ActionsのIPアドレスをNSGやAzure Key Vaultに登録直後にVMへSSH接続を試みたところ、接続が拒否される事態に陥りました。
    • 原因: 設定変更が反映されるまでのタイムラグを考慮していなかったため。
    • 解決方法: SSH接続の処理を、設定が確実に反映されるまで待機するように実装し直しました。

  2. トラブルシューティングの重要性

    Azure CLIの活用が非常に有効であることを再認識しました。

    AnsibleでVMへのSSH接続を試みた際に以下の問題が発生しました。

    • 問題: 設定は正しいはずなのに認証エラーが発生。
    • 最終的な原因: Azure Key VaultへのVM秘密鍵登録時に改行コードが含まれていなかった。Azure Portalからの直接入力では改行が正しく認識されず、認証エラーにつながった。
    • 解決方法: Azure CLIからファイルの内容を指定して登録することで、改行コードを適切に処理し、認証が成功しました。


    この経験を通じて、GUIだけでなくCLIも使いこなすことの重要性を再確認しました。

まとめ:Difyの継続運用に向けたバージョンアップ自動化の今後とマルチクラウド展開への展望

本記事では、Difyのバージョンアップ自動化についてまとめました。今回はAzure環境での構築例をご紹介しましたが、AWSやGCPなどの他のクラウドプラットフォームにも応用できる考え方です。今後は、他のクラウド環境における自動化手法の検討や、Firewallが設定された環境でのバージョンアップ自動化、さらにローカルデプロイ環境におけるDifyの構築自動化など、より深く掘り下げていきたいと考えています。

この記事が役に立ったと思ったら、
ぜひ「いいね」とシェアをお願いします!

リンクをコピーXでシェアするfacebookでシェアする

この記事を書いた人

生成AI基盤チーム
生成AI基盤チーム
メンバーズ社内の部署横断で、生成AI基盤の設計・構築、生成AI活用支援、ナレッジ共有などを行っているR&Dチームです。現在は、DevOps支援を専門とするデブオプスリードカンパニーのメンバーを中心とした構成になっています。DevOpsやSRE、インフラなどの観点を中心に、さまざまなナレッジを積極的に発信していきます!
詳しく見る
ページトップへ戻る