実際に使われるデプロイメントテンプレート
どのチームにも、誰かが手順を忘れてデプロイが失敗した経験がある。ビルド成果物をレジストリにプッシュし忘れた。「小さな変更だから」とスモークテストをスキップした。ロールバック計画はミーティングで話しただけで誰も文書化しておらず、障害発生時に誰も何をすべきか合意できなかった。
これらの問題はスキルの問題ではない。プロセスの問題だ。プレッシャーがかかると——本番インシデント、タイトな納期、マネージャーからの進捗確認——人は手順を飛ばし始める。やりたくてやるのではなく、従うべき明確なチェックリストがないからだ。
デプロイメントテンプレートはこの問題を解決する。官僚的なドキュメントではない。セーフティネットだ。
デプロイメントテンプレートの役割
デプロイメントテンプレートとは、アプリケーションの新バージョンを任意の環境にプッシュするたびに必ず実行すべき手順のリストである。仕事のやり方を指示するものではない。何を忘れてはいけないかを示すものだ。
このテンプレートはバックエンドAPI、Webアプリケーション、バックグラウンドワーカーのいずれでも機能する。技術的な詳細は異なる——Dockerイメージを使うチームもあれば、コンパイル済みバイナリを使うチームもある——が、構造は同じだ。その構造は4つのフェーズから成る:ビルドと検証、ステージングへのデプロイ、本番へのデプロイ、ロールバック計画の準備。
以下の図は4つのフェーズとそのつながり、およびフェーズが失敗した場合のフィードバックループを示している。
フェーズ1:ビルドと検証
コードをどこかにデプロイする前に、実際に動作することを証明する必要がある。このフェーズではほとんどのチームがすでに自動化を持っているが、テンプレートによって何も見逃されないようにする。
手順はシンプルだ:
以下はNode.jsアプリケーション向けにフェーズ1を実装したGitHub Actionsワークフローである:
name: Build and Verify
on:
push:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm ci
- run: npm test
- run: npm run build
- name: Push Docker image
run: |
docker build -t myapp:${{ github.sha }} .
docker tag myapp:${{ github.sha }} registry.example.com/myapp:${{ github.sha }}
docker push registry.example.com/myapp:${{ github.sha }}
このワークフローはmainブランチへのプッシュのたびにトリガーされ、テストを実行し、アプリケーションをビルドし、Dockerイメージをレジストリにプッシュする。いずれかのステップが失敗すると、デプロイは自動的に停止する。
- 正しいブランチからコードをチェックアウトする。「正しいはず」のブランチではなく、実際に検証したブランチを使う。
- ビルドプロセスを実行する。これにより成果物(Dockerイメージ、JARファイル、コンパイル済みバイナリなど、スタックに応じたもの)が生成される。
- ユニットテストと統合テストを実行する。コードが期待通りに動作し、各コンポーネントが正しく連携することを確認する。
- プロセスを停止させるエラーや警告がないか確認する。警告があってもビルドが通れば合格だが、一部の警告は実際の問題を示している。
- ターゲット環境がアクセスできるレジストリに成果物をプッシュする。成果物が保存されていなければ、後でデプロイできない。
このフェーズのいずれかのステップが失敗した場合、デプロイは停止する。コードを修正して最初からやり直す。例外は認めない。
フェーズ2:ステージングにデプロイ
ステージングはリハーサル環境だ。可能な限り本番をミラーリングするが、実際のユーザーが触れることはない。ここではテストでは見つけられない問題——設定ミスマッチ、環境固有のバグ、本番に近いセットアップでのみ存在するサービスとの統合問題——を発見する。
このフェーズの手順は以下の通り:
- レジストリから成果物をプルする。フェーズ1を通過したのとまったく同じ成果物を使う。
- ステージング環境にデプロイする。本番で使うのと同じデプロイメカニズムを使うべきである。
- スモークテストを実行する。アプリケーションがリクエストに応答し、データベースに接続し、依存関係と通信できることを確認する簡単なチェックだ。
- ログで予期しないエラーを確認する。正常なアプリケーションは予測可能なログを生成する。異常なものは調査に値する。
- フル環境が必要な統合テストを実行する。システム全体が動作している場合にのみ意味を持つテストもある。
すべてが正常に見えれば、次のフェーズに進む。何かが壊れていれば、フェーズ1に戻り、コードを修正し、再ビルドしてステージングに再デプロイする。
フェーズ3:本番にデプロイ
これは重要なフェーズだ。実際のユーザーがアプリケーションに依存している。ここでのミスは実際の業務に影響を与える。
本番デプロイは段階的に行うべきである。ブルーグリーンデプロイ、カナリアリリース、ローリングアップデートのいずれでも構わない。重要なのは、すべてを一度に置き換えないことだ。
手順は以下の通り:
- 他のデプロイが進行中でないことを確認する。2つのデプロイが同時に実行されると混乱を招く。
- ステージングを通過したのと同じ成果物をプルする。再ビルドしない。検証済みの成果物を使う。
- 選択した戦略でデプロイする。カナリアリリースを使う場合は、トラフィックのごく一部から始める。
- メトリクスを監視する:応答時間、エラー率、CPU使用率、メモリ消費、リクエストスループット。これらの数値が新バージョンの健全性を示す。
- デプロイ後にヘルスチェックとスモークテストを実行する。アプリケーションが実際にトラフィックを正しく処理していることを確認する。
メトリクスが異常を示したら、待ってはいけない。ロールバックを実行する。
フェーズ4:ロールバック計画
すべてのデプロイには元に戻す手段が必要だ。「なんとなく戻す」という漠然とした考えではない。デプロイを開始する前に書かれた具体的な計画だ。
ロールバック計画は3つの質問に答える:
- いつロールバックするか?トリガーを定義する。例:エラー率が5%を超えた場合、応答時間が50%増加した場合、または重要なエンドポイントが応答しなくなった場合。
- どのようにロールバックするか?メカニズムを指定する。これは以前のバージョンにトラフィックをリダイレクトする、古い成果物を再デプロイする、またはデータベースマイグレーションのロールバックスクリプトを実行するなどである。
- 誰が決定するか?ロールバックを実行する権限を持つ人物または役割を指名する。インシデント時に合意を待つのは時間の無駄だ。
ロールバック計画を文書化する。チームと共有する。本番デプロイを開始する前に合意を得る。
アプリケーションデプロイの実用的チェックリスト
以下はチームに合わせて調整できる短いチェックリストである。網羅的ではないが、必須項目をカバーしている。
ビルドと検証
- 検証済みブランチからコードをチェックアウト
- ビルド成功
- ユニットテストと統合テストが合格
- 成果物をレジストリにプッシュ
ステージングにデプロイ
- レジストリから成果物をプル
- エラーなくデプロイ完了
- スモークテスト合格
- ログに予期しないエラーなし
本番にデプロイ
- 同時実行中のデプロイなし
- ステージングで使った成果物を使用
- 段階的デプロイ戦略を適用
- 10〜15分間メトリクスを監視
- ヘルスチェック合格
ロールバック計画
- ロールバックトリガーを定義
- ロールバックメカニズムを指定
- 決定者を特定
- デプロイ前に計画を共有し合意
テンプレートをチームに合わせて調整する
始めたばかりのチームはシンプルなバージョンを使うかもしれない:ビルド、ステージングにデプロイ、本番にデプロイ、ロールバック。成熟したチームは各フェーズにセキュリティスキャン、パフォーマンステスト、承認ゲートを追加するかもしれない。
重要なのは、いくつの手順を含めるかではない。テンプレートを一貫して使うことだ。誰も読まないWikiに保存されたテンプレートは役に立たない。デプロイパイプラインに組み込まれ、リリースのたびに見直され、新しいことを学ぶたびに更新されるテンプレート——それがデプロイをより安全にする。
まとめ
デプロイメントテンプレートが存在する理由は、特にプレッシャーがかかっているときに記憶は当てにならないからだ。手順を書き留めよ。共有せよ。毎回使え。何かがうまくいかなくなったとき、あなたは何をすべきか正確にわかっている——そしてチームの他のメンバーも同様だ。