Appearance
インフラ構成 (GCP リソース図)
採用構成
- 同一 GCP project に staging と prod を同居 (リソース名で env を suffix)
- Cloud Run v2 + Cloud SQL (Unix socket) で API ↔ DB を結ぶ
- Workload Identity Federation で GitHub Actions から短命 token で deploy
- Secret Manager で機密値、TF state には slot だけ作って値は外部投入
- per-resource IAM で staging SA が prod の secret を読めない構成
依存関係
┌──────────────────────────── GCP プロジェクト ─────────────────────────────┐
│ │
│ GitHub Actions (OIDC token) │
│ │ │
│ ▼ │
│ ┌────────────────────────┐ │
│ │ Workload Identity Pool │ ← assertion.repository == astyleinc/liga-mono
│ │ └─ Provider (OIDC) │ │
│ └─────────┬──────────────┘ │
│ │ impersonate │
│ ▼ │
│ ┌────────────────────────┐ push image ┌───────────────────────┐ │
│ │ SA: github-actions-X │ ─────────────► │ Artifact Registry │ │
│ │ run.developer (svc) │ │ liga-images (共有) │ │
│ │ artifactregistry.wr │ └────────────┬──────────┘ │
│ │ cloudsql.client │ │ pull │
│ │ secretmanager.acc(X) │ ▼ │
│ │ iam.serviceAccountUser│ ┌─────────────────────────┐ │
│ │ (cloud-run-api-X だけ)│ │ Cloud Run (api) │ │
│ └─────────┬──────────────┘ deploy │ liga-api-X │ │
│ └──────────────────────────────►│ ─ allUsers / public │ │
│ └──────┬──────────┬──────┘ │
│ Unix socket /cloudsql/... │ secret env │
│ │ ▼ │
│ ▼ ┌─────────────────────┐ │
│ ┌────────────┐ │ Secret Manager │ │
│ │ Cloud SQL │ │ BETTER_AUTH_SECRET │ │
│ │ PostgreSQL │ │ STRIPE_* │ │
│ │ liga-X │ │ DATABASE_URL ★ │ │
│ └────────────┘ │ R2_*, etc. │ │
│ └─────────────────────┘ │
│ │
│ X = staging / prod │
│ ★ DATABASE_URL のみ TF が組み立てて投入。他は gcloud で人間が外部投入 │
└──────────────────────────────────────────────────────────────────────────┘リソース一覧 (Terraform 管理)
| ファイル | 内容 |
|---|---|
main.tf | provider / GCS backend / 必須 API 有効化 |
cloud_sql.tf | Cloud SQL (PostgreSQL 17, edition=ENTERPRISE) + DB + user + 乱数 password |
cloud_run.tf | Cloud Run v2 (Hono API) + 公開設定 |
artifact_registry.tf | Docker image 置き場 (staging/prod 共有) |
secrets.tf | Secret Manager slot 群 + placeholder version + DATABASE_URL 投入 |
iam.tf | SA × 2 + WIF Pool/Provider + bindings (per-resource scope) |
outputs.tf | CI / Netlify が使う値の出力 |
env 別の分離
| staging | prod | |
|---|---|---|
| Cloud SQL | liga-staging (ZONAL, db-f1-micro) | liga-prod (REGIONAL HA, db-custom-1-3840) |
| Cloud Run | liga-api-staging (min=0) | liga-api-prod (min=1 warm) |
| Secret | *_staging | *_prod |
| IAM SA | cloud-run-api-staging / github-actions-staging | 同 prod |
| WIF Pool | github-actions-staging | github-actions-prod |
| state prefix | staging | prod |
ラベル (請求 / ログ集計用)
全リソースに統一ラベル:
| Key | Value | 用途 |
|---|---|---|
app | liga | プロジェクト単位 |
env | staging / prod | 環境別 (AR / state バケットは共有なので省略) |
managed-by | terraform | 手動作成と区別 |
component | database / api / secret / registry / tfstate | リソース種別 |
利用例:
sql
-- BigQuery billing export で env 別月次コスト
SELECT labels.value AS env, service.description, SUM(cost) AS cost_usd
FROM `<dataset>.gcp_billing_export_v1_*`, UNNEST(labels) AS labels
WHERE labels.key = "env"
GROUP BY env, service.description
ORDER BY cost_usd DESC;# Cloud Logging を env で絞る
resource.labels.env="staging"セキュリティ設計
同 GCP project に staging + prod を同居させているため、SA の権限が 他環境に染み出さない よう per-resource IAM に徹底。
| SA | 何ができるか | 範囲 |
|---|---|---|
cloud-run-api-${env} | Cloud SQL connect | project (DB 認証必須) |
*_${env} Secret 読取 | secret-level (他環境の secret 不可) | |
github-actions-${env} | image push | liga-images repo (共有) |
| Cloud Run deploy | liga-api-${env} service-level (他環境不可) | |
| SA 借用 (act as) | cloud-run-api-${env} SA-level (他環境不可) | |
*_${env} Secret 読取 | secret-level (他環境不可) | |
| Cloud SQL connect | project (migration 用、DB 認証必須) |
残るリスクと許容理由
| リスク | 影響 | 許容理由 / 対策 |
|---|---|---|
| Cloud SQL public IP (staging のみ 0.0.0.0/0 許可) | DB 認証あれば任意 IP から到達可 | DB password は 32 文字乱数、Secret Manager のみで配布。Netlify が Unix socket 不可なため staging 限定で開く。prod は Private IP + Serverless VPC Access に切替 |
| Cloud Run public endpoint | 誰でも API を叩ける | Better Auth が認証 gate (アプリ層) |
| state に DB password 平文 | state を盗まれれば DB 直接接続可 | GCS バケットは project Owner のみ |
| Artifact Registry 共有 | 仮に staging CI が prod の image を push する可能性 | image tag が SHA-based で overwrite 不能 |
本番化前に検討する強化
- Cloud SQL を Private IP + Serverless VPC Access に切替
- Cloud Run に Cloud Armor で WAF / IP 制限
- Secret Manager の値ローテーション スケジュール
- GCS state バケットに CMEK
- Netlify / GCP / GitHub の 2FA + recovery code 定期確認