Appearance
Terraform 設計と運用
infra/terraform/ で GCP リソースを HCL で管理する。gcloud の手打ちで作らず、 全部 IaC 化することで「いつ・誰が・何を・なぜ作ったか」が PR レビュー可能になる。
採用したパターン
| 判断 | 理由 |
|---|---|
| Terraform (Pulumi / gcloud script を捨て) | GCP の公式 provider が成熟、コミュニティ大 |
| 同 GCP project に staging + prod 同居 | IaM 設定の手間半分、課金も同じ、規模的に許容 |
| state は GCS backend、env 別 prefix | 1 バケットで両 env、reconfigure で切替 |
| Secret slot だけ TF、値は外部投入 | state に機密値を乗せない |
| Cloud Run image は CI が更新 | TF と CI が image を奪い合わない (lifecycle.ignore_changes) |
| per-resource IAM | 同 project 同居でも env 越境を遮断 |
ファイル構成
| ファイル | 内容 |
|---|---|
main.tf | provider / GCS backend / API 有効化 / 共通 labels |
variables.tf | project_id, region, env (staging/prod), github_repo, auth_base_url, db_tier 等 |
cloud_sql.tf | Cloud SQL (PostgreSQL 17, edition=ENTERPRISE) + DB + user + 乱数 password |
cloud_run.tf | Cloud Run v2 (Hono API) + Unix socket mount + secret env |
artifact_registry.tf | Docker image 置き場 (共有) |
secrets.tf | Secret Manager slot + placeholder version + DATABASE_URL 組み立て |
iam.tf | SA × 2 + WIF Pool/Provider + per-resource IAM bindings |
outputs.tf | CI / Netlify が使う値の出力 |
envs/staging.tfvars | staging 用の値 |
envs/prod.tfvars | prod 用の値 (project_id は staging と同じ) |
初期セットアップ
既に staging で実行済み
このセクションは新規 env を立てるとき or 別 project に移すときの手順。
1. State 用 GCS バケットを手動作成
sh
PROJECT_ID=<shared-project-id>
gcloud config set project $PROJECT_ID
gcloud storage buckets create gs://${PROJECT_ID}-tf-state \
--location=asia-northeast1 \
--uniform-bucket-level-access
gcloud storage buckets update gs://${PROJECT_ID}-tf-state \
--update-labels=app=liga,managed-by=terraform,component=tfstate
gsutil versioning set on gs://${PROJECT_ID}-tf-state2. terraform init
sh
cd infra/terraform
terraform init \
-backend-config="bucket=${PROJECT_ID}-tf-state" \
-backend-config="prefix=staging"3. apply
sh
terraform plan -var-file=envs/staging.tfvars
terraform apply -var-file=envs/staging.tfvarsCloud SQL の作成に 8〜12 分かかる。
4. Secret 値投入
sh
ENV=staging
printf "%s" "$(openssl rand -base64 32)" | gcloud secrets versions add BETTER_AUTH_SECRET_${ENV} --data-file=-
printf "%s" "$(openssl rand -hex 16)" | gcloud secrets versions add CRON_SECRET_${ENV} --data-file=-
printf "%s" "sk_test_..." | gcloud secrets versions add STRIPE_SECRET_KEY_${ENV} --data-file=-
# ... 他の secret も同様DATABASE_URL は TF が自動投入済み。
5. terraform output で GHA / Netlify 設定
sh
terraform output -jsonを見て GitHub Secrets / Netlify env vars に値を投入。
6. DB マイグレ + seed
Cloud SQL Auth Proxy 経由で:
sh
INSTANCE=$(terraform output -raw cloud_sql_connection_name)
cloud-sql-proxy $INSTANCE &
DB_URL_RAW=$(gcloud secrets versions access latest --secret=DATABASE_URL_${ENV})
export DATABASE_URL=$(echo "$DB_URL_RAW" | sed -E 's|@/([^?]+)\?host=/cloudsql/[^"]+|@127.0.0.1:5432/\1|')
pnpm -F @liga/db migrate
pnpm -F @liga/db seedprod を増やすとき
1. backend を prod prefix に切替
sh
cd infra/terraform
terraform init -reconfigure \
-backend-config="bucket=${PROJECT_ID}-tf-state" \
-backend-config="prefix=prod"2. envs/prod.tfvars の project_id を staging と同じ値に置換
hcl
project_id = "project-0fccf552-fc68-4756-9c1"
env = "prod"
auth_base_url = "https://liga.netlify.app"
db_tier = "db-custom-1-3840"
cloud_run_min_instances = 13. apply
sh
terraform plan -var-file=envs/prod.tfvars
terraform apply -var-file=envs/prod.tfvars同 GCP project に prod 用リソースが立ち上がる (env=prod の suffix 付き、HA + warm)。
日常運用 cheatsheet
| やりたいこと | コマンド |
|---|---|
| staging に切替 | terraform init -reconfigure -backend-config="bucket=${PROJECT_ID}-tf-state" -backend-config="prefix=staging" |
| 差分確認 | terraform plan -var-file=envs/staging.tfvars |
| 反映 | terraform apply -var-file=envs/staging.tfvars |
| Output 取得 | terraform output -json |
| Cloud Run 状態 | gcloud run services describe liga-api-staging --region=asia-northeast1 |
| Cloud SQL 接続 | gcloud sql connect liga-staging --user=liga |
| Secret 値更新 | gcloud secrets versions add <NAME>_staging --data-file=- |
| Cloud Run ログ | gcloud run services logs read liga-api-staging --region=asia-northeast1 --limit=50 |
トラブルシュート
Q. backend prefix を切り替え忘れて staging に prod apply しそうになった
- A.
terraform planでliga-api-stagingをliga-api-prodに rename しようとしてる、など怪しい差分が出る。terraform init -reconfigure -backend-config="prefix=<正しい>"で戻す
Q. apply 中に Ctrl-C してしまった
- A. lock が残ってる場合:
terraform force-unlock <LOCK_ID>→ 再 apply
Q. db-f1-micro が rejected される
- A. 新規 project のデフォルト edition が
ENTERPRISE_PLUSで、これだと shared-core tier が使えない。cloud_sql.tfでsettings.edition = "ENTERPRISE"を明示 (実装済み)
Q. Cloud Run deploy が reserved env names: PORT で失敗
- A.
PORTは Cloud Run がports.container_portから自動セットする予約名。TF の env block から削除 (実装済み)
Q. google_project_service.enabled が staging / prod 両方で repeat?
- A. API は project スコープなので冪等。
disable_on_destroy = falseで destroy 時にも消さない
Q. WIF が staging / prod で 2 つ Pool を作る
- A. 意図的。env ごとに独立した OIDC 信頼境界を持たせて、staging から prod の SA を借りられないように