システム設計/インフラ — GCP プロモーション管理システム 3設計課題

2026-04-28 B: システム設計/インフラ ★★★☆☆ Cloud Run v2 × Cloud SQL × Datastream Terraform × Workload Identity Federation

概要

🔗

Cloud SQL Auth Proxy(サイドカー)

Cloud Run v2のマルチコンテナ機能でAuth Proxyをサイドカーとして動かす。キーファイル不要でIAMベース認証。

🌊

Datastream CDC

Cloud SQLのPostgreSQL logical replicationを使ったCDC(Change Data Capture)でBigQueryにリアルタイム同期。

🏗️

Terraform 環境分離

GCPではプロジェクト = 課金・権限の分離単位。環境ごとにプロジェクトを分け、Terraformで管理する。

🔐

Workload Identity Federation

GitHub ActionsからGCPにデプロイする際にサービスアカウントキー(JSONファイル)が不要になる。

問題

ECサイトのプロモーション管理システムをGCP上で運用しています。現在の構成:

[Cloud Run v2] → [Cloud SQL (PostgreSQL)] → [BigQuery(日次バッチ)]

現在の課題

課題詳細
接続数過多キャンペーンイベント時に Cloud Run に急激なトラフィック(通常の10〜50倍)。Cloud SQL への接続数が上限(500接続)に達してタイムアウト
リアルタイム分析なしBigQuery へのデータ同期が日次バッチのみでリアルタイム分析ができない
コスト管理困難すべてのサービスが同一プロジェクトに混在しており、チーム別コスト管理が難しい

制約

  • GCPのみ使用(他クラウドは不可)
  • IaC は Terraform 1.8+ を使用
  • Cloud Run v2(第2世代)を前提
  • Workload Identity Federation を使用してサービスアカウントキーを廃止すること

ヒント(段階的開示)

ヒント1 — 各問題の方向性
  • 接続数問題: Cloud Runが直接Cloud SQLに接続していることが問題の根本。間に接続プーリング層を挟む必要がある。
  • リアルタイムパイプライン: Datastream(CDC)→ BigQuery Storage Write API の構成が定番。
  • プロジェクト分離: terraform.tfvars ごとに環境を分ける。共通リソース(VPC, IAM)は shared プロジェクトで管理。
ヒント2 — 接続プーリング(推奨構成)
Cloud Run → Cloud SQL Auth Proxy (Cloud Run sidecar) → Cloud SQL
                ↓
          min-instances=N で接続を維持
          SQLAlchemy: pool_size=5, max_overflow=0

# 接続数計算:
# max_instances=50 × pool_size=5 = 最大250接続
# (500接続上限の50%で余裕あり)
ヒント3 — Terraform骨格とWorkload Identity
# environments/prod/main.tf
module "cloud_run" {
  source     = "../../modules/cloud-run"
  project_id = var.project_id
  region     = var.region
}

# Workload Identity Federation(GitHub Actions 用)
resource "google_iam_workload_identity_pool" "github" {
  workload_identity_pool_id = "github-actions-pool"
  project                   = var.project_id
}

改善後アーキテクチャ全体図

Cloud Run v2 Pod app container pool_size=5, max_overflow=0 cloud-sql-proxy (sidecar) localhost:5432 / Workload Identity IAMベース Cloud SQL PostgreSQL logical replication max_connections=500 CDC Datastream data_freshness=900s INSERT/UPDATE/DELETE取得 BigQuery リアルタイムストリーム Looker Studio / DataDog Terraform: shared / dev / staging / prod プロジェクト分離 + Workload Identity Federation (GitHub Actions)

解答① 接続数問題: Cloud Run + Cloud SQL Auth Proxy(サイドカー)

Cloud Run v2 では マルチコンテナ(サイドカー) が利用可能。Cloud SQL Auth Proxy をサイドカーとして動かす。

# modules/cloud-run/main.tf

resource "google_cloud_run_v2_service" "app" {
  name     = "${var.service_name}-${var.env}"
  location = var.region
  project  = var.project_id

  template {
    scaling {
      min_instance_count = 2   # 最小2インスタンスで接続維持
      max_instance_count = 50
    }

    service_account = google_service_account.app.email

    containers {
      name  = "app"
      image = var.app_image

      env {
        name  = "DB_HOST"
        value = "127.0.0.1"  # Cloud SQL Auth Proxy 経由
      }
      env {
        name  = "DB_POOL_SIZE"
        value = "5"           # インスタンスあたりの接続数を制限
      }
    }

    # Cloud SQL Auth Proxy をサイドカーとして追加
    containers {
      name  = "cloud-sql-proxy"
      image = "gcr.io/cloud-sql-connectors/cloud-sql-proxy:2"

      args = [
        "--structured-logs",
        "--port=5432",
        var.cloud_sql_connection_name,
      ]

      resources {
        limits = {
          cpu    = "0.5"
          memory = "256Mi"
        }
      }
    }
  }
}

# アプリのサービスアカウント(Cloud SQL Client ロールのみ)
resource "google_service_account" "app" {
  account_id   = "${var.service_name}-sa-${var.env}"
  display_name = "${var.service_name} Service Account (${var.env})"
  project      = var.project_id
}

resource "google_project_iam_member" "cloudsql_client" {
  project = var.project_id
  role    = "roles/cloudsql.client"
  member  = "serviceAccount:${google_service_account.app.email}"
}
# アプリ側の接続プール設定 (SQLAlchemy)
from sqlalchemy import create_engine

engine = create_engine(
    f"postgresql+psycopg2://user:password@127.0.0.1:5432/db",
    pool_size=5,         # Cloud Run 1インスタンスあたり5接続
    max_overflow=0,      # 上限を厳格に守る
    pool_timeout=30,
    pool_pre_ping=True,  # 接続の死活チェック
)

# 接続数計算:
# max_instances=50 × pool_size=5 = 最大250接続(500接続上限の50%で余裕あり)

解答② リアルタイムパイプライン: Datastream → BigQuery

# modules/datastream/main.tf

resource "google_datastream_connection_profile" "source" {
  display_name          = "cloudsql-postgres-${var.env}"
  location              = var.region
  connection_profile_id = "cloudsql-postgres-${var.env}"
  project               = var.project_id

  postgresql_profile {
    hostname = var.cloud_sql_private_ip
    port     = 5432
    username = var.db_replication_user
    password = var.db_replication_password  # Secret Manager から注入
    database = var.db_name
  }
}

resource "google_datastream_stream" "main" {
  display_name  = "cloudsql-to-bigquery-${var.env}"
  stream_id     = "cloudsql-to-bigquery-${var.env}"
  location      = var.region
  project       = var.project_id
  desired_state = "RUNNING"

  source_config {
    source_connection_profile = google_datastream_connection_profile.source.id

    postgresql_source_config {
      publication      = "datastream_publication"
      replication_slot = "datastream_slot"

      include_objects {
        postgresql_schemas {
          schema = "public"
          postgresql_tables { table = "orders" }
          postgresql_tables { table = "campaigns" }
        }
      }
    }
  }

  destination_config {
    destination_connection_profile = google_datastream_connection_profile.destination.id

    bigquery_destination_config {
      data_freshness = "900s"  # 最大遅延15分

      single_target_dataset {
        dataset_id = "${var.project_id}:${var.bq_dataset_id}"
      }
    }
  }

  backfill_all {}
}
コスト試算: Datastream 約$0.50/GB。月間CDCデータ量100GB想定 → 約$50/月。BigQuery Storage は最初の10GBは無料、以降$0.02/GB。

解答③ プロジェクト分離とコスト管理

# プロジェクト構成
# gcp-org/
# ├── shared/          # VPC, Cloud DNS, Artifact Registry
# ├── dev/             # 開発環境
# ├── staging/         # ステージング環境
# └── prod/            # 本番環境

# environments/prod/terraform.tfvars
project_id  = "myapp-prod-123456"
env         = "prod"
region      = "asia-northeast1"
app_image   = "asia-northeast1-docker.pkg.dev/myapp-shared/images/app:v1.2.3"

# environments/dev/terraform.tfvars
project_id  = "myapp-dev-654321"
env         = "dev"
region      = "asia-northeast1"
app_image   = "asia-northeast1-docker.pkg.dev/myapp-shared/images/app:latest"

# ラベルでコスト管理(全リソースに付与)
locals {
  common_labels = {
    env     = var.env
    team    = "mops"
    managed = "terraform"
    project = "promotion-system"
  }
}
# Workload Identity Federation(GitHub Actions 用)
resource "google_iam_workload_identity_pool" "github" {
  workload_identity_pool_id = "github-actions-pool"
  project                   = var.project_id
}

resource "google_iam_workload_identity_pool_provider" "github" {
  workload_identity_pool_id          = google_iam_workload_identity_pool.github.workload_identity_pool_id
  workload_identity_pool_provider_id = "github-provider"
  project                            = var.project_id

  attribute_mapping = {
    "google.subject"       = "assertion.sub"
    "attribute.actor"      = "assertion.actor"
    "attribute.repository" = "assertion.repository"
  }

  oidc {
    issuer_uri = "https://token.actions.githubusercontent.com"
  }
}

# terraform.tf
terraform {
  required_version = ">= 1.8"

  backend "gcs" {
    bucket = "myapp-tfstate-prod"
    prefix = "terraform/state"
  }

  required_providers {
    google = {
      source  = "hashicorp/google"
      version = "~> 6.0"
    }
  }
}

ポイント解説

1 Cloud Run v2 マルチコンテナ(サイドカー)
Cloud SQL Auth Proxy をサイドカーにすることで、アプリコンテナはサービスアカウントキー不要でCloud SQLに接続できる。 IAMで最小権限を実現。
2 pool_size × max_instances = 総接続数
Cloud Runはオートスケールするため、SQLAlchemy の pool_size を小さく保ち、 max_overflow=0 で接続数上限を厳格に管理する。 接続数 = インスタンス数 × pool_size であることを意識する。 (50 × 5 = 250接続、500接続上限の50%で余裕あり)
3 Datastream CDC
Cloud SQL のPostgreSQLのlogical replicationを使ったCDC(Change Data Capture)。 日次バッチと違いINSERT/UPDATE/DELETEをリアルタイムに拾える。 data_freshness=900s で最大15分の遅延でBigQueryに反映。
4 環境別プロジェクト分離
GCPでは「プロジェクト = 課金・権限の分離単位」なので、環境ごとにプロジェクトを分けるのが正解。 Terraform は環境ごとに root module を持ち、共通ロジックは modules/ に切り出す。 全リソースに labels = {env, team} を付与 → 請求エクスポートでチーム別コスト集計が可能。
5 Workload Identity Federation
GitHub ActionsからGCPにデプロイする際にサービスアカウントキー(JSONファイル)が不要になる。 OIDCトークンをGCPが直接信頼する仕組み。サービスアカウントキーのローテーション管理が不要になる。

実務への応用

  • タイムセール開始時: min-instances=2 に設定することで、オートスケール前の初回リクエストでも 接続プールが温まっており、Cloud SQLタイムアウトが発生しない。
  • Datastreamでキャンペーンデータをリアルタイム集計: BigQueryに流したデータをLooker StudioやDataDogで可視化し、 「キャンペーン開始後のCV数・GMVをリアルタイム監視」できるようになる。
  • Terraformでコスト管理: labels = {env = "prod", team = "mops"} を全リソースに付与 → GCPの請求エクスポートをBigQueryに送り、ラベルでチーム別コストを集計できる。

次のステップ

発展問題: Cloud Spanner vs Cloud SQL の選択基準(グローバル展開・トランザクション保証の観点)
  • 参考: google_cloud_run_v2_service Terraform公式ドキュメント
  • 参考: Datastream Terraform provider
  • 参考: Cloud SQL Auth Proxy v2 マルチコンテナガイド

今日のまとめ

Cloud Runの接続数問題は「サイドカーAuth Proxy + pool_size制御」で解決し、 リアルタイム分析はDatastreamのCDCで実現。

Terraform環境分離は「プロジェクト単位 + Workload Identity」が2026年のベストプラクティス。 全リソースにラベルを付与することでチーム別コスト管理も自動化できる。

自己評価

自分の回答

気づき・メモ