# Cloud Run の CI/CD：Cloud Build / GitHub Actions × Workload Identity で鍵レス・Blue/Green・カナリアを実コードで

> Cloud Runへの継続的デプロイを本番品質で組む実装ガイド。Artifact Registry、Cloud BuildとGitHub Actions（Workload Identity Federationで鍵レス）の使い分け、--no-traffic＋タグURLで検証してからカナリア→Blue/Green→即時ロールバック、DBマイグレーションのジョブ分離、Terraformとの責務分離までを、cloudbuild.yaml・GitHub Actions・gcloudの実コードで解説します。

- 公開日: 2026-06-28
- 著者: 友田 陽大
- タグ: GCP, Cloud Run, CI/CD, DevOps, Workload Identity, セキュリティ, インフラ, Terraform
- URL: https://tomodahinata.com/blog/google-cloud-run-cicd-cloud-build-github-actions-workload-identity-blue-green-canary-guide
- カテゴリ: Google Cloud Run 本番運用
- 総合ガイド: https://tomodahinata.com/blog/google-cloud-run-production-guide

## 要点

- Cloud RunのCI/CDの背骨は『イメージはCIでビルドしてArtifact Registryへ、Cloud Runには不変リビジョンをデプロイ、インフラ構成はTerraform』という責務分離。これでドリフトと事故を防ぐ
- 鍵レスが必須。GitHub ActionsはWorkload Identity Federationでサービスアカウント鍵を発行せずに認証する。permissions: id-token: write を忘れない
- 安全な出荷は『--no-traffic＋タグURLで隔離検証 → update-trafficで5%カナリア → 段階引き上げ → 100%（Blue/Green） → 問題時は旧リビジョンへ即時ロールバック』。リビジョンが不変だから再ビルド不要で戻せる
- DBマイグレーションはデプロイと混ぜず、専用のCloud Run Jobに分離する。アプリのロールアウトとスキーマ変更を独立させ、前方/後方互換のある段階適用にする
- Cloud BuildはGCPネイティブで完結、GitHub Actionsはエコシステムが広い。新規はWIF鍵レスを前提に、チームの既存CIに寄せて選ぶ

---

「デプロイが怖い」——本番のコンテナ基盤で最も避けたい感覚です。怖さの正体は、**戻せないこと**と、**何が変わったか分からないこと**。Cloud RunのCI/CDは、この2つを構造的に潰せます。リビジョンが不変だから**再ビルドなしで即座に戻せる**し、責務を分離すれば**何が変わったかが常に明確**になります。

私は[放送事業者向けプラットフォームをGCPで運用](/case-studies/broadcaster-ai-content-platform)する中で、**Cloud Buildでstg/prodを出し分け、Terraformは『インフラ構成』・Cloud Buildは『イメージと最新env』と責務を分離し、DBマイグレーションは専用ジョブに切り出し、CI/CDはWorkload Identity Federationで鍵レス**——という構成で、止まらない社内プラットフォームを回していました。本記事はその設計を、**[Google Cloud公式ドキュメント](https://docs.cloud.google.com/run/docs/continuous-deployment-with-cloud-build)に忠実に**、実コードで再現します。

本番運用の全体像は [Cloud Run 本番運用ガイド](/blog/google-cloud-run-production-guide)、長時間ジョブそのものの設計は [Jobs / Workflows ガイド](/blog/google-cloud-run-jobs-workflows-batch-async-idempotent-guide) を参照してください。

---

## 設計原則：3つの責務を分ける

Cloud RunのCI/CDで事故が起きるのは、たいてい**責務が混ざっている**ときです。最初に境界を引きます。

| 責務 | 担うもの | 真実源 |
|------|---------|-------|
| **アプリの中身** | コンテナイメージ（CIでビルド → Artifact Registry） | Git（コミットSHA＝イメージタグ） |
| **インフラ構成** | サービス・SA・VPC・スケール設定（Terraform） | Terraform state |
| **どのリビジョンに流すか** | トラフィック配分（不変リビジョン） | Cloud Runのトラフィック設定 |

この分離が効くのは——**「イメージタグ＝コミットSHA」**にすれば、本番で動いているものが**どのコミットか一意に追跡**でき、**インフラ変更（Terraform）とアプリ変更（イメージ）が混ざらない**から。`latest` タグは使いません（何が動いているか分からなくなる）。

---

## Artifact Registry：イメージの置き場所

イメージは **Artifact Registry**（旧Container Registry）に置きます。まずリポジトリを作ります。

```bash
gcloud artifacts repositories create app \
  --repository-format=docker \
  --location=asia-northeast1 \
  --description="app container images"
# イメージURLの形：asia-northeast1-docker.pkg.dev/PROJECT_ID/app/api:GIT_SHA
```

---

## 経路A：Cloud Build（GCPネイティブで完結）

GCPだけで完結させたいなら Cloud Build。`cloudbuild.yaml` に「ビルド → プッシュ → デプロイ」を宣言します。

```yaml
# cloudbuild.yaml — push trigger で起動。$SHORT_SHA はCloud Buildが注入する。
steps:
  # 1. ビルド（コミットSHAをタグに）
  - name: "gcr.io/cloud-builders/docker"
    args:
      ["build", "-t",
       "${_REGION}-docker.pkg.dev/$PROJECT_ID/app/api:$SHORT_SHA", "."]
  # 2. Artifact Registry へプッシュ
  - name: "gcr.io/cloud-builders/docker"
    args:
      ["push",
       "${_REGION}-docker.pkg.dev/$PROJECT_ID/app/api:$SHORT_SHA"]
  # 3. トラフィックを流さずにデプロイ（タグURLで検証してから昇格する）
  - name: "gcr.io/google.com/cloudsdktool/cloud-sdk"
    entrypoint: gcloud
    args:
      ["run", "deploy", "api",
       "--image", "${_REGION}-docker.pkg.dev/$PROJECT_ID/app/api:$SHORT_SHA",
       "--region", "${_REGION}",
       "--no-traffic", "--tag", "sha-$SHORT_SHA"]
images:
  - "${_REGION}-docker.pkg.dev/$PROJECT_ID/app/api:$SHORT_SHA"
substitutions:
  _REGION: asia-northeast1
options:
  logging: CLOUD_LOGGING_ONLY
```

GitHubリポジトリに push トリガーを接続すれば、コミットごとに自動でビルド・デプロイ（トラフィックは流さない）まで走ります。

```bash
gcloud builds triggers create github \
  --repo-name=app --repo-owner=YOUR_ORG \
  --branch-pattern="^main$" \
  --build-config=cloudbuild.yaml
```

---

## 経路B：GitHub Actions × Workload Identity（鍵レス）

既存CIがGitHub Actionsなら、こちらが自然です。**サービスアカウント鍵を発行せず**、Workload Identity Federation（WIF）でGCPに認証します。

```yaml
# .github/workflows/deploy.yml
name: deploy
on:
  push:
    branches: [main]

permissions:
  contents: read
  id-token: write   # これが無いとGitHubはOIDCトークンを注入せず、認証が失敗する

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      # 鍵レス認証：プールとプロバイダはWIFで事前設定（下記リンク参照）
      - id: auth
        uses: google-github-actions/auth@v3
        with:
          # ★プロジェクト「番号」を含むフルパス。プロジェクトIDではない。
          workload_identity_provider: "projects/123456789/locations/global/workloadIdentityPools/github/providers/app-repo"
          service_account: "deployer@PROJECT_ID.iam.gserviceaccount.com"

      - uses: google-github-actions/deploy-cloudrun@v3
        with:
          service: api
          region: asia-northeast1
          image: asia-northeast1-docker.pkg.dev/PROJECT_ID/app/api:${{ github.sha }}
          flags: "--no-traffic --tag=sha-${{ github.sha }}"
```

> **WIFのプール/プロバイダ設定（Attribute Conditionで自分のリポジトリだけ許可する等）は本記事では繰り返しません。** 設定の勘所——`assertion.repository` の一致を必ず付ける・`sub` をワイルドカードにしない——は専用記事 [GitHub Actionsを鍵レスにする](/blog/github-actions-oidc-keyless-cicd-aws-gcp-guide) にまとめています（DRY）。デプロイ用SAには最小権限（`roles/run.developer`＋Artifact Registry読み取り＋ランタイムSAへの `roles/iam.serviceAccountUser`）だけを与えます。

---

## 安全な出荷：検証 → カナリア → Blue/Green → 即時ロールバック

CIは「トラフィックを流さずにデプロイ」までで止めるのが肝です。**人間（または自動チェック）が検証してから昇格**します。リビジョンが不変だからこそ、この段階制御が安全に効きます。

```bash
# 1. タグURLで隔離検証（本番トラフィックに影響しない）
#    → https://sha-abc123---api-xxxxx.a.run.app をスモークテスト
curl -H "Authorization: Bearer $(gcloud auth print-identity-token)" \
  https://sha-abc123---api-xxxxx.a.run.app/healthz

# 2. 健全なら5%だけカナリア
gcloud run services update-traffic api --region asia-northeast1 \
  --to-tags sha-abc123=5

# 3. エラー率・レイテンシを監視しつつ段階引き上げ（5 → 25 → 50%）
gcloud run services update-traffic api --region asia-northeast1 \
  --to-tags sha-abc123=50

# 4. 問題なければ100%へ（Blue/Green切替）
gcloud run services update-traffic api --region asia-northeast1 --to-latest

# ── 異常を検知したら、旧リビジョンへ即時ロールバック（再ビルド不要）──
gcloud run services update-traffic api --region asia-northeast1 \
  --to-revisions api-00021-prev=100
```

**ロールバックが「旧リビジョンに100%戻すだけ」で完結する**のがCloud Run最大の安全装置です。イメージの作り直しもデプロイのやり直しも要りません。これを CI/CD の標準手順に組み込んでおけば、夜間の障害でも数十秒で平常へ戻せます。

---

## DBマイグレーションはデプロイから分離する

最も事故りやすいのが**スキーマ変更**です。アプリのロールアウトとマイグレーションを同じステップに混ぜると、ロールバックしたときに「コードは古いがスキーマは新しい」不整合に陥ります。正解は**専用のCloud Run Jobに切り出し、前方/後方互換のある段階適用**にすること。

```bash
# マイグレーション専用ジョブを用意し、デプロイとは独立に実行する
gcloud run jobs deploy db-migrate \
  --image asia-northeast1-docker.pkg.dev/PROJECT_ID/app/migrate:${GIT_SHA} \
  --region asia-northeast1 \
  --service-account migrator@PROJECT_ID.iam.gserviceaccount.com \
  --max-retries 0           # マイグレーションは安易にリトライさせない
gcloud run jobs execute db-migrate --region asia-northeast1 --wait
```

ゼロダウンタイムのスキーマ変更は「①互換性のある列追加 → ②新旧両対応のコードをデプロイ → ③バックフィル → ④古い参照を消すコード → ⑤旧列削除」という**多段リリース**にします。設計の詳細は [ゼロダウンタイムのスキーマ移行](/blog/postgresql-zero-downtime-schema-migration-lock-safe-ddl-guide) を参照してください（DBはCloud SQL/PostgreSQLでも原則は同じ）。ジョブの作り込み自体は [Jobs / Workflows ガイド](/blog/google-cloud-run-jobs-workflows-batch-async-idempotent-guide) へ。

---

## Cloud Build と GitHub Actions、どちらを選ぶか

| | **Cloud Build** | **GitHub Actions** |
|---|----------------|---------------------|
| 認証 | GCP内なのでネイティブに簡単 | **WIFで鍵レス**（設定は要る） |
| エコシステム | GCPに最適化 | **広い**（Lint/テスト/他クラウドと統合しやすい） |
| 向くチーム | GCP中心・インフラもCloud Buildに寄せたい | 既にGitHub Actionsが標準 |
| ビルド環境 | マネージド・並列・キャッシュ | ランナー（self-hosted可） |

**正解は「チームの既存CIに寄せる」**こと。どちらも `--no-traffic`＋タグ検証→カナリア→Blue/Greenという**出荷フローは同じ**に作れます。私のプロジェクトでは、ビルド/デプロイのコアは Cloud Build に集約しつつ、GitHub 側で CodeQL・依存更新・テストを回す併用構成にしていました。

---

## 本番投入チェックリスト

- [ ] イメージタグは **コミットSHA**（`latest` を使わない）
- [ ] **Terraform＝インフラ / イメージ＝アプリ** で責務分離
- [ ] CI/CDは **WIFで鍵レス**（`id-token: write` を付ける）
- [ ] デプロイ用SAは **最小権限**（`run.developer`＋AR読み取り＋`serviceAccountUser`）
- [ ] CIは **`--no-traffic`＋`--tag`** まで。昇格は検証後
- [ ] **カナリア → Blue/Green** の段階出荷をスクリプト化
- [ ] **即時ロールバック手順**（旧リビジョンへ100%）を runbook に
- [ ] **DBマイグレーションは専用ジョブ**に分離し、段階適用にする
- [ ] stg環境で本番同等の検証（WAF含む）を先に通す

---

## まとめ：デプロイを「怖くない」作業にする

Cloud RunのCI/CDは、**責務分離（イメージ/インフラ/トラフィック）**と**不変リビジョンによる段階出荷**で、「戻せない・何が変わったか分からない」という恐怖を構造的に消せます。鍵レス（WIF）で認証情報の漏洩リスクも断ち、マイグレーションを分離して不整合も防ぐ。これで**少人数でも、本番デプロイを淡々とこなせる**ようになります。

全体設計は [Cloud Run 本番運用ガイド](/blog/google-cloud-run-production-guide)、コストは [並行性・課金ガイド](/blog/google-cloud-run-autoscaling-concurrency-billing-cost-optimization-guide)、長時間処理は [Jobs / Workflows ガイド](/blog/google-cloud-run-jobs-workflows-batch-async-idempotent-guide) へ。GCPのCI/CD整備や鍵レス化の伴走が必要なら、実運用の知見を踏まえてお手伝いします。
