本番障害発生時、なぜイメージのトレーサビリティとロールバックが必要か

アプリケーションの新バージョンが本番稼働しました。5分後、ユーザーからエラー報告が相次ぎます。チームチャットで最初に飛び交う質問は「今、どのバージョンが動いているんだ?」

誰もその質問にすぐ答えられなければ、貴重な時間を失います。デプロイログを調べ、レジストリのタグを確認し、実際に何がデプロイされたのかを周りに聞いて回っているうちに、数分が数時間に変わります。本番でどのイメージが動いているか判明した頃には、被害はすでに拡大しています。

この状況は、多くのチームが認める以上に頻繁に発生しています。そして、それは2つのことをオプション扱いしてきたからです:何が動いているかを正確に把握すること、そして正常に動作していた状態に確実に戻す手段を持つこと。

トレーサビリティはビルド時に始まる

本番で何が動いているかを追跡する能力は、コンテナイメージをビルドする瞬間から始まります。イメージにどのようにタグを付けるかによって、後でそれを確実に識別できるかどうかが決まります。

v1.2.3production のようなタグは人間にとって便利です。バージョンを一目で認識できます。しかし、タグはトレーサビリティには信頼できません。タグはイメージを指す単なるラベルであり、そのラベルは変更可能です。myapp:production というイメージは、今日はバージョン1.2.3を指していても、明日はバージョン1.3.0を指すかもしれません。タグだけを追跡していると、実際にどのバージョンが動いているのか確信が持てません。

信頼できる唯一の情報源はイメージダイジェストです。ダイジェストはイメージのコンテンツから生成される一意のハッシュです。2つのイメージが同じダイジェストを持つなら、それらは同一です。曖昧さがなく、誤ったタグ付けのリスクも、ラベルの上書きもありません。何が正確に動いているかを知る必要があるとき、必要なのはダイジェストです。

タグだけでなくダイジェストを記録する

パイプラインでは、各ステージを通過するすべてのイメージのダイジェストを取得する必要があります。イメージがビルドされたら、そのダイジェストを記録します。セキュリティスキャンを通過したら、再度記録します。ステージングに昇格し、本番にデプロイされる際も、そのダイジェストをデプロイ記録に残し続けます。

この情報はどこに保存するのでしょうか?最も実用的な場所はデプロイマニフェストです。デプロイマニフェストは、システムにコンテナの実行方法を指示するファイルです。KubernetesではYAMLファイル、Docker Composeではcomposeファイルです。デプロイするたびに、マニフェストはタグだけでなく、正確なダイジェストを参照する必要があります。

Kubernetesデプロイメントでの具体例は以下の通りです:

パイプラインでダイジェストを取得するには、次のような手順を使用します:

# イメージをビルドしてプッシュ
docker build -t myregistry.com/myapp:latest .
docker push myregistry.com/myapp:latest

# レジストリからダイジェストを取得
export DIGEST=$(docker inspect --format='{{index .RepoDigests 0}}' myregistry.com/myapp:latest)
echo "Deploying image: $DIGEST"

# デプロイマニフェストでダイジェストを使用
sed "s|image: myregistry.com/myapp:latest|image: $DIGEST|" deployment.yaml > deployment-digest.yaml
kubectl apply -f deployment-digest.yaml

これにより、デプロイメントは常に可変タグではなく、正確なイメージコンテンツを参照するようになります。

以下のシーケンス図は、ダイジェスト記録とロールバックがデプロイメントライフサイクルのどこに位置するかを示しています:

sequenceDiagram participant Dev as 開発者 participant CI as CIパイプライン participant Reg as レジストリ participant K8s as Kubernetes participant User as ユーザー Dev->>CI: コードをプッシュ CI->>Reg: イメージをビルド&プッシュ<br/>(ダイジェストを記録) CI->>K8s: ダイジェストでデプロイ<br/>@sha256:... K8s->>User: 新バージョンを提供 User->>K8s: エラーを報告 K8s->>CI: アラート: 本番障害 CI->>K8s: ロールバック: kubectl rollout undo<br/>(以前のダイジェストを使用) K8s->>User: 以前のバージョンを提供
spec:
  template:
    spec:
      containers:
      - name: myapp
        image: myregistry.com/myapp@sha256:a1b2c3d4e5f6...

@sha256:... の部分に注目してください。これがダイジェストです。この形式を使用すると、Kubernetesに対して、latest がたまたま指しているイメージではなく、その正確なイメージを実行するように指示することになります。

マニフェストにダイジェストを記録することで、永続的な記録が作成されます。過去の任意の時点を振り返り、どのイメージが動いていたかを正確に知ることができます。いつデプロイされたか、誰がデプロイをトリガーしたか、どのような変更が含まれていたかを確認できます。

この記録がなければ、推測に頼ることになります。そして、インシデント発生時の推測は高くつきます。

ロールバック:必要になる前に構築するセーフティネット

トレーサビリティは「何が動いているか」という問いに答えます。ロールバックは「どうやって正常に動作していた状態に戻すか」という問いに答えます。

ロールバックとは、アプリケーションを、安定していることが確認されている以前のバージョンのイメージに戻すプロセスです。しかし、インシデントの最中にこれを効果的に行うことはできません。デプロイ前に準備しておく必要があります。

優れたロールバック戦略は、次の3つの質問から始まります:

  1. 以前のイメージはまだレジストリで利用可能か?
  2. 以前のデプロイマニフェストはまだ使用可能か?
  3. 以前のイメージは現在の設定と互換性があるか?

多くのチームはデプロイマニフェストをGitで管理しています。デプロイのたびに、正確なダイジェストを含むマニフェストをコミットします。問題が発生した場合、マニフェストを以前のコミットに戻して再デプロイできます。これはシンプルで、監査可能で、さまざまな環境で機能します。

Kubernetesでは、kubectl rollout undo を使用して以前のリビジョンに戻すことができます。このコマンドが機能するのは、Kubernetesがデプロイメントリビジョンの履歴を保持しているからです。ただし、保持するリビジョン数を設定する必要があります。少なすぎると、十分にロールバックできなくなります。多すぎると、おそらく使わない履歴のためにクラスタメモリを消費します。

ロールバックが有効な場合とそうでない場合

ロールバックは、アプリケーションレベルの問題に対して迅速かつ効果的です。新しいバージョンでビジネスロジックにバグが混入した場合や、ライブラリの更新で何かが壊れた場合、以前のイメージにロールバックすることでサービスを迅速に復旧できます。

しかし、ロールバックは万能な修正策ではありません。問題がデータベーススキーマにある場合、アプリケーションイメージをロールバックしても役に立たないかもしれません。データベースがすでに、古いアプリケーションコードでは処理できない状態になっている可能性があります。問題がイメージとは別に変更された設定にある場合、イメージだけをロールバックしても、悪い設定が残ったままになります。

ロールバックメカニズムの限界を理解してください。定期的にテストしてください。チームがいつロールバックを使用すべきか、いつ別の解決策を探すべきかを把握していることを確認してください。

ロールバック後は、根本原因を修正する

ロールバックはサービスを復旧します。問題を修正するわけではありません。ロールバックが完了し、ユーザーが影響を受けなくなったら、本当の作業が始まります。

問題を引き起こしたイメージは修正する必要があります。パイプラインは修正されたバージョンで実行を継続する必要があります。その新しいイメージは、ビルド、スキャン、昇格、デプロイと同じプロセスを経ます。ロールバックはセーフティネットであり、旅の終わりではありません。

ロールバックを最終ステップと見なす誤りを犯すチームもいます。ロールバックしてインシデント解決を宣言し、次に進みます。根本原因を調査しなかったため、同じバグが次のリリースで再発します。そうならないようにしてください。

実践的なチェックリスト

次の本番デプロイの前に、このチェックリストを実行してください:

  • パイプライン内のすべてのイメージが、タグだけでなくダイジェストで参照されている
  • デプロイマニフェストが正確なダイジェストとともにバージョン管理に保存されている
  • 以前のイメージが、少なくとも過去Nバージョン分、レジストリに保持されている
  • ロールバック手順が文書化され、非本番環境でテストされている
  • チームが、ロールバックで修正できる問題と、別のアプローチが必要な問題の違いを理解している

チームにとっての意味

トレーサビリティとロールバックは高度なトピックではありません。これらは基本的な運用衛生です。実装するために複雑なプラットフォームや高価なツールは必要ありません。必要なのは、イメージへのタグ付け方法、デプロイメントの記録方法、そして何か問題が発生した瞬間に備えるための規律です。

次に本番が壊れたとき、最初の質問はやはり「どのバージョンが動いている?」でしょう。イメージトレーサビリティが整っていれば、数秒で答えが出ます。そして、テスト済みのロールバックメカニズムがあれば、数時間ではなく数分でサービスを復旧できます。

必要になる前にセーフティネットを構築してください。午前2時にデバッグしている未来の自分が感謝するでしょう。