CodeDeployのApplicationStopイベントフックはどう実行される?

AWS CodeDeploy

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

AWSのPULL型デプロイサービスCodeDeployはデプロイの各ライフサイクルイベントに処理をフックすることができます。

デプロイは

  1. アプリケーションの停止
  2. アプリケーションファイルのダウンロード
  3. アプリケーションファイルのインストール
  4. アプリケーションの起動

という順で実行され、 次のデプロイライフサイクル図において、オレンジ背景の箇所が実際にフック処理を割り込めるイベントです。

codedeploy_app_hooks

イベント名から各イベントで何をやっているか想像はつきますが、ApplicationStopイベントだけは注意が必要なため、補足します。

ApplicationStop イベントフックはどう実行される?

ライフサイクルイベントのフローを見ればわかるように、ApplicationStop イベントは DownloadBundle イベントよりも前に存在します。

そのため、ApplicationStop イベントでは前回正常にデプロイされたリビジョンの AppSpec ファイルと ApplicationStop スクリプトを元に実行されます。

新規にデプロイするファイル群で定義した ApplicationStop 処理は、次回デプロイ時の ApplicationStop イベントまで実行されません。

デプロイするファイル群はStartからEndまでのデプロイのライフサイクルに対応するわけではなく、 DownloadBundleからApplicationStopまでのアプリケーションのライフサイクルに対応します。

注意しましょう。

codedeploy-bundle-and-lifecycle

CodeDeploy FAQにあるデプロイのライフサイクルイベントの説明を転載します。

デプロイのライフサイクルイベント 説明
ApplicationStop デプロイの最初のライフサイクルイベントであり、リビジョンのダウンロード前から開始します。このデプロイのライフサイクルイベントに使用される AppSpec ファイルおよびスクリプトは前回正常にデプロイされたリビジョンのものです。

アプリケーションを正常に停止する、またはデプロイの準備として最近インストールしたパッケージを削除する場合は ApplicationStop のデプロイのライフサイクルイベントを使用します。

DownloadBundle このデプロイのライフサイクルイベントでは、エージェントがインスタンスの一時的なロケーションに、リビジョンファイルをコピーします。このデプロイのライフサイクルイベントはエージェント用にリザーブされており、ユーザーのスクリプトの実行には使用できません。
BeforeInstall BeforeInstall ライフサイクルイベントはファイルの暗号化や現在のバージョンのバックアップ作成など、インストール前のタスクに使用します。
Install このデプロイのライフサイクルイベントでは、エージェントが一時的なロケーションからリビジョンファイルを最終的な送信先フォルダにコピーします。このデプロイのライフサイクルイベントはエージェント用にリザーブされており、ユーザーのスクリプトの実行には使用できません。
AfterInstall AfterInstall デプロイライフサイクルイベントはアプリケーションの設定またはファイルの許可の変更に使用します。
ApplicationStart 基本的に、このデプロイのライフサイクルイベントは ApplicationStop で停止したサービスを再起動するために使用します。
ValidateService ValidateService は最後のデプロイのライフサイクルイベントであり、デプロイが正常に完了したことを検証します。

ApplicationStop処理が原因でデプロイが失敗する!

ApplicationStopイベントのエラーが原因で新規デプロイが失敗する時はどうすればよいでしょうか?

前述のとおり、ApplicationStopイベントではデプロイ済みファイルが利用されます。

以下の2通りの対応があります。

  1. ApplicationStop イベントの処理を書き換える
  2. ApplicationStop イベントが失敗しても無視する

順に見ていきましょう。

1. ApplicationStop イベントの処理を書き換える

力ずくでデプロイ済みのファイルを書き換えます。

前回デプロイしたファイル群のパスは、次のファイルで確認できます。

/opt/codedeploy-agent/deployment-root/deployment-instructions/DEPLOYMENT-GROUP-ID_last_successful_install

手元の環境で確認すると次の様になっていました。

$ cat /opt/codedeploy-agent/deployment-root/deployment-instructions/706529a7-bef8-4f84-9fd6-3ba8994687ad_last_successful_install
/opt/codedeploy-agent/deployment-root/706529a7-bef8-4f84-9fd6-3ba8994687ad/d-DLK7M2IBE
$ tree /opt/codedeploy-agent/deployment-root/706529a7-bef8-4f84-9fd6-3ba8994687ad/d-DLK7M2IBE
/opt/codedeploy-agent/deployment-root/706529a7-bef8-4f84-9fd6-3ba8994687ad/d-DLK7M2IBE
├── bundle.tar
├── deployment-archive
│   ├── appspec.yml
│   └── scripts
│       └── application_stop
└── logs
    └── scripts.log

3 directories, 4 files

あとは該当ファイルを修正するだけです。

きめ細かな修正ができる一方で、サーバー台数がたくさんあると、このアプローチは厄介です。

2. ApplicationStop イベントが失敗しても無視する

デプロイ(aws deploy create-deployment)時に--ignore-application-stop-failuresスイッチを渡し、ApplicationStop イベントで失敗しても、処理を継続させます。

エラーの発生するApplicationStopスクリプトをデプロイした上で、新規にデプロイを実行して実際の動作を確認します。

$ aws deploy create-deployment \
    --ignore-application-stop-failures \
    --application-name foo \
    --deployment-config-name CodeDeployDefault.OneAtATime \
    --deployment-group-name foo \
    --description "zip" \
    --s3-location bucket=YOUR-BUCKET,bundleType=zip,key=cd/03.zip
{
    "deploymentId": "d-DLK7M2IBE"
}

デプロイの詳細を確認します。

$ aws deploy get-deployment-instance --deployment-id d-DLK7M2IBE --instance-id i-876fc318
{
    "instanceSummary": {
        "instanceId": "arn:aws:ec2:ap-northeast-1:123456789012:instance/i-876fc318",
        "status": "Succeeded",
        "deploymentId": "d-DLK7M2IBE",
        "lastUpdatedAt": 1460204425.313,
        "lifecycleEvents": [
            {
                "endTime": 1460204413.889,
                "status": "Failed",
                "diagnostics": {
                    "errorCode": "ScriptFailed",
                    "scriptName": "scripts/application_stop",
                    "logTail": "LifecycleEvent - ApplicationStop\nScript - scripts/application_stop\n[stderr]/opt/codedeploy-agent/deployment-root/706529a7-bef8-4f84-9fd6-3ba8994687
ad/d-VAOLFRQBE/deployment-archive/scripts/application_stop: line 2: fail: command not found\n",
                    "message": "Script at specified location: scripts/application_stop failed with exit code 127"
                },
                "startTime": 1460204413.773,
                "lifecycleEventName": "ApplicationStop"
            },
            ...
            {
                "endTime": 1460204420.446,
                "status": "Succeeded",
                "diagnostics": {
                    "errorCode": "Success",
                    "scriptName": "",
                    "logTail": "",
                    "message": "Succeeded"
                },
                "startTime": 1460204420.324,
                "lifecycleEventName": "ApplicationStart"
            },
            {
                "endTime": 1460204423.768,
                "status": "Succeeded",
                "diagnostics": {
                    "errorCode": "Success",
                    "scriptName": "",
                    "logTail": "",
                    "message": "Succeeded"
                },
                "startTime": 1460204423.634,
                "lifecycleEventName": "ValidateService"
            }
        ]
    }
}

lifecycleEventsApplicationStopイベントはstatusFailedですが(エラー内容はlogTail から確認できます)、 後続のイベントはSucceededで成功し、 デプロイそのものもstatusSucceededと成功しています。

注意点としては

  • マネージメントコンソールからデプロイする時は--ignore-application-stop-failuresスイッチを指定できない
  • ApplicationStop処理をまるごとスキップするわけではない

といったことがあります。

まとめ

今回はAWS CodeDeployを使った人が一度はハマると思われるApplicationStopイベントについて解説しました。

バンドルファイルが展開されるタイミングと各イベントで利用されるバンドルファイルをしっかり理解しましょう。

参考