はじめに Link to heading

前回の記事「Google Cloud Platform上でAIエージェントによるインフラ自動運用環境を構築する」では、GCP上でAIエージェントの基盤を構築しました。

今回は、実際にホスティングサービスとして運用可能なWordPressマルチテナント環境の設計に挑戦します。単なるWordPressホスティングではなく、HA構成、動的キャッシュ、WAF、セキュリティ監視まで含めた本格的なプロダクション環境を、要件定義から詳細なTerraform設計まで一気に作り上げます。

設計の背景と目標 Link to heading

やりたいこと Link to heading

  • 10サイト程度のWordPressをマルチテナントでホスティング
  • 小規模トラフィック(同時接続100程度)から始める
  • GCPのマネージドサービスを活用してHA構成を実現
  • セキュリティとコストのバランスを取る
  • 将来的な拡張性を確保

制約条件 Link to heading

  • 予算: 無料枠4万円を活用、長期的には月3,000円目標
  • トラフィック: 初期は小規模、スケーラビリティ確保
  • 学習目的: GCPネイティブサービスの理解を深めたい

Phase 1: 要件定義 Link to heading

詳細な要件定義はdocs/requirements.mdに記載していますが、ここでは主要なポイントを紹介します。

機能要件 Link to heading

1. マルチテナント構成 Link to heading

10サイトの独立したWordPress環境
├── サイト1: example1.com
├── サイト2: example2.com
...
└── サイト10: example10.com

各サイトごとに:
- 独立したデータベース
- 独立したWordPressファイルディレクトリ
- Nginx仮想ホスト

2. 高可用性(HA)アーキテクチャ Link to heading

[Cloud Load Balancer (Global)]
         ↓
[Managed Instance Group (Auto Scaling)]
    ↓           ↓
  VM1 ←───────→ VM2 (Filestore NFS共有)
    ↓
[Cloud SQL (Primary + Standby)]

ポイント:

  • Managed Instance Group による自動スケーリング・ヘルスチェック
  • Cloud SQL のリージョナルHA(Primary + Standby)
  • Filestore NFS による WordPress ファイルの共有ストレージ

3. パフォーマンス最適化 Link to heading

2層キャッシュ戦略:

[ユーザー] → [Cloud CDN (L1 Cache)] → [Nginx + PHP-FPM + OPcache (L2 Cache)] → [Cloud SQL]
役割キャッシュ対象TTL
L1: Cloud CDNグローバルエッジキャッシュ静的コンテンツ + 動的ページCache-Control準拠
L2: OPcachePHPオペコードキャッシュコンパイル済みPHPメモリ上で永続化

動的コンテンツのキャッシュ:

Cloud CDNはUSE_ORIGIN_HEADERSモードで動作し、WordPressが出力するCache-Controlヘッダーに従ってキャッシュします:

1// WordPressプラグインでの実装例
2header('Cache-Control: public, max-age=300, s-maxage=600');
  • 認証済みユーザー: Cache-Control: private, no-cache(キャッシュなし)
  • ゲストユーザー: Cache-Control: public, max-age=300(5分間キャッシュ)

キャッシュ整合性の確保:

記事更新時:
1. WordPress側でCDNパージAPIを呼び出し
2. Cloud CDN → 該当URLのキャッシュクリア
3. OPcache → 次回アクセス時に自動再コンパイル

非機能要件 Link to heading

1. セキュリティ Link to heading

多層防御アーキテクチャ:

[外部] → [Cloud Armor (WAF)] → [Cloud Load Balancer]
            ↓
      [Private VPC]
            ↓
   [VM (内部IP のみ)]
            ↓
   [Wazuh Agent (SIEM)]

セキュリティコンポーネント:

コンポーネント役割実装内容
Cloud ArmorWAFOWASP Top 10対策、地理的アクセス制限、DDoS防御
Private VPCネットワーク分離外部IP無効化、Cloud NAT経由での外部通信
WazuhSIEMファイル整合性監視、脆弱性スキャン、ログ分析
Secret Manager機密情報管理DBパスワード、WordPress管理者パスワード

最小権限の原則:

1# Web ServerサービスアカウントのIAM権限
2roles/secretmanager.secretAccessor       // DB読み取り専用
3roles/secretmanager.secretCreator        // Secret新規作成のみ
4roles/secretmanager.secretVersionAdder   // バージョン追加のみ
5roles/cloudsql.client                    // Cloud SQL接続
6roles/file.editor                        // Filestoreアクセス

❌ 削除: roles/secretmanager.admin(過剰権限)

攻撃シナリオへの耐性:

  • VM侵害時でもSecret削除・IAM設定変更は不可能
  • 横展開: 他のSecretへのアクセス不可
  • 証跡消去: Secret削除不可で痕跡が残る

2. バックアップ Link to heading

06:00 JST: DBスナップショット作成
   ↓ (5分以内)
06:05 JST: VM永続ディスクスナップショット作成

保持期間: 2日間(4世代)
リストア手順: Terraform変数で指定

整合性の確保:

  • DBとVMのスナップショット間隔を5分以内に設定
  • リストア時はペアで復元(DB 06:00 + VM 06:05)

3. 監視・アラート Link to heading

[Cloud Monitoring] → [Slack Webhook] → [LLMエージェント]
                           ↓
                    自律的なアクション

Phase 1のアラート対象:

  • ✅ HTTP接続失敗(サービス監視)
  • ✅ ヘルスチェック失敗
  • ⏱️ レスポンス速度(Phase 2以降)

SSL証明書管理 Link to heading

[Cloud DNS] ← Let's Encrypt DNS-01 Challenge
     ↓
[Load Balancer SSL証明書]
     ↓
自動更新(certbotでcron実行)

Phase 2: Terraform設計 Link to heading

詳細設計はdocs/terraform-design.md(2,200行超)に記載していますが、ここでは設計の核心部分を解説します。

モジュール構成 Link to heading

terraform/
├── environments/
│   ├── prod/
│   │   ├── main.tf          # モジュール呼び出し
│   │   ├── variables.tf     # 共通変数定義
│   │   └── terraform.tfvars # 本番環境値
│   └── dev/
│       └── terraform.tfvars # 開発環境値
├── modules/
│   ├── network/             # VPC、サブネット、NAT
│   ├── compute/             # MIG、Instance Template
│   ├── database/            # Cloud SQL
│   ├── loadbalancer/        # LB、Cloud CDN、Cloud Armor
│   ├── filestore/           # NFS共有ストレージ
│   ├── monitoring/          # アラート、ログ
│   └── iam/                 # サービスアカウント、権限
└── scripts/
    └── startup_script.sh    # VM初期化スクリプト

設計原則:

  • 再利用性: 環境(prod/dev)間で同じモジュールを使用
  • 変数駆動: ドメインリスト1つで全リソース生成
  • 依存関係の明確化: depends_onで順序を保証

重要な技術的決定 Link to heading

1. Service Networking(Cloud SQL Private IP) Link to heading

課題: Cloud SQLをPrivate IPで接続するには、VPCとのピアリングが必要。

解決策:

 1# modules/network/service_networking.tf
 2
 3# Service Networking API有効化
 4resource "google_project_service" "servicenetworking" {
 5  service = "servicenetworking.googleapis.com"
 6}
 7
 8# Private IP範囲を予約(/16)
 9resource "google_compute_global_address" "private_ip_address" {
10  name          = "private-ip-address"
11  purpose       = "VPC_PEERING"
12  address_type  = "INTERNAL"
13  prefix_length = 16
14  network       = google_compute_network.vpc.id
15}
16
17# Service Networking接続を作成
18resource "google_service_networking_connection" "private_vpc_connection" {
19  network                 = google_compute_network.vpc.id
20  service                 = "servicenetworking.googleapis.com"
21  reserved_peering_ranges = [google_compute_global_address.private_ip_address.name]
22  
23  depends_on = [google_project_service.servicenetworking]
24}

依存関係の解決:

1# environments/prod/main.tf
2
3module "database" {
4  source = "../../modules/database"
5  
6  # 重要: Service Networking接続が完了してからCloud SQL作成
7  depends_on = [module.network]
8}

NGパターン: depends_on = [module.network.service_networking_connection]
(Terraformはモジュール出力へのdepends_onを許可しない)

OKパターン: depends_on = [module.network]
(モジュール全体への依存)

2. Filestore(共有ストレージ) Link to heading

課題: MIGでVMが複数台起動する際、WordPress ファイルを共有する必要がある。

検討した選択肢:

  • 永続ディスク: Regional MIGではstateful設定不可
  • Filestore: NFSマウントで複数VMから同時アクセス可能

実装:

 1# modules/filestore/main.tf
 2
 3# Filestore用IP範囲を予約(/29)
 4resource "google_compute_global_address" "filestore_ip" {
 5  name          = "filestore-ip-range"
 6  purpose       = "VPC_PEERING"
 7  address_type  = "INTERNAL"
 8  prefix_length = 29
 9  network       = var.network_id
10}
11
12resource "google_filestore_instance" "wordpress" {
13  name     = "wordpress-filestore"
14  tier     = "BASIC_HDD"  # コスト最適化
15  
16  file_shares {
17    name        = "wordpress"
18    capacity_gb = 1024
19  }
20  
21  networks {
22    network           = var.network_id  # フルリソース名が必須
23    modes             = ["MODE_IPV4"]
24    connect_mode      = "DIRECT_PEERING"
25    reserved_ip_range = google_compute_global_address.filestore_ip.name
26  }
27  
28  depends_on = [google_project_service.filestore]
29}

起動スクリプトでのマウント:

 1# scripts/startup_script.sh
 2
 3# Filestoreマウント
 4FILESTORE_IP="${filestore_ip}"
 5FILESTORE_SHARE="wordpress"
 6
 7apt-get install -y nfs-common
 8mkdir -p /mnt/wordpress
 9mount -t nfs $FILESTORE_IP:/$FILESTORE_SHARE /mnt/wordpress
10
11# fstabに追加(再起動後も自動マウント)
12echo "$FILESTORE_IP:/$FILESTORE_SHARE /mnt/wordpress nfs defaults 0 0" >> /etc/fstab

3. 動的10サイト生成 Link to heading

課題: 10サイト分のNginx設定、DBテーブル、ディレクトリを手動で作るのは非効率。

解決策: Terraformのcountと起動スクリプトのループで自動生成。

Terraform側(変数駆動):

1# environments/prod/terraform.tfvars
2
3domains = [
4  "example1.com",
5  "example2.com",
6  // ... 10サイト分
7]
 1# modules/database/databases.tf
 2
 3locals {
 4  site_count = length(var.domains)  # ドメイン数から自動算出
 5}
 6
 7# 各サイト用のDB作成
 8resource "google_sql_database" "wordpress_db" {
 9  count    = local.site_count
10  name     = "wordpress_site${count.index + 1}"
11  instance = google_sql_database_instance.main.name
12}
13
14# 各サイト用のDBユーザー作成
15resource "google_sql_user" "wordpress_user" {
16  count    = local.site_count
17  name     = "wp_user_site${count.index + 1}"
18  instance = google_sql_database_instance.main.name
19  password = random_password.db_password[count.index].result
20}

起動スクリプト側(動的生成):

 1# scripts/startup_script.sh
 2
 3# メタデータからドメインリストを取得
 4DOMAINS_JSON=$(curl -H "Metadata-Flavor: Google" \
 5  http://metadata.google.internal/computeMetadata/v1/instance/attributes/domains)
 6
 7# 各サイト用のディレクトリ作成
 8for i in {1..10}; do
 9  mkdir -p /mnt/wordpress/site${i}
10  chown -R www-data:www-data /mnt/wordpress/site${i}
11  
12  # Nginx仮想ホスト生成
13  DOMAIN=$(echo $DOMAINS_JSON | jq -r ".[$((i-1))]")
14  cat > /etc/nginx/sites-available/site${i}.conf <<EOF
15server {
16    listen 80;
17    server_name $DOMAIN;
18    root /mnt/wordpress/site${i};
19    
20    location / {
21        try_files \$uri \$uri/ /index.php?\$args;
22    }
23    
24    location ~ \.php$ {
25        fastcgi_pass unix:/var/run/php/php8.2-fpm.sock;
26        fastcgi_param SCRIPT_FILENAME \$document_root\$fastcgi_script_name;
27        include fastcgi_params;
28    }
29}
30EOF
31  ln -s /etc/nginx/sites-available/site${i}.conf /etc/nginx/sites-enabled/
32done
33
34nginx -t && systemctl reload nginx

WordPress自動セットアップスクリプト:

 1# /usr/local/bin/setup-wordpress-site.sh
 2
 3#!/bin/bash
 4SITE_NUM=$1
 5DB_PASSWORD=$(gcloud secrets versions access latest --secret="prod-wordpress-db-password-${SITE_NUM}")
 6ADMIN_PASSWORD=$(openssl rand -base64 24)
 7
 8cd /mnt/wordpress/site${SITE_NUM}
 9wp core download --allow-root
10wp config create \
11  --dbname="wordpress_site${SITE_NUM}" \
12  --dbuser="wp_user_site${SITE_NUM}" \
13  --dbpass="$DB_PASSWORD" \
14  --dbhost="10.x.x.x" \
15  --allow-root
16
17wp core install \
18  --url="https://$(echo $DOMAINS_JSON | jq -r ".[${SITE_NUM}-1]")" \
19  --title="Site ${SITE_NUM}" \
20  --admin_user="admin" \
21  --admin_password="$ADMIN_PASSWORD" \
22  --admin_email="admin@example.com" \
23  --allow-root
24
25# 管理者パスワードをSecret Managerに保存
26gcloud secrets create prod-wordpress-admin-password-${SITE_NUM} \
27  --data-file=<(echo -n "$ADMIN_PASSWORD") || \
28gcloud secrets versions add prod-wordpress-admin-password-${SITE_NUM} \
29  --data-file=<(echo -n "$ADMIN_PASSWORD")
30
31echo "✅ Site ${SITE_NUM} セットアップ完了"
32echo "管理者パスワードは以下で取得:"
33echo "gcloud secrets versions access latest --secret=prod-wordpress-admin-password-${SITE_NUM}"

4. Cloud SQL HA設定の環境別パラメータ化 Link to heading

課題: 本番環境はHA(REGIONAL)、開発環境は単一VM(ZONAL)でコスト削減したい。

解決策:

 1# modules/database/variables.tf
 2
 3variable "availability_type" {
 4  description = "Cloud SQL HA設定"
 5  type        = string
 6  
 7  validation {
 8    condition     = contains(["REGIONAL", "ZONAL"], var.availability_type)
 9    error_message = "availability_type must be REGIONAL or ZONAL."
10  }
11}
 1# modules/database/main.tf
 2
 3resource "google_sql_database_instance" "main" {
 4  name             = "${var.environment}-mysql"
 5  database_version = "MYSQL_8_0"
 6  region           = var.region
 7  
 8  settings {
 9    tier              = "db-custom-2-7680"  # HA対応スペック
10    availability_type = var.availability_type
11    
12    backup_configuration {
13      enabled            = true
14      start_time         = "03:00"
15      binary_log_enabled = var.availability_type == "REGIONAL"  # HAの場合のみ有効
16    }
17    
18    ip_configuration {
19      ipv4_enabled    = false
20      private_network = var.network_id
21    }
22  }
23  
24  depends_on = [module.network]
25}
1# environments/prod/terraform.tfvars
2db_availability_type = "REGIONAL"  # 本番: HA有効
3
4# environments/dev/terraform.tfvars
5db_availability_type = "ZONAL"     # 開発: 単一インスタンス

最小権限の原則(Secret Manager) Link to heading

当初の設計(問題あり):

1# ❌ 過剰権限
2resource "google_project_iam_member" "web_secret_admin" {
3  role = "roles/secretmanager.admin"  # 何でもできてしまう
4}

問題点:

  • Secret削除可能
  • IAM設定変更可能
  • 全Secret一覧取得可能

改善後(最小権限):

 1# modules/iam/main.tf
 2
 3# ✅ 読み取り専用
 4resource "google_project_iam_member" "web_secret_accessor" {
 5  role = "roles/secretmanager.secretAccessor"
 6}
 7
 8# ✅ Secret新規作成のみ
 9resource "google_project_iam_member" "web_secret_creator" {
10  role = "roles/secretmanager.secretCreator"
11}
12
13# ✅ 既存Secretへのバージョン追加のみ
14resource "google_project_iam_member" "web_secret_version_adder" {
15  role = "roles/secretmanager.secretVersionAdder"
16}

セキュリティ効果:

操作admin分離後
Secret読み取り
Secret作成
バージョン追加
Secret削除⚠️ 可能❌ 不可
IAM設定変更⚠️ 可能❌ 不可
全Secret一覧⚠️ 可能❌ 不可

攻撃シナリオへの耐性:

  • VM侵害時でもSecret削除・IAM変更不可
  • 横展開: 他のSecretへのアクセス不可
  • 証跡消去: Secret削除不可で痕跡が残る

設計の進化過程(修正履歴) Link to heading

この設計書は6回のレビューを経て完成しました:

v1.1: Cloud SQL Private IP対応 Link to heading

  • google_service_networking_connection追加
  • Filestore NFSによる共有ストレージ実装
  • 10サイト動的生成

v1.2-v1.4: 依存関係の修正 Link to heading

  • depends_on = [module.network]の正しい使い方
  • Filestore のnetwork_idパラメータ修正
  • API有効化の依存関係追加

v1.5: Cloud SQL HA設定とパスワード管理 Link to heading

  • availability_typeの環境別パラメータ化
  • WordPress管理者パスワードのSecret Manager保存

v1.6: 最小権限の原則(Secret Manager) Link to heading

  • 権限を3ロールに分解
  • セキュリティリスク評価: High → Low

コスト試算 Link to heading

Phase 1(初期構成) Link to heading

リソーススペック月額(USD)月額(円)
Compute Engine (MIG)e2-small × 2台$25¥3,750
Cloud SQLdb-custom-2-7680 (HA)$180¥27,000
FilestoreBASIC_HDD 1TB$200¥30,000
Cloud Load Balancer100GB/月$20¥3,000
Cloud CDN100GB/月$10¥1,500
Cloud Armorポリシー1つ$5¥750
合計$440¥66,000

Phase 2(コスト最適化後) Link to heading

Cloud SQL: db-custom-2-7680 (HA)
   ↓
Cloud SQL: db-f1-micro (Single Zone)
削減: $160/月(¥24,000)

Phase 2合計: $280/月(¥42,000)

Phase 3(長期目標) Link to heading

Filestore: BASIC_HDD 1TB
   ↓
VM内蔵ディスク + rsync同期
削減: $200/月(¥30,000)

Phase 3合計: $80/月(¥12,000)

最終目標(月3,000円)への道筋:

  • 無料枠4万円を使い切るまでPhase 1で学習
  • GCPサービスの理解が深まったらPhase 2へ移行
  • 運用が安定したらPhase 3で自前VM構成へ

技術的に学んだこと Link to heading

1. Terraformの依存関係は奥が深い Link to heading

NG例:

1depends_on = [var.some_variable]           # 変数は不可
2depends_on = [module.network.output_name]   # 出力値は不可

OK例:

1depends_on = [module.network]              # モジュール全体
2depends_on = [google_project_service.api]  # リソース

2. GCPのService Networkingは必須知識 Link to heading

Cloud SQLやFilestoreをPrivate IPで使うには、VPC Peeringの理解が不可欠:

VPC ←(Service Networking)→ Google Service Producer Network
  • IP範囲を予約(google_compute_global_address
  • Service Networking接続を確立(google_service_networking_connection
  • API有効化を忘れずに(google_project_service

3. 最小権限の原則は細かく設定する Link to heading

「とりあえず admin ロール」は危険。GCPの細かいロール(secretAccessor, secretCreator, secretVersionAdder)を組み合わせることで、真の最小権限を実現できる。

4. マルチテナント設計は変数駆動にする Link to heading

ドメインリスト1つで全リソースを生成する設計にすれば:

  • サイト追加はdomainsに1行追加するだけ
  • DB、Nginx設定、ディレクトリが自動生成
  • 手動設定ミスがゼロに

今後の展開 Link to heading

次のステップ Link to heading

  1. Terraform実装: この設計書をコード化
  2. Ansible Playbook設計: WordPress細かい設定、Wazuh Agent導入
  3. 実環境での検証: terraform planapply
  4. AIエージェント統合: 障害検知 → Slack通知 → LLMが自動対応

拡張構想 Link to heading

  • CI/CDパイプライン: GitHubからの自動デプロイ
  • Blue-Green Deployment: ゼロダウンタイム更新
  • マルチリージョン: グローバル展開
  • データ分析基盤: BigQueryとの連携

まとめ Link to heading

今回は、WordPress マルチテナント環境の要件定義からTerraform設計までを一気に駆け抜けました。

得られた成果:

  • ✅ 本格的なプロダクション環境の設計書(2,200行超)
  • ✅ HA、セキュリティ、コスト最適化を両立
  • ✅ 6回のレビューを経た高品質な設計
  • ✅ 最小権限の原則を徹底

技術的ハイライト:

  • Service Networkingによる Private IP 設計
  • Filestore NFSでのマルチVM共有ストレージ
  • 変数駆動の動的10サイト生成
  • Secret Manager権限の3ロール分離

次回は、この設計書をもとに実際にTerraformコードを実装し、terraform applyで環境を構築します。お楽しみに!

参考資料 Link to heading


前回の記事: Google Cloud Platform上でAIエージェントによるインフラ自動運用環境を構築する

次回予告: Terraform実装編 - 設計書をコード化してGCP環境を構築する