CodeDeploy を用いた ECS B/G デプロイのハマりどころ
対象読者
- BlueGreen デプロイ を CFn(aws cdk) で実装したい
- rolling update で稼働中の ECS サービスを B/G デプロイに変更したい
- Codepipeline を利用した ECS の B/G デプロイを実装したい
概要
aws cdk で構築した ECS(Fargate) を rolling update から B/G デプロイに切り替えようとした際に発生した問題について記載しています
誤りや、より良い実装方法があればコメントで教えていただけると嬉しいです
発生した問題
稼働中のサービスの Deployment Controller を変更しようとしてエラー
Pipeline 実装後、まず単純に稼働中サービスの Deployment Controller を変更しようとしたところ以下エラーになりました
const service = new ecs.FargateService(this, "fargateService", {
serviceName: 'srv',
cluster,
taskDefinition,
deploymentController: {
- type: ecs.DeploymentControllerType.ECS,
+ type: ecs.DeploymentControllerType.CODE_DEPLOY
},
});
発生したエラー
Resource handler returned message: "Resource of type 'AWS::ECS::Service' with identifier 'srv' already exists."
サービスの変更ではなく新規サービスの作成として扱われてしまうようで、稼働中のサービスをそのまま変更できませんでした
そのため対応したケースでは別途 B/G デプロイ用のサービスをもう1つ立ち上げ、メンテなどのアクセスが無いタイミングで ALB の通信先を切り替える形としました
論理 ID を指定したオーバーライドで行けるのかもしれないですが、時間が取れず試せていません
Deployment Controller が CodeDeploy の場合 ALB の操作が cdk から行なえない
前述のエラー対策として B/G デプロイ用のサービスを立ち上げ、ALB の通信先の切り替えを検証していたのですが以下のエラーでした
Deployment group’s ECS service must be configured for a CODE_DEPLOY deployment controller
CodeDeploy によるデプロイ制御する場合、ALB や Target Group などの変更は CodeDeploy から行なう必要があり、cdk(CFn) からは変更できませんでした
そのため今回の対応ではダウンタイム(2〜30秒程度)を許容し、メンテ中にサービス自体を B/G 対応のサービスに再作成する方法で対応しました
サービスが複数ポートを公開している場合、Blue/Greenに設定しているターゲットグループのポートのみデプロイ時に紐づく
// task definition
const td = new ecs.FargateTaskDefinition(this, "TaskDefinition", {
// 省略
});
td.addContainer("api", {
portMappings: [
{
containerPort: 8080,
},
{
containerPort: 8081,
},
],
});
// deproyment group
const greenTg = new elb.ApplicationTargetGroup(
this,
"GreenTargetGroup",
{
targetGroupName: "GreenTargetGroup",
port: 8080,
},
);
const blueTg = new elb.ApplicationTargetGroup(
this,
"BlueTargetGroup",
{
targetGroupName: "BlueTargetGroup",
port: 8080,
},
);
const deploymentGroup = new EcsDeploymentGroup(
this,
"DeploymentGroup",
{
application: new EcsApplication(this, "BlueGreenApplication", {}),
deploymentGroupName: "deployment-group",
service: srv,
blueGreenDeploymentConfig: {
blueTargetGroup: greenTg,
greenTargetGroup: blueTg,
listener,
},
deploymentConfig: EcsDeploymentConfig.ALL_AT_ONCE,
},
);
上記のようにサービスは 8080/8081 ポートを公開していますが、deployment group に指定できるBlue/Green ターゲットグループは一種類のため、8080/8081 どちらかのポートしか指定できませんでした。
そのため、以下のように pipeline の action で ALB と紐づけることで対策しました
# target group の arn を取得
aws elbv2 describe-target-groups \
--names "TargetGroup Name" \
--query 'TargetGroups[0].TargetGroupArn' \
--output text
arn:aws:elasticloadbalancing:$region:$accountId:targetgroup/$targetGroupName/abc123
# 対象タスクの IP を取得
aws ecs describe-tasks \
--cluster $ecsClusterName
--tasks "arn:aws:ecs:us-west-1:$accountId:task/$clusterName/abc123" \
--query "tasks[0].attachments[0].details[?name=='privateIPv4Address'].value" \
--output text
10.0.123.456
# コンテナのポートと紐づけ
aws elbv2 register-targets \
--target-group-arn "arn:aws:elasticloadbalancing:$region:$accountId:targetgroup/$targetGroupName/abc123" \
--targets "Id=10.0.123.456,Port=$targetContainerPort"
1サービスが複数イメージを使用している場合 imageDetail.json が使えない
const td = new ecs.FargateTaskDefinition(this, "TaskDefinition", {});
td.addContainer("api", {
image: apiImage,
portMappings: [
{
containerPort: 80,
},
],
});
td.addContainer("client", {
image: clientImage,
portMappings: [
{
containerPort: 81,
},
],
});
このような形で1サービス内で複数のコンテナイメージを参照する必要があったのですがimagedefinitions.json
であれば複数イメージを指定できるのですが、imageDetail.json
の場合は単一のイメージしか指定できず、今回のように1サービス内で複数のコンテナを稼働させる場合は使えませんでした
そのため、imageDetail.json
は使用せず taskdef.json
を pipeline 内で作成することで対応しました
まとめ
ドキュメントを読む限り CodeDeploy を利用した B/G デプロイは簡単な印象ですが、自分のように推奨とは異なる構成の場合はドキュメントどおり進められず大変でした
AWS の推奨する構成の場合はドキュメントどおりに実装を進めることが可能で、シンプルで効率よく開発が進められるのかと思いますが、事情により推奨する構成にできないこともあると思いますので困っている方のヒントになれば幸いです
Discussion