はじめに
弊社では生成AI活用の基盤として、DifyをGoogle Cloud上にセルフホストして活用しています。
大規模ではないため、運用負荷を抑えつつセキュアに運用できるよう、以下を重視して構成しました。その内容について本記事では紹介させて頂きます。
- Terraformでインフラをコード管理(再現性・変更管理)
- GCEに外部IPを付与しない(インスタンスへの直接到達を遮断)
- Identity-Aware Proxy (IAP) でGoogleアカウント認証を必須化(IDベースのアクセス制御)
なお、弊社の規模ではそこまでするのは工数・コストに見合わないと感じたため採用しませんでしたが、DifyのGitHubでサーバレスな構成をTerraformで構築するサンプルのリンクもありましたので、そちらの用途が合う方は以下をご覧ください。
構築するアーキテクチャ
今回構築するアーキテクチャの全体像は以下の通りです。
ポイントは以下の2点です。
- 外部IPを持たないGCEインスタンス: Difyが稼働するGCEインスタンスには外部IPアドレスを付与しません。これにより、意図しない外部からのインスタンスへの直接の到達を遮断します。インスタンスから外部APIへの通信はCloud NATを経由して行います。
- IAPによるアクセス制御: ユーザーはHTTPSロードバランサ経由でDifyにアクセスしますが、その手前でIAPがGoogleアカウントによる認証を要求します。許可されたメンバーのみがDifyの先に進めるため、セキュアなアクセスが保証されます。SSHでのインスタンスへのアクセスもIAP経由に限定します。
Cloud RunでローカルMCPサーバーをリモートMCPサーバーとして立ち上げてDifyと連携する、といったことも行っておりますが、そのトピックは別記事で解説したいと思います。
Terraformによるインフラ構築
それでは、実際にTerraformでインフラを構築していきましょう。
主要なリソースのコードを抜粋して解説します。
0. 基本設定
はじめに、プロジェクト全体で利用する変数や設定をmain.tfで定義します。
以降のコードで登場するlocal.regionなどはここで定義されています。
locals {
project = "ご自身のプロジェクトID"
region = "asia-northeast1" // お好きなリージョンを指定してください
}
provider "google" {
project = local.project
region = local.region
}
1. VPCとファイアウォール
まずは、ネットワークの基礎となるVPCと、GCEインスタンスへの通信を制御するファイアウォールルールを定義します。
resource "google_compute_network" "main_vpc" {
name = "main-vpc"
auto_create_subnetworks = false
routing_mode = "GLOBAL"
}
resource "google_compute_subnetwork" "main_subnet" {
name = "main-subnet"
ip_cidr_range = "10.0.0.0/24"
region = local.region
network = google_compute_network.main_vpc.id
private_ip_google_access = true
}
# Cloud NAT に必要なルーター
resource "google_compute_router" "main_router" {
name = "main-router"
network = google_compute_network.main_vpc.id
region = local.region
}
# IAPからのSSHを許可
resource "google_compute_firewall" "allow-ssh-from-iap" {
name = "allow-ssh-from-iap"
network = google_compute_network.main_vpc.name
allow {
protocol = "tcp"
ports = ["22"]
}
source_ranges = [
"35.235.240.0/20" # IAP for TCP forwarding のIP範囲
]
direction = "INGRESS"
}
# LBからのヘルスチェックおよびIAPを経由したトラフィックを許可
resource "google_compute_firewall" "allow-health-check" {
name = "allow-health-check"
network = google_compute_network.main_vpc.name
allow {
protocol = "tcp"
ports = ["80"]
}
source_ranges = [
"130.211.0.0/22", # GCPヘルスチェックのIP範囲
"35.191.0.0/16"
]
direction = "INGRESS"
target_tags = ["dify"]
}
ここでは、LBとIAPからの通信に必要な最小限のファイアウォールルールのみを設定しています。GCEインスタンスにはdifyタグを付与し、このルールが適用されるようにします。
2. GCEインスタンスとCloud NAT
次に、Difyを動作させるGCEインスタンスを定義します。
resource "google_compute_instance" "dify" {
name = "dify"
machine_type = "e2-standard-2" # 2vCPU, 8GB
zone = "${local.region}-a"
boot_disk {
initialize_params {
image = "debian-cloud/debian-11"
size = 50
}
}
// 外部IPを割り当てない
network_interface {
subnetwork = google_compute_subnetwork.main_subnet.id
}
tags = ["dify"]
}
// インスタンスグループ
resource "google_compute_instance_group" "dify" {
name = "dify-group"
zone = "${local.region}-a"
instances = [google_compute_instance.dify.self_link]
named_port {
name = "http"
port = 80
}
}
// 外部へのアウトバウンド通信のためのCloud NAT
resource "google_compute_router_nat" "dify" {
name = "dify-nat"
router = google_compute_router.main_router.name
region = local.region
nat_ip_allocate_option = "AUTO_ONLY"
source_subnetwork_ip_ranges_to_nat = "ALL_SUBNETWORKS_ALL_IP_RANGES"
}
network_interfaceブロックでaccess_configブロックを記述しないことで、外部IPアドレスが作成されないようにしているのがポイントです。
また、Difyが外部のLLM APIなどを呼び出せるように、google_compute_router_natを作成してインターネットへの出口を確保しています。
3. ロードバランサとIAP
最後に、外部からのHTTPSリクエストを受け付け、IAPで認証を行うロードバランサ周りを設定します。
# (グローバルIPアドレス、SSL証明書などの定義は省略)
# バックエンドのヘルスチェック
resource "google_compute_health_check" "http_health_check" {
name = "http-health-check"
check_interval_sec = 30
timeout_sec = 5
healthy_threshold = 2
unhealthy_threshold = 3
http_health_check {
port = "80"
request_path = "/apps" # Difyのログイン後の画面パスを指定
}
}
resource "google_compute_backend_service" "dify_backend_service" {
name = "dify-backend-service"
protocol = "HTTP"
port_name = "http"
load_balancing_scheme = "EXTERNAL_MANAGED"
timeout_sec = 300 // 生成AIのレスポンス速度も考慮したタイムアウト時間を設定する
health_checks = [google_compute_health_check.http_health_check.self_link]
backend {
group = google_compute_instance_group.dify.self_link
}
// IAPを有効化
iap {
enabled = true
oauth2_client_id = var.iap_oauth2_client_id
oauth2_client_secret = var.iap_oauth2_client_secret
}
}
resource "google_compute_url_map" "dify_url_map" {
name = "dify-url-map"
default_service = google_compute_backend_service.dify_backend_service.self_link
}
resource "google_compute_target_https_proxy" "https_proxy" {
name = "https-lb-proxy"
url_map = google_compute_url_map.dify_url_map.self_link
ssl_certificates = [google_compute_managed_ssl_certificate.dify_ssl_certificate.self_link]
}
resource "google_compute_global_forwarding_rule" "https_forwarding_rule" {
name = "https-content-rule"
ip_protocol= "TCP"
port_range = "443"
target = google_compute_target_https_proxy.https_proxy.self_link
ip_address = google_compute_global_address.dify_lb_ip.address
}
google_compute_backend_serviceリソースのiapブロックでenabled = trueとし、事前にGCPコンソールで発行したOAuth2のクライアントIDとシークレットを指定するだけで、簡単にIAPを有効化できます。
認証情報はvariables.tfで定義し、実際の値は terraform.tfvars などで管理するのが安全です。
variable "iap_oauth2_client_id" {
description = "IAP OAuth2 Client ID"
type = string
}
variable "iap_oauth2_client_secret" {
description = "IAP OAuth2 Client Secret"
type = string
sensitive = true
}
構築後、Google Cloudのコンソール画面でIAPでアクセス許可するユーザーの設定を行なってください。
Difyのセットアップ
terraform applyでインフラが構築できたら、Dify本体をセットアップします。
-
IAP経由でSSH接続
gcloud compute ssh dify \ --project=${YOUR_PROJECT_ID} \ --zone=${YOUR_ZONE} \ --tunnel-through-iap -
DockerとDocker Compose、Gitをインストール
※手順は公式ドキュメント等をご参照ください。本記事では割愛します。 -
Difyの実行
GitHubからソースをクローンし、docker composeで起動します。詳細については公式ドキュメント、GitHubをご覧ください。git clone https://github.com/langgenius/dify.git cd dify/docker cp .env.example .env docker compose up -d
これで、ロードバランサのIPアドレスにブラウザでアクセスすると、Googleのログイン画面が表示され、認証を通ったユーザーだけがDifyを利用できる状態になります。
まとめ
今回はTerraformを使い、GCEインスタンスに直接アクセスさせず、IAPで保護されたロードバランサ経由でDifyを利用するセキュアな環境を構築しました。
IAPを利用することで、Difyで公開したアプリを社内のみの利用に制限することができます。
Difyでアカウントの管理などしなくて良く、社内メンバーにはURLだけを案内すれば良いのでセキュアかつ運用面でも利点があると思いますので、似たような用途で使いたい場合には是非参考にしていただけますと幸いです。
