はじめに Link to heading

前回の記事では、WordPressマルチテナント環境の要件定義とTerraform設計を完成させました。

今回は、設計書をもとに実際のTerraformコードを実装し、terraform planで検証するまでの全プロセスを解説します。

成果物:

  • ✅ 7モジュール、46ファイル、2,525行のTerraformコード
  • ✅ terraform plan検証済み(prod: 94リソース, dev: 59リソース)
  • ✅ エラー0件で実装完了

🚀 実装フロー Link to heading

全体の流れ Link to heading

1. Terraform環境準備
   ├─ Terraform 1.13.5インストール
   ├─ GCP認証設定
   └─ 必要なAPI有効化

2. モジュール実装(7モジュール)
   ├─ Network(VPC/NAT/Firewall)
   ├─ IAM(最小権限)
   ├─ Filestore(NFS共有ストレージ)
   ├─ Database(Cloud SQL + 10サイト自動生成)
   ├─ Compute(MIG + Auto Scaling)
   ├─ Load Balancer(LB/CDN/WAF/SSL)
   └─ Monitoring(アラート/ログ)

3. 環境別設定
   ├─ prod環境(10サイト、HA構成)
   └─ dev環境(3サイト、単一構成)

4. 検証
   ├─ terraform fmt(フォーマット)
   ├─ terraform validate(構文チェック)
   └─ terraform plan(実行計画)

5. コミット&プッシュ

1. Terraform環境準備 Link to heading

Terraform 1.13.5インストール Link to heading

課題: HomebrewのTerraformは1.5.7(MPL 2.0)で止まっている

選択肢:

  • Terraform 1.5.7(完全オープンソース)
  • Terraform 1.13.5(BUSL - 商用制限あり)
  • OpenTofu(Terraformフォーク、Apache 2.0)

判断: 自社インフラ管理なのでBUSL制限に該当しない → 最新版1.13.5を採用

 1# Homebrewのterraformをアンインストール
 2brew uninstall terraform
 3
 4# 最新版バイナリをダウンロード
 5cd /tmp
 6wget https://releases.hashicorp.com/terraform/1.13.5/terraform_1.13.5_linux_amd64.zip
 7unzip terraform_1.13.5_linux_amd64.zip
 8sudo mv terraform /usr/local/bin/
 9
10# バージョン確認
11terraform version
12# Terraform v1.13.5

ライセンス確認:

1cat LICENSE.txt | head -20
2# Business Source License (BUSL)
3# 競合サービス提供時のみ制限
4# 自社インフラ管理には影響なし

GCP認証とAPI有効化 Link to heading

 1# Application Default Credentials設定
 2gcloud auth application-default login
 3
 4# 必要なAPI一括有効化
 5gcloud services enable \
 6  compute.googleapis.com \
 7  servicenetworking.googleapis.com \
 8  sqladmin.googleapis.com \
 9  file.googleapis.com \
10  secretmanager.googleapis.com \
11  cloudresourcemanager.googleapis.com \
12  --project=infra-ai-agent
13
14# 成功: Operation "operations/..." finished successfully.

2. モジュール実装 Link to heading

実装したモジュール一覧 Link to heading

モジュールファイル数主な機能
Network5VPC, サブネット, NAT, Firewall, Service Networking
IAM3サービスアカウント, 最小権限設定(3ロール分離)
Filestore3NFS共有ストレージ(1TB), IP予約
Database4Cloud SQL HA, 10サイトDB自動生成, Secret Manager
Compute6Instance Template, MIG, Auto Scaling, 起動スクリプト
Load Balancer6Backend, Frontend, SSL, Cloud Armor, CDN
Monitoring3HTTP Alert, Health Check Alert, Log Sink

Network モジュールのポイント Link to heading

Service Networking(Cloud SQL Private IP):

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

学んだこと:

  • Cloud SQLをPrivate IPで使うには、Service Networking必須
  • API有効化 → IP予約 → Peering接続の順序が重要
  • depends_onでリソース作成順序を制御

IAM モジュール - 最小権限の原則 Link to heading

Secret Manager権限を3ロールに分離:

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

セキュリティ効果:

操作admin(NG)3ロール分離(OK)
Secret読み取り
Secret作成
バージョン追加
Secret削除⚠️ 可能❌ 不可
IAM設定変更⚠️ 可能❌ 不可

Database モジュール - ドメインリスト駆動 Link to heading

10サイト分のDB/ユーザーを自動生成:

 1# modules/database/databases.tf
 2
 3# サイト数 = ドメイン数
 4locals {
 5  site_count = length(var.domains)
 6}
 7
 8# DB自動生成
 9resource "google_sql_database" "wordpress_sites" {
10  count     = local.site_count
11  name      = "wordpress_site_${count.index + 1}"
12  instance  = google_sql_database_instance.wordpress.name
13  charset   = "utf8mb4"
14  collation = "utf8mb4_unicode_ci"
15}
16
17# DBユーザー自動生成
18resource "google_sql_user" "wordpress_users" {
19  count    = local.site_count
20  name     = "wp_user_${count.index + 1}"
21  instance = google_sql_database_instance.wordpress.name
22  password = random_password.db_passwords[count.index].result
23}
24
25# パスワードをSecret Managerに保存
26resource "google_secret_manager_secret" "db_passwords" {
27  count     = local.site_count
28  secret_id = "${var.env}-wordpress-db-password-${count.index + 1}"
29  
30  replication {
31    auto {}
32  }
33}

メリット:

  • ドメインリスト追加 → 自動的にDB/Secret生成
  • 手動設定ミスがゼロ
  • スケーラブル(10サイトでも100サイトでも同じコード)

Compute モジュール - 起動スクリプト Link to heading

438行の起動スクリプトで実現:

 1# terraform/scripts/startup_script.sh
 2
 3#!/bin/bash
 4set -e
 5
 6# Filestore NFSマウント
 7mount -t nfs ${NFS_IP}:${NFS_PATH} /var/www/wordpress
 8
 9# ドメインリストから動的にNginx設定生成
10DOMAINS_JSON='${domains_json}'
11DOMAIN_COUNT=$(echo "$DOMAINS_JSON" | jq '. | length')
12
13for i in $(seq 1 $DOMAIN_COUNT); do
14  DOMAIN=$(echo "$DOMAINS_JSON" | jq -r ".[$((i-1))]")
15  
16  # Nginx仮想ホスト生成
17  cat > /etc/nginx/sites-available/site${i} << EOF
18server {
19    listen 80;
20    server_name ${DOMAIN};
21    root /var/www/wordpress/site${i};
22    
23    location ~ \.php$ {
24        fastcgi_pass unix:/var/run/php/php8.2-fpm.sock;
25        # ...
26    }
27}
28EOF
29  ln -sf /etc/nginx/sites-available/site${i} /etc/nginx/sites-enabled/
30done
31
32# WordPress自動セットアップスクリプト生成
33cat > /usr/local/bin/setup-wordpress-site.sh << 'SCRIPT'
34#!/bin/bash
35SITE_NUM=$1
36DB_PASS=$(gcloud secrets versions access latest \
37  --secret="${ENV}-wordpress-db-password-${SITE_NUM}")
38
39# WordPress インストール
40wp core download
41wp config create --dbname="wordpress_site_${SITE_NUM}" --dbpass="$DB_PASS"
42wp core install --url="https://${DOMAIN}" --admin_password="$(openssl rand -base64 32)"
43
44# 管理者パスワードをSecret Managerに保存
45gcloud secrets create "${ENV}-wordpress-admin-password-${SITE_NUM}" || \
46gcloud secrets versions add "${ENV}-wordpress-admin-password-${SITE_NUM}"
47SCRIPT

3. 検証フェーズ Link to heading

terraform fmt(フォーマット) Link to heading

1cd terraform
2terraform fmt -recursive
3
4# 22ファイル自動整形
5environments/dev/main.tf
6environments/prod/main.tf
7modules/compute/autoscaling.tf
8modules/database/databases.tf
9# ...

terraform validate(構文チェック) Link to heading

 1# prod環境
 2cd environments/prod
 3terraform init
 4terraform validate
 5# ✅ Success! The configuration is valid.
 6
 7# dev環境
 8cd ../dev
 9terraform init
10terraform validate
11# ✅ Success! The configuration is valid.

terraform plan(実行計画) Link to heading

prod環境(10サイト構成) Link to heading

1cd environments/prod
2terraform plan
3
4# Plan: 94 to add, 0 to change, 0 to destroy.

作成されるリソース内訳:

カテゴリリソース数主な内容
Network11VPC, サブネット×2, NAT, Firewall×4, Service Networking
IAM11サービスアカウント×2, IAM権限×9
Filestore3NFS×1, IP予約×2
Database33Cloud SQL×1, DB×10, User×10, Secret×20, Password×10
Compute14Template×1, MIG×1, Autoscaler×1, Health Check×1, etc
Load Balancer19Backend, URL Map×2, SSL×1, Cloud Armor, Forwarding×2, etc
Monitoring3Alert×2, Log Sink×1
合計94

dev環境(3サイト構成) Link to heading

1cd ../dev
2terraform plan
3
4# Plan: 59 to add, 0 to change, 0 to destroy.

差分:

  • prod: 10サイト → DB×10 + Secret×20
  • dev: 3サイト → DB×3 + Secret×6
  • リソース差: 35個(主にサイト数の違い)

4. 遭遇した問題と解決 Link to heading

問題1: Secret Manager構文エラー Link to heading

エラー内容:

Error: Unsupported argument
  on modules/database/databases.tf line 36:
  36:     automatic = true

An argument named "automatic" is not expected here.

原因: Secret Managerのreplicationブロック構文が変更された

修正:

1# ❌ 古い構文
2replication {
3  automatic = true
4}
5
6# ✅ 新しい構文
7replication {
8  auto {}
9}

問題2: Cloud SQL PITR設定エラー Link to heading

エラー内容:

Error: point_in_time_recovery_enabled is only available for 
[POSTGRES SQLSERVER]. You may want to consider using 
binary_log_enabled instead.

原因: MySQLではpoint_in_time_recovery_enabledは使えない

修正:

1# ❌ PostgreSQL/SQL Server用
2backup_configuration {
3  point_in_time_recovery_enabled = true
4}
5
6# ✅ MySQL用
7backup_configuration {
8  binary_log_enabled = var.availability_type == "REGIONAL"
9}

学んだこと: バイナリログはHA構成時のみ有効化すべき


5. 実装の工夫 Link to heading

1. 変数駆動の設計 Link to heading

ドメインリスト1つで全て決まる:

1# terraform.tfvars
2domains = [
3  "example1.com",
4  "example2.com",
5  # ... 10個
6]

自動生成されるもの:

  • データベース×10
  • DBユーザー×10
  • Secret Manager Secret×10(DBパスワード)
  • Secret Manager Secret×10(WordPress管理者パスワード)
  • Nginx仮想ホスト×10
  • URL Map Host Rule×10

2. 環境別パラメータ化 Link to heading

prod環境:

1db_availability_type = "REGIONAL"  # HA有効
2db_tier              = "db-custom-2-7680"  # 2 vCPU
3machine_type         = "e2-small"
4min_replicas         = 2

dev環境:

1db_availability_type = "ZONAL"  # 単一インスタンス
2db_tier              = "db-custom-1-3840"  # 1 vCPU
3machine_type         = "e2-micro"
4min_replicas         = 1

3. .gitignore設定 Link to heading

機密情報を確実に除外:

# Terraform
**/.terraform/
**/.terraform.lock.hcl
**/terraform.tfstate*
*.tfvars
!*.tfvars.example

📊 最終成果物 Link to heading

ファイル構成 Link to heading

terraform/
├── README.md                     # セットアップガイド
├── VALIDATION.md                 # 検証手順
├── environments/
│   ├── prod/                     # 本番環境(10サイト)
│   │   ├── provider.tf
│   │   ├── main.tf
│   │   ├── variables.tf
│   │   ├── outputs.tf
│   │   └── terraform.tfvars.example
│   └── dev/                      # 開発環境(3サイト)
│       └── (同上)
├── modules/
│   ├── network/                  # 5ファイル
│   ├── iam/                      # 3ファイル
│   ├── filestore/                # 3ファイル
│   ├── database/                 # 4ファイル
│   ├── compute/                  # 6ファイル
│   ├── loadbalancer/             # 6ファイル
│   └── monitoring/               # 3ファイル
└── scripts/
    └── startup_script.sh         # 438行

統計:

  • 総ファイル数: 46ファイル
  • 総行数: 2,525行
  • モジュール数: 7モジュール
  • 環境数: 2環境(prod/dev)

terraform plan結果 Link to heading

環境サイト数リソース数エラー
prod10940
dev3590

🎓 学んだこと Link to heading

1. Terraformのバージョン選択 Link to heading

  • 1.5.7: MPL 2.0(完全オープンソース)
  • 1.13.5: BUSL(商用制限あり、自社利用はOK)
  • OpenTofu: Apache 2.0(完全オープンソース、互換性あり)

判断基準:

  • 自社インフラ管理 → BUSLでも問題なし
  • 競合サービス提供 → OpenTofuを検討

2. Service Networkingの重要性 Link to heading

Cloud SQLやFilestoreをPrivate IPで使うには:

1. API有効化(servicenetworking.googleapis.com)
2. IP範囲予約(google_compute_global_address)
3. VPC Peering(google_service_networking_connection)

この順序を守らないとエラーになる。

3. 最小権限の原則 Link to heading

roles/secretmanager.adminのような強力なロールは避ける:

❌ admin(何でもできる)
✅ accessor + creator + versionAdder(必要最小限)

セキュリティ効果:

  • VM侵害時でもSecret削除不可
  • IAM設定変更不可
  • 監査証跡が残る

4. 変数駆動の威力 Link to heading

length(var.domains)でサイト数を決定する設計:

1locals {
2  site_count = length(var.domains)
3}
4
5resource "google_sql_database" "wordpress_sites" {
6  count = local.site_count
7  # ...
8}

メリット:

  • ドメイン追加 = terraform applyだけ
  • 手動設定ミスゼロ
  • コードの再利用性が高い

5. terraform planの重要性 Link to heading

ステップ検証内容GCP接続
terraform fmtコードスタイル不要
terraform validate構文チェックのみ不要
terraform plan実際のリソース作成計画必須

terraform planで初めて:

  • 実際のGCP APIとの整合性を確認
  • リソース名の衝突を検出
  • 依存関係の問題を発見

🚀 次のステップ Link to heading

Phase 1: デプロイ準備 Link to heading

1# terraform.tfvarsに実際のドメインを設定
2vi environments/prod/terraform.tfvars
3
4# 実行計画の保存
5terraform plan -out=tfplan
6
7# 実際のデプロイ(慎重に!)
8terraform apply tfplan

Phase 2: Ansible統合 Link to heading

  • WordPress細かい設定
  • Wazuh Manager構築
  • SSL証明書の詳細設定
  • バックアップスクリプト

Phase 3: AIエージェント統合 Link to heading

  • Slack通知連携
  • LLM自動対応
  • 異常検知 → 自動修復

💡 実装のポイント Link to heading

成功の秘訣 Link to heading

  1. 設計書を丁寧に作る - 実装前の設計が8割
  2. terraform planで検証 - validateだけでは不十分
  3. 段階的な実装 - モジュール単位で確認
  4. エラーを恐れない - エラーから学ぶことが多い
  5. 最小権限の徹底 - セキュリティは最初から

ハマりポイント Link to heading

  1. Service Networkingの依存関係 - API有効化を忘れずに
  2. Secret Manager構文変更 - automatic = trueauto {}
  3. MySQL vs PostgreSQL - binary_log_enabled vs point_in_time_recovery_enabled
  4. .terraform/のコミット - .gitignoreを最初に設定
  5. terraform.tfvarsの管理 - 機密情報を絶対にコミットしない

まとめ Link to heading

今回は、設計書からTerraform実装、terraform plan検証までを完走しました。

成果:

  • ✅ 7モジュール、2,525行のコード実装
  • ✅ terraform plan成功(prod: 94, dev: 59リソース)
  • ✅ エラー0件
  • ✅ コミット&プッシュ完了

学び:

  • Service Networkingの重要性
  • 最小権限の原則(Secret Manager 3ロール分離)
  • terraform planの必須性
  • ドメインリスト駆動の設計

次回予告:

  • terraform apply編 - 実際にGCPリソースをデプロイし、WordPressを動かす
  • コスト監視の実装
  • 実運用での知見共有

参考リンク Link to heading


前回の記事: GCP上でWordPressマルチテナント環境を設計する

次回予告: Terraform Apply編 - 実際のデプロイと運用開始