はじめに 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: OPcache | PHPオペコードキャッシュ | コンパイル済み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 Armor | WAF | OWASP Top 10対策、地理的アクセス制限、DDoS防御 |
| Private VPC | ネットワーク分離 | 外部IP無効化、Cloud NAT経由での外部通信 |
| Wazuh | SIEM | ファイル整合性監視、脆弱性スキャン、ログ分析 |
| 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 SQL | db-custom-2-7680 (HA) | $180 | ¥27,000 |
| Filestore | BASIC_HDD 1TB | $200 | ¥30,000 |
| Cloud Load Balancer | 100GB/月 | $20 | ¥3,000 |
| Cloud CDN | 100GB/月 | $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
- Terraform実装: この設計書をコード化
- Ansible Playbook設計: WordPress細かい設定、Wazuh Agent導入
- 実環境での検証:
terraform plan→apply - 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
- infra-ai-agent リポジトリ
docs/requirements.md: 詳細な要件定義書docs/terraform-design.md: 完全なTerraform設計書
- Google Cloud Documentation
前回の記事: Google Cloud Platform上でAIエージェントによるインフラ自動運用環境を構築する
次回予告: Terraform実装編 - 設計書をコード化してGCP環境を構築する