TECH BLOG

エルカミーの技術ブログです

Azure VM上にDifyを構築し、Azure OpenAIとPrivate Linkで閉域接続する方法

はじめに

Difyをエンタープライズな企業環境で運用する場合、「AIモデルとの通信がインターネットを経由しないこと」がセキュリティ要件として求められるケースが多いです。

本記事では、Azure VM上にDify(OSS版)を構築し、Azure OpenAI Service(AOAI)とPrivate Linkで閉域接続する構成を解説します。これにより、ユーザーの入力データやAIの応答がパブリックインターネットを経由しない構成になります。

対象読者
  • Difyをセキュアな環境で運用したいインフラエンジニア
  • Azure上でのAI基盤構築を検討している方
前提(この記事で扱う範囲)
  • Azure が利用できること
  • VNet / NSG / Private Endpoint / Private DNS の基礎用語を知っている(詳説はしない)
  • DifyはOSS版(Docker Compose)を想定
構成のゴール
  • AOAIのパブリックアクセス無効
  • VMからAOAIの名前解決が privatelink.openai.azure.com → 10.0.x.x になる
  • 外部公開はFront Door経由のみ(VMのインバウンドはFront Doorのサービスタグで制限)

全体アーキテクチャ
graph TD
    subgraph Internet["インターネット"]
        User["👤 ユーザー<br/>(許可IPのみ)"]
    end

    subgraph Azure["Azure"]
        AFD["Azure Front Door + WAF<br/>IP制限: 許可リストのみ Allow / Default: Block"]

        subgraph VNet["VNet <br/>仮想ネットワーク(閉域網)"]
            VM["Azure VM<br/>Dify コンテナ群"]
            Disk["💾 永続データディスク<br/>(/data)"]
            PE["Private Endpoint<br/>(プライベートIP 10.0.x.x)"]
        end

        AOAI["Azure OpenAI Service<br/>(パブリックアクセス無効)"]
    end

    User -->|"HTTPS"| AFD
    AFD -->|"NSG: AzureFrontDoor.Backend のみ許可"| VM
    VM --- Disk
    VM -->|"VNet内通信<br/>(Internet不使用)"| PE
    PE -->|"Azure Backbone"| AOAI

    style AFD fill:#f96,stroke:#333,stroke-width:2px
    style VM fill:#9cf,stroke:#333,stroke-width:2px
    style AOAI fill:#9f9,stroke:#333,stroke-width:2px
    style PE fill:#fcf,stroke:#333,stroke-width:1px
ポイント
  • ユーザー → Dify:Front Door + WAF でIP制限
  • Dify → AOAI:Private Link で閉域接続(インターネット不使用)
  • SSH管理:NSGでIP制限 + Entra ID認証(オプション)

構築手順の全体像
フェーズ 作業内容 所要時間(目安)
1 VNet / Subnet / NSG 作成 15分
2 VM作成 + Docker + Dify デプロイ 30分
3 Azure OpenAI の Private Link 化 30分
4 Front Door + WAF 設定 30分
5 動作確認 + 監視設定 15分

以降、各フェーズの要点とハマりポイントを解説する。


フェーズ1:ネットワーク基盤(VNet / NSG)
VNet / Subnet 作成
# 変数定義
RG_NAME="your-rg"
LOCATION="japaneast"
VNET_NAME="dify-vnet"
SUBNET_NAME="dify-subnet"
NSG_NAME="dify-nsg"

# VNet作成(単一サブネット構成)
az network vnet create \
  -g $RG_NAME -n $VNET_NAME \
  --address-prefix 10.0.0.0/16 \
  --subnet-name $SUBNET_NAME \
  --subnet-prefix 10.0.1.0/24
NSGルール設計

ここが最初の設計判断ポイント。Front Doorを前段に置く場合、VMのNSGは以下の2ルールだけでよい。

# Web (80/443): Front Doorからのみ許可
az network nsg rule create \
  -g $RG_NAME --nsg-name $NSG_NAME -n AllowWebFromFrontDoor \
  --priority 100 \
  --source-address-prefixes "AzureFrontDoor.Backend" \
  --destination-port-ranges 80 443 \
  --access Allow --protocol Tcp --direction Inbound

# SSH (22): 管理者IPのみ許可
az network nsg rule create \
  -g $RG_NAME --nsg-name $NSG_NAME -n AllowAdminSSH \
  --priority 110 \
  --source-address-prefixes "<YOUR_ADMIN_IP>/32" \
  --destination-port-ranges 22 \
  --access Allow --protocol Tcp --direction Inbound
サービスタグ AzureFrontDoor.Backend を使うことで、Front Doorからのトラフィックだけを許可できる。IPアドレスのハードコードが不要で、Azure側のIP変更にも自動追従する。

フェーズ2:VM + Dify デプロイ
VMスペック(推奨最小構成)
項目
サイズ Standard_B2s(2 vCPU / 4GB RAM)
OS Ubuntu 22.04 LTS
ディスク OSディスク 30GB + データディスク 64〜100GB
永続データディスクの設計

Difyのデータ(DB、ナレッジベース、設定)はOSディスクとは分離して永続ディスクに配置する。これにより、VMの再作成時もデータが保全される。

# データディスクをアタッチ
az vm disk attach \
  -g $RG_NAME --vm-name $VM_NAME \
  --name dify-data-disk \
  --new --size-gb 100

# VM内でマウント(初回のみ)
sudo mkfs.ext4 /dev/sda
sudo mkdir -p /data
sudo mount /dev/sda /data

# fstabに追記(再起動後も自動マウント)
UUID=$(sudo blkid /dev/sda -s UUID -o value)
echo "UUID=$UUID /data ext4 defaults,nofail 0 2" | sudo tee -a /etc/fstab
Difyのデプロイ
cd /data
git clone https://github.com/langgenius/dify.git
cd dify/docker
cp .env.example .env
docker compose up -d
Tips: Difyの永続データ(volumes/)は/data配下にシンボリックリンクを張ると、バックアップ対象が明確になる。

フェーズ3:Azure OpenAI の Private Link 化
なぜPrivate Linkが必要か

通常、DifyからAOAIへのAPI呼び出しはインターネットを経由する。Private Linkを使うと、この通信がAzureのバックボーンネットワーク内で完結し、パブリックインターネットを一切通らなくなる。

【Before】VM → インターネット → AOAI(パブリックエンドポイント)
【After】 VM → VNet内Private IP → Azure Backbone → AOAI(閉域)
手順1:AOAIリソース作成 + カスタムドメイン有効化
AOAI_NAME="your-aoai"

az cognitiveservices account create \
  -g $RG_NAME -n $AOAI_NAME \
  --kind OpenAI --sku s0 -l $LOCATION --yes

# カスタムドメイン有効化(Private Link使用時に必須)
az cognitiveservices account update \
  -g $RG_NAME -n $AOAI_NAME \
  --custom-domain $AOAI_NAME
ハマりポイント1: Private Linkを使う場合、カスタムドメインの有効化が必須。これを忘れると名前解決が正しく動かない。
手順2:Private Endpoint 作成
PE_NAME="dify-pe-aoai"

az network private-endpoint create \
  -g $RG_NAME -n $PE_NAME \
  --vnet-name $VNET_NAME \
  --subnet $SUBNET_NAME \
  --private-connection-resource-id \
    $(az cognitiveservices account show -g $RG_NAME -n $AOAI_NAME --query id -o tsv) \
  --group-id account \
  --connection-name aoai-conn

この時点で、サブネット内にPrivate Endpoint用のNICが作成され、プライベートIPが1つ割り当てられる。

手順3:Private DNS Zone の設定

Private Endpointを作っただけでは、VMからAOAIのFQDNを引いたときにパブリックIPが返ってしまう。Private DNS Zoneを設定し、VNet内ではプライベートIPに解決されるようにする。

DNS_ZONE="privatelink.openai.azure.com"

# DNS Zone作成
az network private-dns zone create \
  -g $RG_NAME -n $DNS_ZONE

# VNetとリンク
az network private-dns link vnet create \
  -g $RG_NAME -n aoai-dns-link \
  --zone-name $DNS_ZONE \
  --virtual-network $VNET_NAME \
  --registration-enabled false

# Private EndpointのDNSレコードを自動作成
az network private-endpoint dns-zone-group create \
  -g $RG_NAME --endpoint-name $PE_NAME \
  -n aoai-dns-group \
  --private-dns-zone $DNS_ZONE \
  --zone-name aoai-zone
手順4:パブリックアクセスの無効化

AOAIのネットワーク設定で「パブリックアクセスを無効」にする。これにより、Private Endpoint経由以外のアクセスが完全に遮断される。

現時点(2026年3月)ではAzure Portal上での手動設定が確実。
手順5:疎通確認
# VM内から実行
nslookup your-aoai.openai.azure.com

# 期待する結果:
# Name:    your-aoai.privatelink.openai.azure.com
# Address: 10.0.1.x  ← VNet内のプライベートIPが返ればOK
ハマりポイント2: nslookupの結果がパブリックIPのままの場合、Private DNS ZoneとVNetのリンクが正しく設定されていない。az network private-dns link vnet listで確認する。

フェーズ4:Front Door + WAF によるアクセス制御
なぜFront Doorを使うのか

NSGだけでもIP制限は可能だが、Front Door + WAFを使うメリットがある。

比較項目 NSGのみ Front Door + WAF
IP制限 可能 可能(より柔軟)
WAF(攻撃防御) なし あり
SSL終端 VM側で管理 Front Doorが管理
カスタムドメイン 自前設定 マネージド証明書対応
将来のスケーリング 困難 容易
WAFポリシーの設計
ポリシーモード:Prevention(遮断)

ルール1: AllowOfficeIPs(優先度100条件: RemoteAddr が 許可IPリスト に一致
  アクション: Allow

ルール2: DenyAll(優先度1000条件: RemoteAddr が 0.0.0.0/0
  アクション: Deny
ハマりポイント3: 接続元のIPがIPv6の場合、IPv4のみの許可リストでは弾かれる。IPv6アドレスも確認してリストに追加すること。
Difyの.env設定

Front Door経由でアクセスする場合、DifyのURLをFront Doorのエンドポイントに変更する必要がある。

# .env の該当箇所を修正
CONSOLE_API_URL=https://your-endpoint.azurefd.net
CONSOLE_WEB_URL=https://your-endpoint.azurefd.net
SERVICE_API_URL=https://your-endpoint.azurefd.net
APP_WEB_URL=https://your-endpoint.azurefd.net

# 反映
docker compose down && docker compose up -d

フェーズ5:監視設定

最低限、以下の2つのアラートを設定する。

VM CPU高騰アラート
az monitor metrics alert create \
  -g "$RG_NAME" \
  -n "alert-vm-cpu-high" \
  --scopes "$VM_ID" \
  --condition "avg Percentage CPU > 80" \
  --window-size 5m \
  --evaluation-frequency 1m \
  --severity 2 \
  --action "$ACTION_GROUP_ID"
Front Door ヘルスチェック失敗アラート
az monitor metrics alert create \
  -g "$RG_NAME" \
  -n "alert-afd-origin-health" \
  --scopes "$AFD_ID" \
  --condition "avg OriginHealthPercentage < 100" \
  --window-size 1m \
  --evaluation-frequency 1m \
  --severity 1 \
  --action "$ACTION_GROUP_ID"
Tips: Front Doorのヘルスプローブパスはデフォルトで/だが、Difyのログイン画面が200を返すだけの場合がある。可能であれば/health等の専用エンドポイントを用意するとより確実。

構築時のハマりポイントまとめ
# ハマりポイント 原因 対処法
1 Private Link接続後もパブリックIP経由になる カスタムドメインが未設定 az cognitiveservices account update --custom-domain を実行
2 nslookupでプライベートIPが返らない Private DNS ZoneとVNetが未リンク private-dns link vnet create を確認
3 Front Door経由でアクセスできない Difyの.envがVM直接アクセスのURLのまま .envのURL群をFront Doorエンドポイントに変更
4 WAFで許可IPなのにブロックされる IPv6アドレスが許可リストにない curl -6 ifconfig.me でIPv6を確認して追加
5 Front Doorのオリジン登録でエラー host-nameにIPアドレスを直接指定 VMのDNSラベル名を使用する

コスト目安(月額・参考値)
リソース SKU 月額目安
Azure VM Standard_B2s 約5,000円
Azure OpenAI S0(従量課金) 利用量に依存
Front Door Standard 約5,000円〜
データディスク 100GB Standard SSD 約1,500円
バックアップ Recovery Services 約500円
合計(AOAI除く) 約12,000円〜/月
※ 2026年3月時点の概算。為替レートや利用量により変動。

まとめ
  • Private Link により、Dify ↔ AOAI間の通信をAzureバックボーン内に閉じることができる
  • Front Door + WAF で、ユーザーアクセスをIP単位で制御できる
  • 構築自体はAzure CLIで2〜3時間程度で完了するが、DNS周りの設定ミスがもっとも多いハマりポイント
  • 本構成はPoC向けの最小構成であり、本番運用では冗長化・バックアップ・監視の強化が必要

参考リンク

Dify構築運用のご相談