Language: 日本語
「デプロイは Green なのにコードが変わっていない?」 Cloud Run CD で最新リビジョンが反映されない罠
GitHub Actions の CD ワークフローがすべて完了し、綺麗な Green ランプが灯っているのを見て安心した直後……。
GitHub Actions の CD ワークフローがすべて完了し、綺麗な Green ランプが灯っているのを見て安心した直後……。
「あれ? 本番の画面を開いても、マージしたはずの変更がまったく反映されていない……?」
と青ざめた経験はありませんか?
develop や main ブランチへのマージを契機に、Google Cloud Run へ自動デプロイする CD パイプラインを組んでいた際、まさにこの 「デプロイ成功という名のサイレント見逃し」 に遭遇しました。
GitHub Actions 上はエラーが 1 件も出ず、CI/CD は完全に成功(exit code 0)として終了しているのに、Cloud Run の本番トラフィックは古いリビジョンを向き続けていたのです。
この記事では、Cloud Run のトラフィック固定(Pinned Traffic)仕様が引き起こすこの罠のメカニズムと、「デプロイコマンドの終了コード」ではなく「実際のインフラ状態(新リビジョンへのトラフィック 100%)」を検証して確実に失敗を検知する 4 ステップの CD ガードレール を解説します。
得られること
- Cloud Run のトラフィック固定(Pinned Traffic)が引き起こす「デプロイ成功(exit 0)なのにトラフィック 0%」の仕組みがわかる
- デプロイ後に期待リビジョンへトラフィック 100% が遷移したかを明示的にアサートする CD 検証手順が学べる
- 「コマンドの終了コード」と「インフラの実状態(State)」の乖離を防ぐ自動化設計の原則が身につく!
なぜ「デプロイ成功なのに反映されない」が起きるのか?
通常の gcloud run deploy は、新しいリビジョンを作成し、自動的に 100% のトラフィックをその新リビジョンへ向けます。
しかし、Cloud Run には 「過去のトラフィック分割やタグ設定を永続化(Pin)する」 という強力な仕様があります。
以下のような運用を一度でも行うと、Cloud Run のトラフィックルーティングは「最新リビジョンへ自動追従するモード(--to-latest)」から 「特定のリビジョンに固定(Pin)されたモード」 へ切り替わります。
- カナリアリリースやパーセント分割:
update-trafficで 90% / 10% などの分割設定を行った - タグ付きリビジョンや Preview QA:
--no-trafficやタグ(--tag)を指定してデプロイした - Terraform による構成管理: Terraform 側が特定リビジョン名を指定してトラフィックを管理していた
【トラフィック固定下でのデプロイの挙動】
GitHub Actions で gcloud run deploy を実行
↓
Cloud Run は新リビジョンを正常にビルド・起動(healthy)
↓
しかし、トラフィック設定は「旧リビジョン固定(100%)」のまま維持される 😱
↓
新リビジョンは「トラフィック 0%」のまま放置される
↓
gcloud コマンドは「リビジョン作成完了」として正常終了 (exit code 0)
↓
GitHub Actions は誇らしげに Green(成功)を表示!
gcloud run deploy からすれば、「指示通りに新しいリビジョンを作成した」という仕事は 100% 完走しているため、エラー(非 0)を出す理由がありません。
結果として、CI/CD パイプラインは Green になり、開発者は「デプロイが成功した」と信じ込みますが、実際のユーザーには旧バージョンのコードが配信され続けるという最悪のサイレント不整合が発生します。
解決策: 期待リビジョンとトラフィック 100% の明示的検証
このサイレント失敗を撲滅するために、CD パイプラインに 「リビジョンとトラフィックの明示的な 4 ステップ検証」 を組み込みました。
【追加した 4 ステップのガードレール】
1. revision suffix をコミットハッシュで固定
↓
2. 作成された revision 名が期待値と一致するか照合
↓
3. gcloud run services update-traffic で期待 revision = 100% を明示指定
↓
4. トラフィックが 100% 振れなければ workflow を強制 fail ⚡️
# GitHub Actions での検証ステップのイメージ
- name: Deploy to Cloud Run
id: deploy
run: |
# コミットハッシュ等で revision suffix を一意に固定
REV_SUFFIX="${{ github.sha }}"
gcloud run deploy backend-service \
--image="${{ env.IMAGE_URI }}" \
--revision-suffix="${REV_SUFFIX}" \
--region=asia-northeast1
- name: Verify and force traffic migration (100%)
run: |
SERVICE_NAME="backend-service"
REGION="asia-northeast1"
EXPECTED_REV="${SERVICE_NAME}-${{ github.sha }}"
# 1. 作成されたリビジョンの存在確認
gcloud run revisions describe "${EXPECTED_REV}" \
--region="${REGION}" > /dev/null
# 2. 明示的に新リビジョンへ 100% トラフィックを割り当て(Pin 解除と強制移行)
gcloud run services update-traffic "${SERVICE_NAME}" \
--to-revisions="${EXPECTED_REV}=100" \
--region="${REGION}"
# 3. 実際のトラフィックルーティングが期待した新リビジョン 100% になったかを直接照合
CURRENT_TRAFFIC_REV=$(gcloud run services describe "${SERVICE_NAME}" \
--region="${REGION}" \
--format="value(status.traffic[0].revisionName)")
CURRENT_PERCENT=$(gcloud run services describe "${SERVICE_NAME}" \
--region="${REGION}" \
--format="value(status.traffic[0].percent)")
if [ "${CURRENT_TRAFFIC_REV}" != "${EXPECTED_REV}" ] || [ "${CURRENT_PERCENT}" != "100" ]; then
echo "❌ エラー: 新リビジョンへのトラフィック切り替えに失敗しました!"
echo "期待: ${EXPECTED_REV} (100%), 実際: ${CURRENT_TRAFFIC_REV} (${CURRENT_PERCENT}%)"
exit 1
fi
echo "✅ 新リビジョン (${EXPECTED_REV}) に 100% トラフィックが正常に切り替わりました。"
ガードレールの効果
- リビジョン作成とトラフィック割り当ての分離・強制:
update-traffic --to-revisions=...=100を明示的に呼ぶことで、過去のトラフィック固定状態を上書きし、新リビジョンへ確実に 100% を向けます。 - 実状態(State)の照合によるフェイルセーフ:
最終的に
status.traffic[0]を直接クエリして照合するため、何らかの理由でトラフィックが切り替わらなかった場合は即座に GitHub Actions を Red で落とせます。
終了コードと実際の状態(State)を混同しない
このトラブルから得られた最大の教訓は、「コマンドの終了コード(exit code)と、インフラの実際の状態(State)を混同してはならない」 ということです。
| 観点 | コマンドの終了コード | インフラの実際の状態(State) |
|---|---|---|
| 意味 | CLI ツールがクラッシュせずに処理を終えたか | 意図したリビジョンが 100% トラフィックを受けているか |
| 信頼性 | ツール側の仕様(トラフィック固定等)によって見かけ上成功しやすい | 実環境の真実(Single Source of Truth) |
| 自動化での扱い | 単なるプロセスの完了通知にすぎない | CD 成功の絶対条件としてアサートすべき対象 |
この「コマンドは通ったのに、実態が追従していない」という現象は、Cloud Run だけでなく様々な場所で発生します。
- DB マイグレーションの CLI を実行したが、ロック競合でスキップされていた
- Terraform の
applyは通ったが、クラウド側の非同期プロビジョニングが裏でコケていた
「デプロイコマンドが 0 で終わったから成功」と盲信するのではなく、「期待した状態に変化したこと(State)を外側から直接クエリして確かめる」。
この一手間を CD パイプラインに挟むだけで、デプロイの信頼性は劇的に向上します。