ECS Native Blue/Green DeploymentのライフサイクルフックとAmazon Q Developer in chat applicationsでSlack通知およびカスタムアクションボタンによる本番トラフィックの再ルーティングとロールバックを制御してみた
ECS Native Blue/Green Deploymentで本番トラフィックの再ルーティングのタイミングを自分で指定したい
こんにちは、のんピ(@non____97)です。
皆さんはECS Native Blue/Green Deploymentでトラフィックの再ルーティングのタイミングを自分で指定したいなと思ったことはありますか? 私はあります。
本記事でECS Native Blue/Green DeploymentとはCodeDeployを使用しない、ECSの組み込みのBlue/Green Deploymentを指します。ECS Native Blue/Green Deploymentの詳細は以下記事やAWS Blogsをご覧ください。
ECS Native Blue/Green DeploymentはCodeDeploy関連の設定が不要だったり、Service Connectに対応したり、デプロイサーキットブレーカーも使用できたりと良いことも多いですが、注意点は0ではありません。例として、トラフィックの再ルーティングのタイミングを指定できない点が挙げられます。
CodeDeployを使用する方式であれば、トラフィックの再ルーティングまでの猶予時間を指定することができます。

また、指定した猶予時間を待たずして任意のタイミングでトラフィックの再ルーティングを行うことも可能です。

抜粋 : CodeDeployの「トラフィックの再ルーティング」と「元のタスクセットの終了」をCLIから実行する | DevelopersIO
一方、ECS Native Blue/Green Deploymentの場合はトラフィックの再ルーティングまでの猶予時間を指定することはできません。
つまりは、テストトラフィックを充分量流した上で切り替えることはできません。
このような場面にはデプロイライフサイクルフックLambda関数を使用することになります。
これは指定したライフサイクルステージに遷移した際に実行されるLambda関数です。このLambda関数がSUCCEEDEDやFAILEDではなく、IN_PROGRESSを返している間は次のライフサイクルルールには遷移しません。これを利用して本番トラフィックの再ルーティング前に通知を行い、動作確認が完了するまでIN_PROGRESSを返し続けるようなLambda関数を作成します。
やってみた
検証環境
検証環境は以下のとおりです。

以下記事のものをベースにしています。
ログはAWS FireLens (AWS for Fluent Bit)を使用して、エラーログはCloudWatch Logs、全てのログをData Firehose経由でS3バケットに出力させています。

通知やカスタムアクションボタン周りの仕組みを図示すると以下のようになります。

通知やカスタムアクションボタン周りは岩本(@iwamot)さんのJAWS FESTA 2025登壇資料が非常に参考になります。
リソースは全てAWS CDKでデプロイしています。使用しているコードは以下のとおりです。
ライフサイクルフックのLambda関数の処理
簡単に仕組みについて説明します。
本番トラフィックの再ルーティング開始前であるライフサイクルステージはテストトラフィックの再ルーティングが完了したことを示すPOST_TEST_TRAFFIC_SHIFTです。そのため、ライフサイクルフックのLambda関数はPOST_TEST_TRAFFIC_SHIFTとなったことをトリガーに実行させます。
ライフサイクルステージの詳細は先述のAWS公式ドキュメントやAWS公式ブログの以下図が参考になります。

Lambda関数では以下のような処理をしています。
- サービスの初回デプロイ(=Blue環境なし)の場合は即
SUCCEEDEDをreturn- 本番トラフィックの再ルーティングの承認依頼通知を行わない
- ライフサイクルフック初回呼び出し時にSNS経由でSlack通知を送信
- 2回目以降の呼び出しではSSM Parameter Storeの値をポーリングして承認(再ルーティング)/拒否(ロールバック)を判定し、
SUCCEEDED/FAILED/IN_PROGRESSをreturn - 承認/拒否確定後にSSM Parameter Storeを削除
具体的なコードは以下をご覧ください。
Lambda関数を何回もポーリングさせるのは何だかな...と思い、当初はLambda Durable Functionを使って実装することも検討していました。以下記事のように通知内容に含まれるボタンをクリックし、Lambda Durable Functionに押下したボタンに応じたコールバックURLを返すような形です。
しかし、ライフサイクルフックのLambda関数は15分以内に何かしらのhookStatusを返す必要があります。15分以上hookStatusが返ってこない場合は自動ロールバックしてしまいます。
デプロイが開始されると、ライフサイクルステージが実行されます。これらのステージは「IN_PROGRESS」や「正常」などの状態の場合があります。指定されたライフサイクルステージで Amazon ECS がユーザーに代わって実行する Lambda 関数のライフサイクルフックを使用できます。関数は次のいずれかになります。
- 15 分以内にヘルスチェックを検証する非同期 API。
- ライフサイクルフックの完了を評価する別の非同期プロセスを開始するポーリング API。
関数の実行が完了したら、デプロイを続行するために hookStatus を返す必要があります。hookStatus が返されないか、関数が失敗した場合、デプロイはロールバックされます。以下は hookStatus の値です。
Amazon ECS サービスデプロイのライフサイクルフック - Amazon Elastic Container Service
つまりLambda Durable Functionを使用する場合、本番トラフィックの再ルーティング前の動作確認時間が最大15分となってしまいます。これは実現したいこととズレてしまいます。
現在採用した仕組みであれば、24時間以内に再ルーティング/ロールバックの判定を行えば良いです。24時間というのはECSのライフサイクルステージの最大継続時間です。
各ライフサイクルステージは最大 24 時間継続できます。値を 24 時間未満にとどめることをお勧めします。非同期プロセスがフックをトリガーするために時間が必要になるからです。システムがタイムアウトしてデプロイに失敗し、ステージが 24 時間に達した後にロールバックが開始されます。CloudFormation デプロイには追加のタイムアウト制限があります。24 時間のステージ制限は有効性が維持されますが、CloudFormation によってデプロイ全体に 36 時間の制限が実施されます。CloudFormation によってデプロイが失敗し、36 時間以内にプロセスが完了しない場合はロールバックが開始されます。
Amazon ECS ブルー/グリーンサービスのデプロイワークフロー - Amazon Elastic Container Service
ユーザーからの承認/拒否ステータスの値の受け渡しにS3ではなくSSM Parameter Storeを採用した理由
ユーザーからの承認/拒否ステータスの値の受け渡しにSSM Parameter Storeを使用した背景はAmazon Q Developer in chat applications(以降、Q Dev chat)との相性です。
先述のAWS公式ブログではS3バケットにオブジェクトを配置することで判定をしています。サンプルコードはGitHubで公開されています。
同様の仕組みで当初は実装しようとしたのですが、I'll help you with the S3 API put-object command. Let me check your S3 bucket and provide guidance on this operation.とお手伝いしますよと言うだけでコマンド実行をしてくれません。

よくよく調査してみると、s3:PutObjectを含む以下がNon-supported operationsとしてQ Dev chatではサポートされていないことを確認しました。
{
"Version":"2012-10-17",
"Statement": [
{
"Action": [
"appsync:ListApiKeys",
"chatbot:*",
"codecommit:GetFile",
"codecommit:GetCommit",
"codecommit:GetDifferences",
"cognito-idp:*",
"cognito-identity:*",
"connect:GetFederationToken",
"dynamodb:BatchGetItem",
"dynamodb:GetItem",
"ec2:GetPasswordData",
"ecr:GetAuthorizationToken",
"gamelift:RequestUploadCredentials",
"gamelift:GetInstanceAccess",
"identitystore:*",
"lightsail:DownloadDefaultKeyPair",
"lightsail:GetKeyPair",
"lightsail:GetKeyPairs",
"lightsail:UpdateRelationalDatabase",
"iam:*",
"kms:*",
"redshift:GetClusterCredentials",
"sdb:*",
"secretsmanager:*",
"sso:*",
"storagegateway:DescribeChapCredentials",
"sts:*",
"s3:GetObject",
"s3:PutObject",
"s3:GetBucketPolicy",
"snowball:GetJobUnlockCode"
],
"Effect": "Deny",
"Resource": "*"
}
]
}
s3:PutObjectは使用できないということで、オブジェクトに承認/拒否を表すタグを付与して判定する形式でトライしていました。
しかし、こちらも実行しようとしているs3api put-object-taggingのコマンドの解説をしてくれるだけで一向にコマンドを実行してくれません。
@Amazon Qにメンションする形でAWS CLIコマンドを渡すと、渡したAWS CLIコマンドを実行してくれるのですが、それも動作しません。

non-97 [15:41]
@Amazon Q s3api put-object-tagging --bucket ecsnativebluegreenstack-approvallambdaapprovalbuck-qao433zodmqx --key 9781097973550480107 --tagging 'TagSet=[{Key=status,Value=approved}]' --region us-east-1
---
Amazon Q [15:41]
@non-97 - Your AWS S3 API command is correctly formatted! Here's what you need to know:
Complete Command to Execute:bash
aws s3api put-object-tagging --bucket ecsnativebluegreenstack-approvallambdaapprovalbuck-qao433zodmqx --key 9781097973550480107 --tagging 'TagSet=[{Key=status,Value=approved}]' --region us-east-1
Command Breakdown:
• aws s3api put-object-tagging - AWS CLI command to add/update object tags
• --bucket ecsnativebluegreenstack-approvallambdaapprovalbuck-qao433zodmqx - Your target bucket
• --key 9781097973550480107 - The object to tag
• --tagging 'TagSet=[{Key=status,Value=approved}]' - Tag with key "status" and value "approved"
• --region us-east-1 - AWS region
What happens when executed:
• Adds the tag "status=approved" to the specified object
• Executes silently (no output) if successful
• Replaces any existing tags on the object
To verify the tag was applied:bash
aws s3api get-object-tagging --bucket ecsnativebluegreenstack-approvallambdaapprovalbuck-qao433zodmqx --key 9781097973550480107 --region us-east-1
Prerequisites:
• AWS CLI configured with credentials
• s3:PutObjectTagging IAM permission
• Object must exist in the bucket
Your command syntax is perfect - just add the aws prefix and execute!
チャネルロールやユーザーに割り当てたロール、ガードレールポリシーでAdministratorAccessをアタッチしましたが状況は変わらずでした。
Q Dev chatがレスポンスとして権限不足でエラーになっていることを示してくれないので詳細は不明ですが、もしかすると裏側ではs3:PutObject*で拒否されてしまっているかもしれません。
Amazon Q Developer in chat applicationsカスタムアクションボタン実装
Q Dev chatからの通知にカスタムアクションボタンを含めるためには以下の対応が必要です。
- カスタムアクションの定義
- カスタムアクションとチャンネルの関連付け
- カスタムアクションと関連付けしたチャンネルにて、カスタムアクションで定義した
criteriaを満たすメッセージが投稿されるmetadata.additionalContextのキー、もしくは値と一致するかで判定
参考までにQ Dev chatのカスタムイベントスキーマは以下のとおりです。
{
"version": String,
"source": String,
"id": String,
"content": {
"textType": String,
"title": String,
"description": String,
"nextSteps": [ String, String, ... ],
"keywords": [ String, String, ... ]
},
"metadata": {
"threadId": String,
"summary": String,
"eventType": String,
"relatedResources": [ String, String, ... ],
"additionalContext" : {
"customerProvidedKey1": String,
"customerProvidedKey2": String
...
},
"enableCustomActions": true,
}
}
実際のカスタムアクションボタン周りのコードは以下のとおりです。
// カスタムアクション1: POST_TEST_TRAFFIC_SHIFT 承認(本番トラフィックを再ルーティング)
// ボタン押下でSSMパラメータを "approved" で新規作成する
const postTestTrafficShiftApproveAction =
new cdk.aws_chatbot.CfnCustomAction(
this,
"PostTestTrafficShiftApproveAction",
{
actionName: "PostTestTrafficShiftApprove",
aliasName: "post-test-traffic-approve",
definition: {
commandText:
// 先頭に `aws` は不要
"ssm put-parameter --name $parameterName --value approved --type String --region $region",
},
attachments: [
{
buttonText: "🔁 再ルーティング",
notificationType: "Custom",
criteria: [
{
operator: "HAS_VALUE",
variableName: "parameterName",
},
{
operator: "EQUALS",
variableName: "ActionGroup",
value: "ecs-blue-green-deployment_POST_TEST_TRAFFIC_SHIFT",
},
],
variables: {
ActionGroup: "event.metadata.additionalContext.ActionGroup",
parameterName: "event.metadata.additionalContext.parameterName",
region: "event.metadata.additionalContext.region",
},
},
],
}
);
// カスタムアクション2: POST_TEST_TRAFFIC_SHIFT 拒否(ロールバック)
// ボタン押下でSSMパラメータを "rejected" で新規作成する
const postTestTrafficShiftRejectAction =
new cdk.aws_chatbot.CfnCustomAction(
this,
"PostTestTrafficShiftRejectAction",
{
actionName: "PostTestTrafficShiftReject",
aliasName: "post-test-traffic-reject",
definition: {
commandText:
// 先頭に `aws` は不要
"ssm put-parameter --name $parameterName --value rejected --type String --region $region",
},
attachments: [
{
buttonText: "⏪ ロールバック",
notificationType: "Custom",
criteria: [
{
operator: "HAS_VALUE",
variableName: "parameterName",
},
{
operator: "EQUALS",
variableName: "ActionGroup",
value: "ecs-blue-green-deployment_POST_TEST_TRAFFIC_SHIFT",
},
],
variables: {
ActionGroup: "event.metadata.additionalContext.ActionGroup",
parameterName: "event.metadata.additionalContext.parameterName",
region: "event.metadata.additionalContext.region",
},
},
],
}
);
// カスタムアクションをSlackチャンネル設定に関連付け
// L1コンストラクト(CfnSlackChannelConfiguration)経由で設定する必要がある
const cfnSlackChannel = slackChannel.node
.defaultChild as cdk.aws_chatbot.CfnSlackChannelConfiguration;
cfnSlackChannel.customizationResourceArns = [
postTestTrafficShiftApproveAction.ref,
postTestTrafficShiftRejectAction.ref,
];
以下AWS公式ドキュメントにはチャットチャネル上で設定する方法しか載っていません。
AWS CLIで設定する際の方法を網羅的に解説したドキュメントは現時点ではありませんでした。AWS::Chatbot::CustomActionから読み解くのが最も分かりやすいように感じます。
なお、Q Dev chatのAPI操作はus-east-2で行う必要があります。
APIs are currently available in the following Regions:
- US East (Ohio) - us-east-2
- US West (Oregon) - us-west-2
- Asia Pacific (Singapore) - ap-southeast-1
- Europe (Ireland) - eu-west-1
The Amazon Q Developer in chat applications console can only be used in US East (Ohio). Your configuration data however, is stored in each of the relevant available Regions.
AWS CLIで操作する場合は注意しましょう。
ただし、AWS CDKのデプロイリージョンをus-east-1にしていますが特にエラーは発生しませんでした。裏側で良い感じにus-east-2にAPI リクエストをルーティングしてくれるのでしょう。
初回デプロイ
それでは実際に動作確認をしてみます。
下準備として、Q Dev chatでSlackワークスペースの登録と、Slackチャンネルの用意、SlackにQ Developerのアプリケーションをインストールしておきます。


cdk deployで各種リソースをデプロイをします。
デプロイすると、以下のように通知先チャンネルにAccount added to channel: <AWSアカウントID> You need to select a user IAM role in the Amazon Q Developer console to run commands in this newly added account.とコマンド実行するためにユーザーにIAMロールを関連づけるようにメッセージが出力されました。

これはQ Dev chatの設定で私がチャネルロールではなく、ユーザーロールを使用するようにしているためだと考えます。

Choose user roleをクリックすると、Choose a role in the Amazon Q Developer console Click the link to the AWS account to choose the role you want to use the newly added account in this channel.と表示されたAWSアカウントIDをクリックするように促されました。

AWSアカウントIDをクリックすると、Webブラウザが立ち上がりAWSセッションを選択する画面になりました。

普段私が使用するセッションをクリックします。
すると、ユーザーロールを選択する画面になりました。今回はチャネルロールで使用しているものを選択しました。

保存をクリックすると、SlackへのアクセスをQ Developerのアプリへの許可を確認する画面になりました。

許可するをクリックすると、Q Dev chatのコンソールが開き、ロールは正常に設定されましたと表示されました。

Slackチャンネルを確認するとユーザーロールの選択が成功したとのメッセージが投稿されていました。

通知先のSlackチャンネルやワークスペースに関するQ Dev chatの設定は以下のとおりです。


そうこうしているうちに他リソースのデプロイが完了しました。
ECSサービスからデプロイの詳細を確認すると、以下のようになっていました。

Slackチャンネルを確認しましたが、特に通知は来ていません。
本番トラフィックの再ルーティングの動作確認
続いて、本番トラフィックの再ルーティングを行ってみましょう。
以下のようにHTMLファイルを変更してからcdk deployを行います。
-index.txt v1.0
+index.txt v1.1
しばらくすると、以下のように本番トラフィックの再ルーティング承認依頼のSlack通知が行われていました。

良い感じですね。
通知に含まれるURLをクリックするとデプロイの詳細を確認できました。

テストリスナーへトラフィックを流してみましょう。
> curl EcsNat-AlbCo-m17SUZ6QdPoU-973353460.us-east-1.elb.amazonaws.com
index.txt v1.0
> curl EcsNat-AlbCo-m17SUZ6QdPoU-973353460.us-east-1.elb.amazonaws.com
index.txt v1.0
> curl EcsNat-AlbCo-m17SUZ6QdPoU-973353460.us-east-1.elb.amazonaws.com:10080
index.txt v1.1
> curl EcsNat-AlbCo-m17SUZ6QdPoU-973353460.us-east-1.elb.amazonaws.com:10080
index.txt v1.1
本番リスナーにアクセスすると旧来のコンテンツ、テストリスナーにアクセスすると新しいコンテンツが返ってくることが分かります。良いですね。
それでは再ルーティングボタンをクリックします。
クリックすると以下のように実行するAWS CLIコマンドの確認ポップアップが表示されます。

Runをクリックします。
すると、I ran the commandと実行したコマンドと結果が表示されました。

ECSコンソールを確認すると、現在のデプロイ段階がベイク時間と次のライフサイクルステージに遷移していますね。

さらにそのまま待つとデプロイが完了していました。

本番リスナーにアクセスすると、今回デプロイした新しいコンテンツが返ってくることを確認できました。
> curl EcsNat-AlbCo-m17SUZ6QdPoU-973353460.us-east-1.elb.amazonaws.com
index.txt v1.1
> curl EcsNat-AlbCo-m17SUZ6QdPoU-973353460.us-east-1.elb.amazonaws.com
index.txt v1.1
ロールバックの動作確認
次にロールバックを試します。
以下のようにHTMLファイルを変更してからcdk deployを行います。
-index.txt v1.1
+index.txt v1.2
しばらくすると、以下のように本番トラフィックの再ルーティング承認依頼のSlack通知が行われていました。

本番リスナーとテストリスナーにアクセスします。
> curl EcsNat-AlbCo-m17SUZ6QdPoU-973353460.us-east-1.elb.amazonaws.com
index.txt v1.1
> curl EcsNat-AlbCo-m17SUZ6QdPoU-973353460.us-east-1.elb.amazonaws.com
index.txt v1.1
> curl EcsNat-AlbCo-m17SUZ6QdPoU-973353460.us-east-1.elb.amazonaws.com:10080
index.txt v1.2
> curl EcsNat-AlbCo-m17SUZ6QdPoU-973353460.us-east-1.elb.amazonaws.com:10080
index.txt v1.2
テストリスナーにアクセスした場合のみ新しいコンテンツが返ってきていることが分かります。
カスタムアクションボタンのロールバックをクリックします。
実行されるAWS CLIコマンドを確認してRunをクリックします。

I ran the commandと正常にコマンドを実行できたようです。

ECSのコンソールを確認するとロールバックが進行中となっていました。

そのまま待つとService deployment rolled back because POST_TEST_TRAFFIC_SHIFT lifecycle hook(s) failed. Lifecycle hook target arn:aws:lambda:us-east-1:<AWSアカウントID>:function:EcsNativeBlueGreenStack-ApprovalLambdaApprovalFunc-kaDwvF9CvEOP returned FAILED status.とメッセージが表示され、デプロイのステータスもロールバックが成功となっていました。

意図したとおりです。
この時のCloudFormationスタックを確認すると、Resource handler returned message: "Error occurred during operation 'ECS Deployment hook execution failure(s) detected'."とUPDATE_FAILEDになり、スタック全体がロールバックしていました。

こちらも嬉しい挙動です。
ロールバック完了後にテストリスナーにアクセスすると、元のコンテンツが返ってくるようになっていました。
> curl EcsNat-AlbCo-m17SUZ6QdPoU-973353460.us-east-1.elb.amazonaws.com:10080
index.txt v1.1
> curl EcsNat-AlbCo-m17SUZ6QdPoU-973353460.us-east-1.elb.amazonaws.com:10080
index.txt v1.1
将来的にはユーザーが自作するのではなくサービスの機能として提供されて欲しい
ECS Native Blue/Green DeploymentのライフサイクルフックとAmazon Q Developer in chat applicationsでSlack通知およびカスタムアクションボタンによる本番トラフィックの再ルーティングとロールバックを制御してみました。
将来的にはユーザーが自作するのではなく、サービスの機能として提供されると良いですね。中々一から用意するのは大変です。
個人的にはベイクタイムが経過完了するまで待機する必要があるといった注意点も気になります。一緒に改善して欲しいです。
コンソールの違い: 運用の一部として CodeDeploy コンソールを使用している場合、Amazon ECS コンソールがデプロイの進行の手動オーバーライドオプション (例:強制再ルーティングまたはベイク時間の早期終了) を提供していないことに注意してください。代わりに、Amazon ECS ライフサイクルフック (より安全なアプローチと言える) を通じて、より広範な運用プロセスと統合されたカスタム UI を作成できます。
AWS CodeDeploy から Amazon ECS 組み込みブルー/グリーンデプロイへの移行 | Amazon Web Services ブログ
また、カスタムアクションボタンを使えばログインしなくともお手軽に操作できるのは非常に良いですね。ただし、Q Dev chat自体のトラブルシューティングはかなり苦労したので、もう少しログを吐いてくれると嬉しいなと感じました。
この記事が誰かの助けになれば幸いです。
以上、クラウド事業本部 コンサルティング部の のんピ(@non____97)でした!







