はじめに 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
| モジュール | ファイル数 | 主な機能 |
|---|---|---|
| Network | 5 | VPC, サブネット, NAT, Firewall, Service Networking |
| IAM | 3 | サービスアカウント, 最小権限設定(3ロール分離) |
| Filestore | 3 | NFS共有ストレージ(1TB), IP予約 |
| Database | 4 | Cloud SQL HA, 10サイトDB自動生成, Secret Manager |
| Compute | 6 | Instance Template, MIG, Auto Scaling, 起動スクリプト |
| Load Balancer | 6 | Backend, Frontend, SSL, Cloud Armor, CDN |
| Monitoring | 3 | HTTP 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.
作成されるリソース内訳:
| カテゴリ | リソース数 | 主な内容 |
|---|---|---|
| Network | 11 | VPC, サブネット×2, NAT, Firewall×4, Service Networking |
| IAM | 11 | サービスアカウント×2, IAM権限×9 |
| Filestore | 3 | NFS×1, IP予約×2 |
| Database | 33 | Cloud SQL×1, DB×10, User×10, Secret×20, Password×10 |
| Compute | 14 | Template×1, MIG×1, Autoscaler×1, Health Check×1, etc |
| Load Balancer | 19 | Backend, URL Map×2, SSL×1, Cloud Armor, Forwarding×2, etc |
| Monitoring | 3 | Alert×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
| 環境 | サイト数 | リソース数 | エラー |
|---|---|---|---|
| prod | 10 | 94 | 0 |
| dev | 3 | 59 | 0 |
🎓 学んだこと 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
- 設計書を丁寧に作る - 実装前の設計が8割
- terraform planで検証 - validateだけでは不十分
- 段階的な実装 - モジュール単位で確認
- エラーを恐れない - エラーから学ぶことが多い
- 最小権限の徹底 - セキュリティは最初から
ハマりポイント Link to heading
- Service Networkingの依存関係 - API有効化を忘れずに
- Secret Manager構文変更 -
automatic = true→auto {} - MySQL vs PostgreSQL -
binary_log_enabledvspoint_in_time_recovery_enabled .terraform/のコミット - .gitignoreを最初に設定- 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
- infra-ai-agent リポジトリ
- Terraform実装:
terraform/ - 設計書:
docs/terraform-design.md - 要件定義:
docs/requirements.md
- Terraform実装:
- 前回の記事: 設計編
- Terraform公式ドキュメント
- Google Cloud Provider
前回の記事: GCP上でWordPressマルチテナント環境を設計する
次回予告: Terraform Apply編 - 実際のデプロイと運用開始