Dev Study
AWSサービスの内部原理 コース

122. CodeDeployはデプロイをライフサイクルイベントの列にする — appspecという台本と3つの舞台

デプロイという「止めて・配って・起動して・検証する」作業を、CodeDeployがどうやって固定されたイベントの列に変え、その各ステップの中身だけをappspecという台本で差し替えるのか——EC2・Lambda・ECSの3つの舞台で台本の意味がまるで違うところまで、図で追っていきます。

① appspecは「デプロイ=ライフサイクルイベントの列」という宣言

リビジョン配るモノ
各イベント名に対応する中身を実行
appspecYAML/JSONの台本
各デプロイを一連のライフサイクルイベントフックとして管理する
イベントA中身を注入
イベントB記載なし=スキップ
イベントC中身を注入
イベントD…以降も同様
  • 公式定義:「AppSpecファイルは、CodeDeploy固有のYAML形式またはJSON形式のファイル。各デプロイを、ファイル内で定義された一連のライフサイクルイベントフックとして管理するために使われる」
  • イベントフックが台本に無ければ、そのイベントでは何も実行されない(no operation)。台本は「必要なイベントだけ埋める」空欄付きの進行表

CodeDeployは「デプロイの骨格(=イベントの列)」を固定し、appspecはその各ステップに何を差し込むかだけを宣言する台本。処理の枠はプラットフォーム側が持ち中身だけを外から注入する——手順(止める→配る→起動→検証)は固定で各ステップの実装だけを差し替える、テンプレートメソッドパターン(フレームワーク×フック)の関係そのものです。

② 同じ「appspec」でも、3つの舞台で意味がまるで違う

appspec1つの名前
フックの実体が分かれる
EC2/オンプレ常にYAMLのみ
files配置マッピング+権限+各イベントのスクリプト
LambdaYAML/JSON
デプロイする関数バージョン+検証用Lambda関数
ECSYAML/JSON
ECSサービス名・コンテナ名・ポート+検証用Lambda関数
エージェント実行EC2のみ:フック=スクリプト
エージェント不在Lambda/ECS:フック=検証用Lambda関数
  • 公式の明記:「The CodeDeploy agent is not used in an AWS Lambda or an Amazon ECS deployment.」
  • EC2/オンプレのappspecの3役割:(a)リビジョンのファイルをインスタンス上のどこに置くかのマッピング(b)配置ファイルの権限指定(c)各ライフサイクルイベントで走らせるスクリプトの指定
  • Lambdaのフックは BeforeAllowTraffic と AfterAllowTraffic の2つだけ
  • ECSのappspecは「新task setへトラフィックを誘導する」ためのサービス名・コンテナ名・ポートを指定する

「appspec」は1つの名前だが、EC2では箱の中身を入れ替えるための配置図+スクリプト、Lambda/ECSでは新しい実体を指定して検証関数をぶら下げる指示書。EC2だけがエージェントを前提とし、他の2つはエージェント不在という決定的な違いがあります。

③ EC2 in-placeデプロイ:イベントの列を上から下へ

BeforeBlockTrafficLBあり時のみ・列の先頭
BlockTraffic予約・スクリプト不可
AfterBlockTrafficLBから切り離し完了
ここで初めて新リビジョンが届く
ApplicationStop
前回リビジョンの台本で走る
DownloadBundle予約・スクリプト不可
BeforeInstall
Install予約(filesで中身は指定可)
AfterInstall
起動したら検証
ApplicationStart
検証を通ったらLBへ戻す
ValidateServiceデプロイ成否を検証するフック
BeforeAllowTrafficLBあり時のみ・列の末尾
AllowTraffic予約・スクリプト不可
AfterAllowTrafficLBへ再接続完了
  • 灰色(muted)=エージェント予約でスクリプト不可の公式イベント:DownloadBundle・Install・BlockTraffic・AllowTraffic(+開始/終了のStart・End)。ただしInstallの「何を置くか」だけはappspecのfilesセクションで指定できる
  • LB(Classic/Application/Network Load Balancer)をデプロイグループに指定した時だけBlock/Allowの6イベントが有効。切り離し(Block)の3つは列の先頭=ApplicationStopの前、再接続(Allow)の3つは列の末尾
  • エージェントの動き:現在のイベント名をhooksから探し、あればスクリプトを記載順に実行。終了コード0=成功。無ければ次のステップへ

デプロイの骨格は「(LBから外す→)止める→配る→起動→検証(→LBへ戻す)」という固定された縦の列。灰色の箱はCodeDeployが自分で握る予約席で、開発者が触れるのは白い箱=フックだけ。これはStep Functions編で見たFSM(状態遷移の宣言)の実例で、ValidateServiceで失敗すれば先へ進まない——ガード条件付きの状態遷移そのものです。

④ ApplicationStopの罠:「まだ届いていない台本」で止める

初回デプロイインスタンス上にappspecがまだ存在しない
2回目以降前回成功リビジョンのappspec+スクリプトが残っている
前回成功地点の記録を参照
走らない読む台本がない
前回の台本で実行旧appspecのスクリプトで今動いている旧アプリを止める
記録ファイル{{deployment-group-id}}_last_successful_install
前回成功リビジョンの場所を指す
  • 公式:「ApplicationStopで使われるAppSpecファイルとスクリプトは、前回、成功裏にデプロイされたリビジョンのもの」。止めるのは「今動いている古いアプリ」で、その止め方を知っているのは古い台本。新しい台本はまだ配られていない(DownloadBundleはApplicationStopの後)
  • 同じルールはLB切り離しのBeforeBlockTraffic/AfterBlockTrafficにも適用される(公式:この3つのスクリプトは前回成功デプロイのAppSpecから実行され、それ以外はすべて今回のAppSpecから実行される)
  • 公式の注記:インスタンスにデプロイする前はAppSpecファイルが存在しないため、ApplicationStopフックは初回デプロイでは走らない。2回目のデプロイから使える

イベント列は上から順に進むが、ApplicationStop(とBlock系2フック)だけは過去=前回リビジョンの台本で実行される。DownloadBundleより前に位置するから当然で、「新台本で古いアプリを止めようとしても、その台本はまだ手元にない」という時系列の必然。「最後に成功した状態」を記録しておき次の操作の起点にする、追記ログと同じ発想がここにもあります。

⑤ ECSのフックはBlue/Greenの検証ポイント

BeforeInstall置換task set作成の前
この時点ではロールバック不可
テストリスナが新task setへ配信
AfterInstall置換task set作成後
以降、結果がロールバックの引き金になり得る
検証を通ったら本番へ
AfterAllowTestTrafficテストトラフィックで検証
本番トラフィック切り替え
BeforeAllowTraffic本番切り替えの直前に検証
AfterAllowTraffic本番切り替え後の最終検証
CodeDeployへコールバック
  • 各フックは1デプロイにつき1回実行される検証用Lambda関数。ECS(とLambda)ではエージェントが使われないので、フックは「サーバー上のスクリプト」ではなく「呼び出される関数」
  • 検証LambdaはputLifecycleEventHookExecutionStatusでSucceeded/FailedをCodeDeployに返す。Failedは自動ロールバック設定時のロールバックの引き金になり得る

ECSのフックは、コンテナ編で見たBlue/Greenの「新task set(Green)を作る→テスト用トラフィックで検証→本番トラフィックを切り替える」流れのそれぞれの検証ポイントそのもの。AfterAllowTestTrafficが「テストリスナで確かめる関所」、BeforeAllowTrafficが「本番切り替えの直前ゲート」——検証に失敗すれば切り替えず旧task setへ戻す、ガード付き遷移が並んでいます。

⑥ フックのタイムアウトと、EC2の「何台ずつ進めるか」

EC2スクリプトtimeout既定3600秒(1時間)
各イベントの上限でもある。超えるとデプロイ失敗
検証用Lambda1時間以内にコールバック
通知が無ければデプロイ失敗扱い
AllAtOncemin healthy 0・できるだけ一斉
1台でも成功すれば成功(全滅でのみ失敗)
HalfAtATimemin healthy 50%・最大半分(切り捨て)ずつ
半数(切り上げ)以上に配れれば全体成功
OneAtATimemin healthy 1・1台ずつ【既定】
途中1台の失敗で即中止。例外:最後の1台の失敗だけは全体成功のまま
  • ★DVA頻出★ HalfAtATime:9台なら最大4台ずつ配り、5台以上成功で全体成功
  • デプロイ設定を指定しない場合の既定はCodeDeployDefault.OneAtATime
  • EC2の設定は「minimum healthy hosts(最低healthy台数/割合)を通じて、デプロイ中つねに使用可能でなければならないインスタンス数」を規定する仕組み

デプロイ設定は「最低これだけは生かしたまま、何台ずつ入れ替えるか」という安全弁。OneAtATimeが最も慎重(既定)で、AllAtOnceが最速・最も乱暴、HalfAtATimeの「半分配れれば成功」という一見ゆるい成功判定がDVAの定番論点です。そして——EC2は「エージェントがスクリプトで箱の中身を入れ替える」ミュータブルな更新、Lambda/ECSは「新しい実体を作ってトラフィックを切り替える」イミュータブルな更新。appspecの中身の違いがそのまま2つのデプロイ哲学の違いになっている——この対比が次のBeanstalk回への橋になります。

公式ドキュメントで詳しく ↗