【完全自動化】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環境】
- GitHubリポジトリのActions > 対象のワークフロー(例では「Difyバージョンアップ」) に移動します。
- Run workflowに「前準備」の「GitHub Actions実行に必要な情報」を入力し、Run workflowを実行します。
- バージョンアップ後のDifyの動作確認を行います。
- DifyのWeb UIにアクセスし、正常に動作することを確認。
- Difyの各種機能が正常に動作することを確認。
Dify自動化で学んだ2つの落とし穴とAzure環境特有のハマりポイント
- 設定変更のタイムラグ
ネットワーク設定を変更した際、その変更がシステムに反映されるまでの時間を考慮することは、エンジニアにとって非常に重要です。
具体的には、以下のようなハマりポイントがありました。- ハマりポイント: GitHub ActionsのIPアドレスをNSGやAzure Key Vaultに登録直後にVMへSSH接続を試みたところ、接続が拒否される事態に陥りました。
- 原因: 設定変更が反映されるまでのタイムラグを考慮していなかったため。
- 解決方法: SSH接続の処理を、設定が確実に反映されるまで待機するように実装し直しました。
- トラブルシューティングの重要性
Azure CLIの活用が非常に有効であることを再認識しました。
AnsibleでVMへのSSH接続を試みた際に以下の問題が発生しました。
- 問題: 設定は正しいはずなのに認証エラーが発生。
- 最終的な原因: Azure Key VaultへのVM秘密鍵登録時に改行コードが含まれていなかった。Azure Portalからの直接入力では改行が正しく認識されず、認証エラーにつながった。
- 解決方法: Azure CLIからファイルの内容を指定して登録することで、改行コードを適切に処理し、認証が成功しました。
この経験を通じて、GUIだけでなくCLIも使いこなすことの重要性を再確認しました。
まとめ:Difyの継続運用に向けたバージョンアップ自動化の今後とマルチクラウド展開への展望
本記事では、Difyのバージョンアップ自動化についてまとめました。今回はAzure環境での構築例をご紹介しましたが、AWSやGCPなどの他のクラウドプラットフォームにも応用できる考え方です。今後は、他のクラウド環境における自動化手法の検討や、Firewallが設定された環境でのバージョンアップ自動化、さらにローカルデプロイ環境におけるDifyの構築自動化など、より深く掘り下げていきたいと考えています。
この記事を書いた人

関連記事
- TerraformとAnsibleで生成AI基盤のインフラを...
生成AI基盤チーム
- 【E2Eテスト自動化】Playwrightの特徴・導入方法・...
金 柾勲(キム・ジョンフン)
Advent Calendar!
Advent Calendar 2024