AWS CodeDeploy の EC2/オンプレミスデプロイ - AppSpec とライフサイクルフックの設計
AppSpec ファイルでデプロイ先パスとライフサイクルフックを宣言的に定義する。タグベースのデプロイグループと Auto Scaling 統合によるフリート管理を紹介します。
AppSpec ファイルの構造
EC2/オンプレミスデプロイの AppSpec ファイル (appspec.yml) は、files セクションでソースファイルのデプロイ先パスを定義し、permissions セクションでファイルの所有者と権限を設定し、hooks セクションでライフサイクルの各段階で実行するスクリプトを指定します。files セクションでは source (リビジョン内のパス) と destination (インスタンス上のパス) のマッピングを定義します。permissions セクションでは owner、group、mode を指定し、デプロイされたファイルの権限を適切に設定します。version フィールドは 0.0 固定であり、AWS が将来の拡張用に予約しています。AppSpec ファイルはリビジョンのルートディレクトリに配置する必要があり、配置漏れはデプロイ失敗の頻出原因です。files セクションで destination にディレクトリを指定した場合、source のディレクトリ構造がそのまま再現されます。単一ファイルのデプロイでは source にファイル名まで指定し、destination にもファイル名を含めるとリネームデプロイが可能です。
ライフサイクルフックの活用
ライフサイクルフックはデプロイの各段階で実行されるスクリプトです。BeforeInstall はファイルのデプロイ前に実行され、古いバージョンのクリーンアップや依存パッケージのインストールに使用します。AfterInstall はファイルのデプロイ後に実行され、設定ファイルの生成やデータベースマイグレーションに使用します。ApplicationStart はアプリケーションの起動スクリプトを実行します。ValidateService はデプロイ後のヘルスチェックを実行し、アプリケーションが正常に動作していることを検証します。ValidateService が失敗するとデプロイが失敗として記録され、自動ロールバックが設定されている場合は前バージョンに戻ります。各フックにはタイムアウト (デフォルト 3600 秒) を設定でき、長時間かかるマイグレーション処理でタイムアウトを延長する運用が一般的です。ApplicationStop は前バージョンの停止に使用されますが、初回デプロイでは前バージョンが存在しないため実行されない点に注意が必要です。フック内で環境変数 DEPLOYMENT_ID と DEPLOYMENT_GROUP_NAME が利用可能なため、デプロイごとに異なる動作を条件分岐で実現できます。
デプロイグループとフリート管理
デプロイグループはデプロイ対象のインスタンスをグループ化する単位です。タグフィルターで Environment:Production かつ Role:WebServer のインスタンスを選択するといった動的な指定が可能です。 Auto Scaling グループとの統合では、スケールアウトで新しく起動したインスタンスに自動的に最新のリビジョンがデプロイされます。デプロイ設定で最小正常ホスト数を指定し、デプロイ中に常に一定数のインスタンスがリクエストを処理し続けることを保証します。 CodeDeployDefault.OneAtATime は 1 台ずつ、 CodeDeployDefault.HalfAtATime は半数ずつデプロイする事前定義の設定です。 EC2 デプロイのプラクティスを深く理解するには、専門書籍 (Amazon)が役立ちます。
設計のベストプラクティスと落とし穴
AppSpec の hooks スクリプトは冪等に設計すべきです。デプロイ失敗後のリトライやロールバックで同じスクリプトが複数回実行される可能性があるため、既にサービスが停止している状態での stop コマンドや、既に存在するディレクトリの作成でエラーにならない設計が必要です。ValidateService フックでは HTTP ヘルスチェックだけでなく、アプリケーションのウォームアップ完了を待つ sleep やリトライロジックを組み込むと、起動直後の一時的な失敗によるデプロイ失敗を防止できます。タグベースのデプロイグループでは、タグの付け替えミスにより意図しないインスタンスにデプロイされる事故が発生しやすいため、CodeDeploy のデプロイ前に対象インスタンス一覧を確認する承認ステップを CodePipeline に組み込む設計が推奨されます。Auto Scaling との統合時は、スケールアウトで起動した新規インスタンスへのデプロイが失敗すると Auto Scaling がインスタンスを異常と判定して終了し、無限ループに陥る場合があります。これを防ぐには Auto Scaling のライフサイクルフック (EC2 側) と連携し、デプロイ完了を待ってから InService に遷移させる構成にします。
CodePipeline との統合とロールバック戦略
CodePipeline を組み合わせることで、ソースコード変更の検知からビルド、テスト、デプロイまでを自動化できます。ステージ間に手動承認アクションを挿入すれば、本番デプロイ前のレビューゲートとして機能します。CodeDeploy のロールバック機能では、ValidateService フックやヘルスチェックの失敗時に自動で前バージョンへ戻します。CloudWatch アラームとの統合により、デプロイ後のエラーレート上昇やレイテンシ増加を検知してロールバックをトリガーすることも可能です。Blue/Green デプロイでは旧環境を一定期間保持し、問題発見時の即座のロールバックを保証します。デプロイログは CloudWatch Logs に送信され、障害分析に活用できます。
CodeDeploy と他のデプロイ方式の比較
EC2 へのデプロイ手段として CodeDeploy、Systems Manager Run Command、Ansible/Chef による構成管理、コンテナ化 (ECS/EKS) が選択肢になります。CodeDeploy はエージェントベースのプル型デプロイで、SSH 接続不要かつセキュリティグループの穴あけが不要という利点があります。Systems Manager Run Command は単発のコマンド実行に向いていますが、ロールバックやデプロイ進捗の段階制御は自前で実装する必要があります。Ansible/Chef は構成管理の冪等性は優れますが、AWS ネイティブのデプロイ進捗管理 (最小正常ホスト数、自動ロールバック) を持ちません。コンテナ化は不変インフラの理想形ですが、レガシーアプリケーションの移行コストが高く、EC2/オンプレミス上の既存ワークロードには CodeDeploy が最も低コストで段階的に導入できます。オンプレミスサーバーをデプロイ対象に含められる点は CodeDeploy 固有の強みであり、ハイブリッド環境で統一的なデプロイパイプラインを構築する唯一の AWS ネイティブ選択肢です。
CodeDeploy の料金
CodeDeploy の EC2/オンプレミスデプロイは無料で利用できます。EC2 インスタンスへのデプロイに追加料金は発生しません。オンプレミスサーバーへのデプロイは 1 インスタンスあたり 1 デプロイにつき約 0.02 ドルです。ECS と Lambda へのデプロイも無料です。CodeDeploy 自体のコストは実質ゼロに近く、CI/CD パイプラインのデプロイステージとして導入のハードルが低い点が魅力です。ただし、S3 に格納するリビジョンのストレージ料金、デプロイトリガーに使う CodePipeline のパイプライン料金 (1 パイプラインあたり月額約 1 ドル)、通知用の SNS 料金は別途発生します。大規模フリート (数百台以上) ではオンプレミスデプロイの従量課金が無視できなくなるため、月間デプロイ回数とインスタンス数の積から事前にコスト試算を行うべきです。
まとめ
CodeDeploy の EC2/オンプレミスデプロイは AppSpec ファイルとライフサイクルフックで宣言的にデプロイプロセスを定義します。タグベースのデプロイグループで動的なフリートに対応し、最小正常ホスト数でデプロイ中の可用性を保証します。オンプレミスサーバーも対象に含められるため、ハイブリッド環境の統一的なデプロイ管理に有効です。スクリプトの冪等設計と Auto Scaling 統合時のライフサイクル連携を徹底することで、安定した無停止デプロイを実現できます。