パイプラインに適切なアーティファクト保存戦略が必要な理由
アプリケーションのビルドが完了しました。すべてのテストに合格し、セキュリティスキャンでも問題は見つかりませんでした。ビルドログには緑色のチェックマークが表示されています。いよいよデプロイの時間です。しかし、デプロイダッシュボードを開いたとき、どのアーティファクトを使えばいいのかわからないことに気づきます。ビルドサーバーはすでにワークスペースをクリーンアップしていました。ビルドを実行した開発者は休暇中です。残っているのは「ビルドは成功した」という曖昧な記憶だけです。
このシナリオは、ほとんどのチームが認めるよりもはるかに頻繁に発生しています。ビルドは成功したのに、アーティファクトが消えてしまう。あるいはさらに悪いことに、デプロイパイプラインが本番環境でアーティファクトをゼロから再ビルドし、異なるバージョンの依存関係を使用して、テストされていないものを生成してしまう。
問題はビルドそのものではありません。問題は、アーティファクトが、保存、ラベル付け、追跡されるべき検証済みの成果物としてではなく、一時的なファイルとして扱われていることです。
パッケージ化と公開の本当の意味
パイプラインがテストとスキャンを完了すると、多くのチームが軽視しがちなフェーズ、つまりパッケージ化と公開に入ります。これらの2つのステップは、成功したビルドを再利用可能な資産に変えるものです。
パッケージ化とは、検証済みのコードを、ターゲット環境で実行可能な形式に変換するプロセスです。形式は何をビルドしているかによって異なります。
- コンテナベースのアプリケーションの場合、パッケージ化とはコンテナイメージを作成することです。
- Javaアプリケーションの場合、JARまたはWARファイルを生成することです。
- JavaScriptやPythonのライブラリの場合、npmやpipがインストールできるパッケージを作成することです。
- インフラストラクチャの場合、検証済みのTerraformモジュールやCloudFormationテンプレートを生成することかもしれません。
公開とは、そのパッケージ化されたアーティファクトを、後でデプロイパイプラインがアクセスできるレジストリやリポジトリに送信する行為です。レジストリは単なるストレージバケットではありません。アーティファクトを整理し、バージョン管理し、発見可能にする構造化されたシステムです。
以下は、CIパイプラインがコンテナイメージをパッケージ化し、一意のバージョンタグを付けて公開する方法を示す最小限のYAMLスニペットです。
- name: Build and tag Docker image
run: |
docker build -t myregistry.com/myapp:1.0.0-b20240315-a1b2c3d .
- name: Push image to registry
run: |
docker push myregistry.com/myapp:1.0.0-b20240315-a1b2c3d
コンテナイメージは、Docker Hub、Amazon ECR、Harborなどのコンテナレジストリに送られます。アプリケーションパッケージは、Nexus、Artifactory、GitHub Packagesなどのアーティファクトリポジトリに送られます。インフラストラクチャモジュールは、モジュールレジストリや適切なタグが付けられたGitリポジトリに送られます。
このフェーズの成否を分けるたった一つのこと
バージョン管理はオプションではありません。公開するすべてのアーティファクトには、一意で追跡可能なバージョンが必要です。これは、プロフェッショナルに見えるからセマンティックバージョニングに従うという話ではありません。「現在本番環境で実行されているものは一体何か?」という質問に答えられるようにするためです。
アーティファクトに latest や stable というラベルが付いている場合、その質問に答えることはできません。これらのラベルは時間の経過とともに変化します。内部のコード変更については何も教えてくれません。本番環境でバグが発生し、それを導入したコミットまで追跡する必要がある場合、latest のようなラベルは何の情報も与えてくれません。
適切なバージョン文字列は、セマンティックバージョンとビルドメタデータを組み合わせたものです。1.2.3-b20240315-a1b2c3d のようなものは、リリース番号、ビルド日付、コミットハッシュを教えてくれます。これにより、アーティファクトを正確なソースコード、正確なビルドジョブ、正確なテスト結果まで追跡するのに十分です。
メタデータはあなたのセーフティネット
バージョン文字列だけでも便利ですが、それだけでは十分ではありません。公開されるすべてのアーティファクトには、以下を記録するメタデータが含まれている必要があります。
- ビルドをトリガーしたコミットハッシュ
- ブランチ名
- ビルド番号
- テスト結果のサマリー
- 誰がビルドをトリガーしたか
コンテナイメージの場合、このメタデータはラベルに格納されます。パッケージファイルの場合、マニフェストまたは付随するメタデータファイルに格納されます。このメタデータにより、アーティファクトはブラックボックスから文書化された資産に変わります。「このアーティファクトは必要なテストをすべて通過しましたか?」と誰かに尋ねられたとき、メタデータを指し示して、通過したことを証明できます。
これがデプロイの確信にとって重要な理由
アーティファクトがパッケージ化され、バージョン管理され、メタデータとともに公開されると、デプロイパイプラインは自信を持ってそれをプルできます。デプロイステップは単純な操作になります。アーティファクトバージョンXを取得し、環境Yにデプロイするだけです。
この基盤がなければ、デプロイは推測作業になります。チームは、元のビルド出力がなくなってしまったため、本番環境でアーティファクトを再ビルドすることになります。本番環境での再ビルドは危険です。ビルドは異なる依存関係バージョン、異なるコンパイラ、異なるベースイメージを使用する可能性があります。その結果、テストされたことのないアーティファクトが、障害が最も深刻な影響を与える環境で実行されることになります。
適切に保存されたアーティファクトは、このリスクを排除します。すべてのテストに合格したアーティファクトは、デプロイされるアーティファクトとまったく同じものです。再ビルドも、驚きもありません。
一般的なレジストリの選択肢
これを正しく行うために、高価なエンタープライズソリューションは必要ありません。重要なのは、自分のスタックに合ったレジストリを選び、一貫して使用することです。
- コンテナイメージ: Docker Hub、Amazon ECR、Google Artifact Registry、Harbor、またはOCI準拠のレジストリ。
- アプリケーションパッケージ: Nexus、Artifactory、GitHub Packages、GitLab Package Registry、またはnpmやPyPIなどの言語固有のレジストリ。
- インフラストラクチャモジュール: Terraform Cloud、セマンティックタグ付きのGit、または専用のモジュールレジストリ。
レジストリ自体よりも、その使い方の方が重要です。厳格なバージョン管理とメタデータを備えたシンプルなレジストリは、誰もがランダムなラベルでアーティファクトをプッシュする高度なレジストリよりも優れています。
パイプラインのためのクイックチェックリスト
パッケージ化と公開のステージを設定またはレビューする場合は、次のチェックを実行してください。
- すべてのアーティファクトに、コミットハッシュまたはビルド番号を含む一意のバージョンがある。
- 本番環境で使用するために、
latestのようなラベルが付いたアーティファクトは公開されていない。 - メタデータ(コミットハッシュ、ブランチ、ビルド番号、テストステータス)が、公開されたすべてのアーティファクトに添付されている。
- ビルドサーバーは、ビルド完了直後にアーティファクトをクリーンアップしない。
- デプロイパイプラインはレジストリからアーティファクトをプルし、決して再ビルドしない。
- レジストリには、本番アーティファクトの誤削除を防ぐ保持ポリシーがある。
次に来ること
アーティファクトが安全に保存され追跡可能になったので、次のステップはデプロイです。しかし、先に進む前に、レジストリがロールバックをサポートするのに十分整理されているかどうかを確認してください。以前のバージョンに戻す必要がある場合、先週のデプロイから正確なアーティファクトを見つけられますか?答えが「いいえ」の場合、保存戦略にはまだギャップがあります。
適切に保存されたアーティファクトは、再現可能で信頼性の高いデプロイの基盤です。それがなければ、すべてのデプロイに隠れたリスクが伴います。それがれば、実行しているものがテストしたものとまったく同じであることを認識してデプロイできます。