Rebounder Tech Blog

運用している当事者が書く、本番システムの記録。

デプロイ後に無関係な機能が消えた。原因は分岐元が古いブランチだった

公開 読了時間 約5分執筆: Rebounder 開発チーム(当該システムの運用当事者)

※本記事にはアフィリエイトリンクを含む場合があります。内容は広告の有無に影響されません。

結論

Cloud Run のデプロイはイメージタグの差し替えでしかなくブランチの履歴を見ないため、枝分かれ点が古いブランチからビルドし直すと、その後mainへマージ済みの機能はgit revertコミットを1つも作らずに本番から消える。

結論

Cloud Run のデプロイは、terraform の locals に書いたイメージタグを新しい値へ差し替えて apply するだけの操作で、ブランチの履歴は一切見ない。枝分かれ点が古いブランチからビルドしたイメージへタグを差し替えると、その後 main へマージ済みの機能は git revert コミットを1つも作らずに本番から消える。

症状

本番で、性質の異なる2件の不具合報告が同時に上がった。

  • 配信中の画面テンプレートの1つが、直前まで表示できていたのに表示されなくなった
  • 編集画面でテンプレートの設定を変更しても、保存しても反映されない

見た目は無関係な2つの機能の不具合に見えた。片方は配信側、片方は編集側で、コードの場所も違う。だが直前に本番のイメージタグを bump したタイミングと重なっていた。

# bump 前
web_image_tag = "e95d5ff" # 公開サイネージ /signage を タブレット/PC(≥900px)で実機モニタ(16:9・1920×1080)の固定スケール縮小表示に…

# bump 後(この bump で症状が出た)
web_image_tag = "9f73a56" # signage fit-stage を実機端末(Android WebView/TV)には当てず全画面描画にする出し分け…

bump 自体は「実機端末には縮小表示を当てない」という、狙った変更どおりに見える差分だった。plan にも destroy は出ない。apply も成功する。タグの文字列が変わったこと自体は正しく、それでも2つの機能が消えた。

原因

web_image_tag が指すイメージは、feat/signage-fit-to-monitor という作業ブランチからビルドされていた。このブランチは、本番が直前に指していたコミットよりずっと前の時点で main から枝分かれしていた。

問題は、その枝分かれ点よりに、別の2つの機能が main へマージ済みだったことだった。

  • 配信中の画面テンプレートの1つを追加した変更
  • 編集画面でのテンプレート反映を修正した3つの変更

作業ブランチはこれらのコミットを含んでいない。そこから作ったイメージへタグを差し替えるということは、main の最新から見て過去の状態を、そのまま本番に持ってくることを意味する。

image = "${module.artifact_registry.image_repo_url}/web:${local.web_image_tag}"

Cloud Run の web サービスが起動時に見るのは、この1行が組み立てる文字列だけである。local.web_image_tag が指すイメージの中に何のコミットが含まれているかは、terraform にとって関知しない話になる。tag を文字列として差し替えているだけなので、「新しいタグ」と「新しい機能」は同じ意味ではない。

git の側から見ても、この操作は revert には見えない。revert はコミット履歴を新しいコミットとして残す操作だが、タグの差し替えはコミット履歴そのものには一切触れない。**mainブランチの履歴を見る限り、消えた機能のコミットは変わらずそこに存在している。**本番から消えたのは、その機能を含んでいないイメージにタグが向いていたからで、コードが消えたわけでも取り消されたわけでもない。この「履歴上は何も起きていないのに、動いているものだけ変わる」という形が、2つの不具合を無関係に見せていた。

直す

正しい復旧は、bump 前に本番が意図して指していたタグをベースとして扱い、そこへ本当に追加したかった2コミットだけを cherry-pick する形にした。

web_image_tag = "f3fe951" # 障害復旧: stale ブランチ(feat/signage-fit-to-monitor)から prod を bump したため
                           # pattern4 とエディタのパターン解決が静かにロールバックしていたのを是正。
                           # f3fe951 = a12b7ef(前任 web:a12b7ef を内包) に signage-fit と
                           # 実機 fit-stage 除外を cherry-pick。schema 変更なし・secret 増減なし

単に main の最新からビルドし直す方法もあるが、それでは main に別途マージされている他の変更まで一度に本番へ持ち込むことになり、意図していなかった変更が紛れ込む余地が生まれる。ベースを、消えた機能を含む直前の意図されたタグに固定し、そこへ差分だけを足すことで、変更点を「実機端末には縮小表示を当てない」という2コミット分だけに絞り込んだ。

なぜ気づけなかったか

CI はビルド対象に指定されたブランチの中身を検証するだけで、そのブランチが main に対してどれだけ古いかは検証の対象に入っていない。terraform の plan もイメージタグの文字列が変わったことは示すが、新しいタグが指すイメージが main のどの時点までを含んでいるかまでは示さない。ブランチが古いこと自体は、レビューでもレビュー対象として意識されていなかった。

結果として、この種の巻き戻りは症状が出るまで誰にも見えない。tag の差し替えは正常な apply として通り、消えた機能のコミットは main の履歴に変わらず存在し続けるため、「何が起きたか」を示す痕跡がインフラ側にもコード履歴側にも残らない。気づく手がかりは、実際に触ったユーザーからの「消えた」という報告だけだった。

Cloud Run のイメージタグまわりでは、イメージが digest で凍って更新されない話 も別の形で「タグを見ているようで見ていない」現象を扱っている。あちらは同じタグを push し直しても反映されない話で、こちらはタグ自体を変えても意図しない過去へ戻る話になる。

よくある質問

Q1なぜgit revertのコミットが1つも無いのに機能が消えたのですか?

デプロイはterraformのlocalsに書かれたイメージタグを新しい値へ書き換えてapplyするだけの操作で、gitの履歴やブランチの祖先関係は一切参照しない。古いブランチからビルドしたイメージへタグを差し替えれば、そのイメージに含まれていない機能はコード上そのまま存在するのに本番からは消える。revertはコミット履歴を操作する行為であり、タグの差し替えはコミット履歴に触れないためrevertコミットは生まれない。

Q2見かけ上ばらばらな2件の不具合が同じ原因だとどうやって分かったのですか?

ユーザー報告は『配信中の画面テンプレートの1つが表示されなくなった』『編集画面でテンプレートを変更しても反映されない』という別機能の不具合に見えた。だが両方とも、bump直前のブランチの枝分かれ点より後にmainへマージ済みの変更で、stale ブランチのイメージにはどちらも含まれていなかった。原因の場所が異なる2つの不具合ではなく、1回のタグ差し替えが両方を同時に巻き戻していただけだった。

Q3正しく戻すには、単に最新のmainからビルドし直せばよかったのですか?

それでも直るが、bump前のタグが元々意図していた機能まで巻き込んで変わってしまう。実際の復旧では、直前に意図されていたタグを正しいベースとして扱い、そこへ本当に新しく追加したかった2コミットだけをcherry-pickしてビルドし直した。ベースを変えずに差分だけを足す形にすることで、意図していなかった変更が紛れ込む余地を無くしている。

Q4この事故はCIやコードレビューで防げなかったのですか?

防げなかった。CIはビルド元に指定されたブランチの内容をそのまま検証するだけで、そのブランチがmainに対してどれだけ古いかは検証対象に入っていない。terraform applyもplan差分としてイメージタグの文字列が変わったことは示すが、そのタグの中身がmainのどの時点を指しているかまでは示さない。ブランチが古いこと自体はレビューの対象にすらならず、症状が出て初めて気づく形になっていた。

確認した環境

  • Terraform 1.9 / google provider 6.x
  • 2026-06-22 に本番で発生(stale ブランチからの bump が12:31、復旧が13:36)

この記事の根拠

  • Terraformファイル 187〜189行目コミット 39cd858
  • Terraformファイル 187〜189行目コミット 85d8a61
  • Terraformファイル 471〜479行目コミット 85d8a61

本文の主張は、上の記録に書かれていることだけです。運用しているリポジトリは非公開のため リンクは張れませんが、どのファイルの何行目を、どのコミット時点で見て書いたかは 記事ごとに残しています。推測で書いた箇所はありません。