複数のセッションや自動化に作業を分けてデプロイを回していると、ふと怖くなる瞬間があります。「いま本番に乗っているのは、本当に自分が検証したものだろうか」。今回はその不安が現実になった記録です。狙ってやった実験ではなく、運用の中で自然に起きた事故なので、化粧せずに書きます。
この記事が役に立つのは、作業を複数のコンテキスト(別々のセッションや自動化ジョブ)に分けて進め、ステージングで検証してから本番へ反映している方です。「環境は分けたが、どれが本物の正本なのか」が曖昧なまま回している状態に心当たりがあるなら、同じ落とし穴を踏む前に読んでおく価値があります。
何をしようとしたか(意図)
スマートフォン向けの新しい見た目を整える作業でした。上部にカテゴリタブを置くなど、表示まわりの調整です。手順としては定石どおりで、ステージング環境で作り込み、実機で見え方を確認し、問題がなければ本番へ反映する、という流れを想定していました。
作業は一気にではなく、いくつかの作業コンテキストに分けて進めていました。便宜上、コンテキストA・B・Cと呼びます。
- コンテキストA:ステージング環境を構築し、最初の版を作った
- コンテキストB:(明示の引き継ぎなく)ステージング上のコードを微調整した
- コンテキストC:(Bの変更を把握しないまま)本番へ反映した
それぞれが何の上で動いていたかは本筋ではないので伏せます。要点は「別々の作業の流れに分かれていた」という一点です。
検証環境
公開できる範囲で構成を整理します。
- ステージング環境:本番と同じ配信構成を持つ検証用の稼働環境
- 本番環境:読者に見えている公開環境
- 正本コピー:デプロイ用に脇へ置いていた配布物(リリース用のファイル一式)
- 確認手段:ファイル内容のハッシュ(sha256)
ポイントは、デプロイ時に参照する「正本」が、稼働中のステージングそのものではなく、別管理で脇に置いたコピーだった、という点です。ここが事故の温床になりました。
結果(症状)
本番へ反映したあと、本番だけが“古い見た目”のままでした。ステージングで直したはずの調整が、本番に乗っていない。エラーは出ていません。デプロイは正常終了しています。にもかかわらず、見た目だけが前の版に戻っているように見える、という静かな不一致でした。
気づいたきっかけは外部からの指摘です。本番とステージングの見え方が違う、と教えてもらって初めて発覚しました。自動化の中で静かに進む事故は、たいてい自分では気づけません。
どこがズレていたか(原因究明)
「見た目が違う」という主観だけでは原因にたどり着けないので、内容そのものの指紋を突き合わせました。配布対象のファイルについて、正本コピー・稼働中ステージング・本番のハッシュを並べて比較します。
$ sha256sum ./release/main.bundle.js
b1c9...e4a2 ./release/main.bundle.js
$ sha256sum /srv/staging/current/main.bundle.js
7f3d...90cc /srv/staging/current/main.bundle.js
$ sha256sum /srv/production/current/main.bundle.js
b1c9...e4a2 /srv/production/current/main.bundle.js
結果は一目瞭然でした。
- 稼働中ステージング:7f3d…(新版)
- 脇に置いた正本コピー:b1c9…(旧版)
- 本番:b1c9…(旧版=正本コピーと一致)
つまりこうです。コンテキストBの微調整は、稼働中のステージング実体には入っていました。ところが、別管理になっていた正本コピーには反映されていなかった。コンテキストCはその古い正本コピーを本番へ運んだため、本番だけ前の版になったのです。
「ステージングで検証した」のは事実です。しかし「本番へ運んだもの」は、検証した実体ではなく、検証から取り残された脇のコピーでした。検証済みの正と、配布した正が、別物だったわけです。
どの配り方が事故を起こすのか(比較)
| 観点 | 脇に置いた正本コピーから配る | 稼働中の現物から取得し、ハッシュ照合して配る |
|---|---|---|
| 正本のズレにくさ | 低い(更新漏れで簡単にズレる) | 高い(常に稼働実体を起点にする) |
| 検証済み状態との一致保証 | なし(検証実体と別物になり得る) | あり(不一致なら自動で止まる) |
| ロールバックの容易さ | 手順次第で曖昧 | 退避+ワンステップで戻せる |
| 自動化との相性 | 静かな事故を量産しやすい | ゲートが効くので安全に回せる |
| 運用の手間 | 少ないが事故時の代償が大きい | 取得と照合の一手間が増える |
対策
再発を防ぐために、考え方と手順の両方を直しました。
1. 「検証済みの正」を稼働中の現物に固定する
検証済みの本物は、脇に置いたコピーではなく「実際に動いているステージングの中身」だと定義し直しました。正本は“置物のコピー”ではなく“動いているもの”に置く、という原則です。
2. デプロイ前に稼働環境から現物を取得する
配布物は、保管していたコピーではなく、稼働中ステージングから取り直してから本番へ運ぶようにしました。
$ rsync -a --delete staging:/srv/staging/current/ ./release/
3. ハッシュ一致を確認してから本番へ(ゲート化)
取得した配布物が稼働中ステージングと一致していることを、デプロイの前提条件にしました。一致しなければ自動で中止します。
$ STAGED=$(ssh staging 'sha256sum /srv/staging/current/main.bundle.js' | awk '{print $1}')
$ DEPLOY=$(sha256sum ./release/main.bundle.js | awk '{print $1}')
$ [ "$STAGED" = "$DEPLOY" ] || { echo '正本がステージング現物と不一致。デプロイを中止します。'; exit 1; }
4. 旧版を退避してワンステップで戻せるようにする
反映前に現行の本番を退避しておき、問題があれば即座に戻せる形にしました。
$ mv /srv/production/current /srv/production/previous
失敗・制約・再現条件
- この事故は「正本を稼働環境とは別の場所に置き、複数の作業コンテキストが片方だけを更新する」と再現します。逆に言えば、正本を稼働現物に一本化し、デプロイ前にハッシュ照合すれば塞げます。
- ハッシュ照合はファイル単位の同一性しか見ません。ビルド設定や環境変数の差まではこの手順では検出できないため、別の確認が必要です。
- 今回示したコマンドやパスは公開用にサニタイズした例です。実際の運用では各自の構成に読み替えてください。
- これは一度確認した範囲の記録です。あらゆる配信構成で同じ手順がそのまま使えると主張するものではありません。
読者への持ち帰り
環境やコンテキストを分けて分業・自動化すると、作業は速くなる一方で「どれが本物か」がズレやすくなります。検証は通っているのに本番だけ古い、という事故は、たいていこの“正本のズレ”が原因です。
対策の核は二つです。正本は置物のコピーではなく、動いているものに置くこと。そしてデプロイは現物照合(ハッシュ確認)をゲートにすること。この二つを入れておくだけで、「直したのに本番が古いまま」という静かな事故の多くは、本番に届く前に止められます。
この検証を回している環境
この検証は、自宅の常設ラボ(使い捨てVM/LXCを回す母艦+GPU+VLAN分離ネットワーク)で動かしています。使っている機材と選定理由、全体構成は1本にまとめています。
ラボ構成のまとめを見る →