本番環境で再ビルドしてはいけない理由

数週間前、私が一緒に仕事をしていたチームに奇妙な問題が発生しました。ステージング環境ではすべてのテストに合格し、QAチームも承認し、プロダクトオーナーもゴーサインを出しました。しかし、本番環境にデプロイしたとたん、テスト中には一度も現れなかったエラーがユーザーに表示され始めたのです。

チームは2日間かけてデバッグに追われました。設定ファイルを比較し、環境変数を確認し、コードの変更をレビューしました。すべてが同一に見えました。最終的に、誰かがビルドのタイムスタンプが異なることに気づきました。本番環境で実行されていたアーティファクトは、ステージングで全テストに合格したアーティファクトと同じものではなかったのです。

これは、日々あらゆるチームで繰り返されている話です。根本原因はほとんどの場合同じです。ステージングと本番の間のどこかで、誰かが既存のアーティファクトをプロモーションする代わりに、アプリケーションを再ビルドすることを選択したのです。

2つの道:再ビルド vs. プロモーション

すべてのソフトウェアデリバリーパイプラインには複数の環境があります。新機能を試すための開発環境、QAやプロダクトオーナーによる検証のためのステージング環境、実際のユーザーがアプリケーションを利用する本番環境です。

アーティファクトをある環境から次の環境に移す際には、2つの選択肢があります。

再ビルドとは、特定のコミットからソースコードを取得し、ターゲット環境向けにビルドプロセスを再度実行することを意味します。パイプラインはコードをチェックアウトし、依存関係をダウンロードし、アプリケーションをコンパイルし、新しいアーティファクトを生成します。

次のフローチャートは、2つのパスとその結果を示しています。

flowchart TD A[開始: 本番にデプロイが必要] --> B{パスを選択} B --> C[再ビルド] B --> D[プロモーション] C --> E[ソースコードをチェックアウト] E --> F[依存関係をダウンロード] F --> G[アプリケーションをコンパイル] G --> H[新しいチェックサムとタイムスタンプを持つ新しいアーティファクト] H --> I[本番にデプロイ] I --> J[リスク: テスト済みアーティファクトと異なる] D --> K[レジストリ内の既存アーティファクト] K --> L[本番用として承認済みとマーク] L --> M[同じバイト列を本番にデプロイ] M --> N[確実性: テスト済みアーティファクトと一致]

プロモーションとは、レジストリにすでに存在し、以前の環境でビルドおよび検証済みのアーティファクトを取得し、次の環境で承認済みとしてマークすることを意味します。新しいビルドはありません。新しいコンパイルもありません。「このアーティファクトは本番環境で許可されました」というメタデータの変更だけです。

これらの2つのアプローチは似ているように聞こえますが、根本的に異なる結果をもたらします。

再ビルドに潜むリスク

本番環境向けに再ビルドすると、新しいアーティファクトが作成されます。ビルドIDもタイムスタンプも異なります。そして決定的なことに、この新しいビルド中にダウンロードされる依存関係が、ステージングビルドで使用されたものと異なる可能性があります。

ビルドプロセスで npm installpip installgo mod download を実行するとどうなるかを考えてみてください。これらのコマンドはリモートリポジトリにアクセスしてパッケージを取得します。もしパッケージメンテナーがステージングビルドと本番ビルドの間にマイナーアップデートをプッシュしていた場合、本番アーティファクトにはそのアップデートが含まれることになります。たとえ変更が技術的に後方互換性があったとしても、テストしたものと本番で実行しているものの間に差異が生じます。

同じリスクはツールチェーンにも当てはまります。CIシステムがビルド間でコンパイラのバージョン、ベースイメージ、ビルドツールを更新した場合、出力が微妙に変化する可能性があります。ステージングビルドでは問題なくコンパイルできたコードが、本番ビルドでは異なるマシン命令を生成するかもしれません。

テストしたものが本番で実行されているという確実性が失われます。確信から期待へと移行してしまうのです。

プロモーションが優れている理由

プロモーションはこの不確実性を排除します。アーティファクトは一度だけビルドされ、レジストリに保存され、ステージングで検証されます。すべてのチェックに合格すると、メタデータやタグを更新して本番にプロモーションします。新しいコンパイルも、新しい依存関係のダウンロードもありません。ステージングで実行されたものとまったく同じバイト列が、本番で実行されるのです。

実際には、プロモーションは通常タグ付けを通じて機能します。コンテナレジストリでは、staging というタグが付いたイメージがあるかもしれません。検証後、同じイメージに production タグを追加します。イメージ自体は変わりません。ラベルだけが変わります。デプロイメントシステムは production タグを監視し、それが現れるとイメージをプルします。

このアプローチはロールバックも簡素化します。新しいバージョンが本番で問題を引き起こした場合、以前にプロモーションされたアーティファクトに戻すだけです。3ヶ月前の古いコミットから再ビルドし、その当時の依存関係やツールチェーンがまだ利用可能であることを願う必要はありません。古いアーティファクトは、以前プロモーションされたときとまったく同じ状態で、すでにレジストリに存在しています。

プロモーション前の検証は依然として必要

プロモーションは検証をスキップすることを意味しません。アーティファクトがステージングから本番に移行する前に、定義された一連のテストに合格する必要があります。単体テスト、統合テスト、そしてアプリケーションに適した環境固有のチェックです。これらのテストはステージングでアーティファクトに対して実行されます。合格すればアーティファクトはプロモーションされます。失敗した場合、アーティファクトはステージングに留まり、チームはソースコードを修正し、再ビルドし、検証プロセスを再度開始します。

重要な違いは、検証とプロモーションで同じアーティファクトを使用することです。あるバージョンをテストして、別のバージョンをデプロイしているわけではありません。

再ビルドが必要な場合

再ビルドが正しい選択となる正当なケースもあります。アーティファクトに環境固有の設定をビルド時に組み込む必要がある場合、環境ごとに個別のビルドが必要になるかもしれません。コンプライアンス要件により、本番アーティファクトを別のよりセキュアなパイプラインでビルドする必要がある場合も、再ビルドせざるを得ないでしょう。

しかし、これらのケースは例外であり、ルールではありません。ほとんどのチームは設定をコードから分離できます。ほとんどのコンプライアンス要件は、アーティファクトを再ビルドするのではなく、プロモーション後に署名することで満たせます。本番環境向けに定期的に再ビルドしているのであれば、その理由が技術的な必要性なのか、単なる習慣なのかを問い直してください。

アーティファクトプロモーションの実践的チェックリスト

プロモーションワークフローを設定する前に、以下の点を確認してください。

  • ビルドプロセスが、すべての環境で動作する単一のアーティファクトを生成すること
  • レジストリが再アップロードなしでタグやメタデータの更新をサポートしていること
  • デプロイメントシステムがタグの変更を監視し、デプロイをトリガーできること
  • ロールバックプロセスが古いコミットではなく、以前にプロモーションされたアーティファクトを参照すること
  • チームが「プロモーション」とはメタデータの変更であり、再ビルドではないことを理解していること

具体的な教訓

再ビルドとプロモーションの選択は、1つの問いに帰着します。テストしたものをデプロイしたと確信したいのか、それとも新しいビルドが同じ結果を生み出すことを期待したいのか。

プロモーションは確実性をもたらします。再ビルドは期待をもたらします。本番環境では、期待よりも確実性が重要です。一度ビルドし、徹底的に検証し、自信を持ってプロモーションしてください。ユーザーは感謝し、デバッグにかかる時間も短縮されるでしょう。