CDKで作成したECSサービスのデプロイ戦略をブルー/グリーンに切り替えてみた
CDKで作成したECSサービスのデプロイ戦略を、デフォルトのローリングアップデートからAmazon ECS ブルー/グリーンデプロイメントに切り替えてみました。
CDK L2 Constructでサポートされているため、比較的少ない変更で切り替えできます。
※本記事でのブルー/グリーンデプロイメントは、CodeDeployを使わないECSネイティブの方式を指します。公式ドキュメントの記載に合わせて「Amazon ECS ブルー/グリーンデプロイメント」と記載します
Amazon ECS ブルー/グリーンデプロイメントとは
Amazon ECS ブルー/グリーンデプロイメントは、ECSがネイティブにサポートするデプロイ戦略です。
デプロイ時に新しいタスクセット(グリーン)を既存のタスクセット(ブルー)と並行して起動し、ALBのリスナールールを使ってトラフィックを切り替えます。
ECS自身がトラフィックの切り替えを管理するため、シンプルな構成でブルー/グリーンデプロイメントを実現できます。
切り替えに必要な変更点
ローリングアップデート(デフォルト)からブルー/グリーンに切り替えるには、以下の変更が必要です。
| 項目 | 説明 |
|---|---|
deploymentStrategy |
ecs.DeploymentStrategy.BLUE_GREEN を指定 |
| グリーン用ターゲットグループ | ブルー用に加え、グリーン用のターゲットグループを追加 |
| テスト用リスナー(オプション) | 本番用リスナーに加え、テスト用リスナーを追加。デプロイ中にグリーン環境を別ポートで事前確認する場合に使用 |
| リスナーのデフォルトアクション | リスナールールでルーティングを行うため、リスナーのデフォルトアクションを固定レスポンスに変更 |
| リスナールール | リスナーにリスナールールを作成 |
| ECSインフラストラクチャロール | AmazonECSInfrastructureRolePolicyForLoadBalancers ポリシーを付与したIAMロール(CDK L2が自動作成) |
alternateTarget |
loadBalancerTarget にグリーンターゲットグループとリスナールールを設定 |
やってみる
ローリングアップデートで動作しているECSサービスを、ブルー/グリーンデプロイに切り替えます。
コード全体を確認したい場合は、以下のGitHubリポジトリをご確認ください。
変更前: ローリングアップデート
まず、変更前のCDKコード(ローリングアップデート)を示します。
ALBとECSサービスの部分を抜粋します。
// ALB
const alb = new elbv2.ApplicationLoadBalancer(this, 'ALB', {
vpc,
internetFacing: true,
});
const listener = alb.addListener('Listener', {
port: 80,
});
// ECS Service
const service = new ecs.FargateService(this, 'Service', {
cluster,
taskDefinition,
desiredCount: 1,
});
listener.addTargets('ECSTarget', {
port: 80,
targets: [
service.loadBalancerTarget({
containerName: 'nginx',
containerPort: 80,
}),
],
healthCheck: {
path: '/',
},
});
この構成では、デプロイ時にローリングアップデートが実行されます。

変更後: ブルー/グリーンデプロイメント
以下がブルー/グリーンに切り替えた後のコードです。
// ALB
const alb = new elbv2.ApplicationLoadBalancer(this, 'ALB', {
vpc,
internetFacing: true,
});
// 本番用リスナー
const prodListener = alb.addListener('Listener', {
port: 80,
defaultAction: elbv2.ListenerAction.fixedResponse(404),
});
// テスト用リスナー(オプション)
const testListener = alb.addListener('TestListener', {
port: 8080,
defaultAction: elbv2.ListenerAction.fixedResponse(404),
});
// ブルー用ターゲットグループ
const blueTargetGroup = new elbv2.ApplicationTargetGroup(this, 'BlueTargetGroup', {
vpc,
port: 80,
protocol: elbv2.ApplicationProtocol.HTTP,
targetType: elbv2.TargetType.IP,
healthCheck: {
path: '/',
},
});
// グリーン用ターゲットグループ
const greenTargetGroup = new elbv2.ApplicationTargetGroup(this, 'GreenTargetGroup', {
vpc,
port: 80,
protocol: elbv2.ApplicationProtocol.HTTP,
targetType: elbv2.TargetType.IP,
healthCheck: {
path: '/',
},
});
// 本番用リスナールール
const prodListenerRule = new elbv2.ApplicationListenerRule(this, 'ProdListenerRule', {
listener: prodListener,
priority: 1,
conditions: [elbv2.ListenerCondition.pathPatterns(['*'])],
targetGroups: [blueTargetGroup],
});
// テスト用リスナールール(オプション)
new elbv2.ApplicationListenerRule(this, 'TestListenerRule', {
listener: testListener,
priority: 1,
conditions: [elbv2.ListenerCondition.pathPatterns(['*'])],
targetGroups: [blueTargetGroup],
});
// ECS Service
const service = new ecs.FargateService(this, 'Service', {
cluster,
taskDefinition,
desiredCount: 1,
deploymentStrategy: ecs.DeploymentStrategy.BLUE_GREEN,
});
const target = service.loadBalancerTarget({
containerName: 'nginx',
containerPort: 80,
protocol: ecs.Protocol.TCP,
alternateTarget: new ecs.AlternateTarget('AlternateTarget', {
alternateTargetGroup: greenTargetGroup,
productionListener: ecs.ListenerRuleConfiguration.applicationListenerRule(prodListenerRule),
}),
});
target.attachToApplicationTargetGroup(blueTargetGroup);
主な変更点は以下の通りです。
- リスナーのデフォルトアクション追加: リスナールールでルーティングするため、
defaultActionに固定レスポンス(404)を設定 - テスト用リスナーの追加(オプション): ポート8080でテスト用リスナーを追加。デプロイ中にグリーン環境を事前確認する場合に使用
- ターゲットグループの分離: ブルー用とグリーン用の2つのターゲットグループを明示的に作成
- リスナールールの作成: 本番用・テスト用それぞれにリスナールールを作成
deploymentStrategyの指定:ecs.DeploymentStrategy.BLUE_GREENを指定alternateTargetの設定: グリーンターゲットグループと本番リスナールールを紐付け
本番用リスナーのConstruct IDについて
本番用リスナーのConstruct IDは、変更前と同じ 'Listener' を指定しています。
// 変更前
const listener = alb.addListener('Listener', { port: 80 });
// 変更後(Construct IDは同じ'Listener'を維持)
const prodListener = alb.addListener('Listener', {
port: 80,
defaultAction: elbv2.ListenerAction.fixedResponse(404),
});
CDKのConstruct IDは、CloudFormationテンプレート内でリソースを一意に識別するための論理IDに対応しています。Construct IDを変更すると、CloudFormationは既存のリスナーとは別のリソースとして扱います。
同じポート80のリスナーが2つ存在する瞬間が生まれるため、A listener already exists on this port エラーでデプロイが失敗します。
Construct IDを同じにすることで、CloudFormationがインプレース更新として処理し、ポート競合を回避できます。
`cdk diff`の結果
Stack EcsDeployCdkBgStack
IAM Statement Changes
┌───┬────────────────────────────────────┬────────┬────────────────┬───────────────────────────┬───────────┐
│ │ Resource │ Effect │ Action │ Principal │ Condition │
├───┼────────────────────────────────────┼────────┼────────────────┼───────────────────────────┼───────────┤
│ + │ ${Service/AlternateTargetRole.Arn} │ Allow │ sts:AssumeRole │ Service:ecs.amazonaws.com │ │
└───┴────────────────────────────────────┴────────┴────────────────┴───────────────────────────┴───────────┘
IAM Policy Changes
┌───┬────────────────────────────────┬─────────────────────────────────────────────────────────────────────────────────────────┐
│ │ Resource │ Managed Policy ARN │
├───┼────────────────────────────────┼─────────────────────────────────────────────────────────────────────────────────────────┤
│ + │ ${Service/AlternateTargetRole} │ arn:${AWS::Partition}:iam::aws:policy/AmazonECSInfrastructureRolePolicyForLoadBalancers │
└───┴────────────────────────────────┴─────────────────────────────────────────────────────────────────────────────────────────┘
Security Group Changes
┌───┬──────────────────────────────┬─────┬──────────┬─────────────────┐
│ │ Group │ Dir │ Protocol │ Peer │
├───┼──────────────────────────────┼─────┼──────────┼─────────────────┤
│ + │ ${ALB/SecurityGroup.GroupId} │ In │ TCP 8080 │ Everyone (IPv4) │
└───┴──────────────────────────────┴─────┴──────────┴─────────────────┘
(NOTE: There may be security-related changes not in this list. See https://github.com/aws/aws-cdk/issues/1299)
Resources
[-] AWS::ElasticLoadBalancingV2::TargetGroup ALB/Listener/ECSTargetGroup ALBListenerECSTargetGroupD95BA00C destroy
[+] AWS::ElasticLoadBalancingV2::Listener ALB/TestListener ALBTestListener0BBAE494
[+] AWS::ElasticLoadBalancingV2::TargetGroup BlueTargetGroup BlueTargetGroupF108EB01
[+] AWS::ElasticLoadBalancingV2::TargetGroup GreenTargetGroup GreenTargetGroupEEB2DF3E
[+] AWS::ElasticLoadBalancingV2::ListenerRule ProdListenerRule ProdListenerRule54DC1588
[+] AWS::ElasticLoadBalancingV2::ListenerRule TestListenerRule TestListenerRule1BA3B638
[+] AWS::IAM::Role Service/AlternateTargetRole ServiceAlternateTargetRole009B279F
[~] AWS::EC2::SecurityGroup ALB/SecurityGroup ALBSecurityGroup8B8624F8
└─ [~] SecurityGroupIngress
└─ @@ -5,5 +5,12 @@
[ ] "FromPort": 80,
[ ] "IpProtocol": "tcp",
[ ] "ToPort": 80
[+] },
[+] {
[+] "CidrIp": "0.0.0.0/0",
[+] "Description": "Allow from anyone on port 8080",
[+] "FromPort": 8080,
[+] "IpProtocol": "tcp",
[+] "ToPort": 8080
[ ] }
[ ] ]
[~] AWS::ElasticLoadBalancingV2::Listener ALB/Listener ALBListener3B99FF85
└─ [~] DefaultActions
└─ @@ -1,8 +1,8 @@
[ ] [
[ ] {
[-] "TargetGroupArn": {
[-] "Ref": "ALBListenerECSTargetGroupD95BA00C"
[+] "FixedResponseConfig": {
[+] "StatusCode": "404"
[ ] },
[-] "Type": "forward"
[+] "Type": "fixed-response"
[ ] }
[ ] ]
[~] AWS::ECS::Service Service/Service ServiceD69D759B
├─ [~] DeploymentConfiguration
│ └─ [+] Added: .Strategy
├─ [~] LoadBalancers
│ └─ @@ -1,9 +1,23 @@
│ [ ] [
│ [ ] {
│ [+] "AdvancedConfiguration": {
│ [+] "AlternateTargetGroupArn": {
│ [+] "Ref": "GreenTargetGroupEEB2DF3E"
│ [+] },
│ [+] "ProductionListenerRule": {
│ [+] "Ref": "ProdListenerRule54DC1588"
│ [+] },
│ [+] "RoleArn": {
│ [+] "Fn::GetAtt": [
│ [+] "ServiceAlternateTargetRole009B279F",
│ [+] "Arn"
│ [+] ]
│ [+] }
│ [+] },
│ [ ] "ContainerName": "nginx",
│ [ ] "ContainerPort": 80,
│ [ ] "TargetGroupArn": {
│ [-] "Ref": "ALBListenerECSTargetGroupD95BA00C"
│ [+] "Ref": "BlueTargetGroupF108EB01"
│ [ ] }
│ [ ] }
│ [ ] ]
└─ [~] DependsOn
└─ @@ -1,5 +1,5 @@
[ ] [
[-] "ALBListener3B99FF85",
[-] "ALBListenerECSTargetGroupD95BA00C",
[-] "TaskDefTaskRole1EDB4A67"
[+] "ProdListenerRule54DC1588",
[+] "TaskDefTaskRole1EDB4A67",
[+] "TestListenerRule1BA3B638"
[ ] ]
✨ Number of stacks with differences: 1
デプロイ
cdk deploy を実行してデプロイします。
cdk deploy
ECSサービスやリスナーはCloudFormationによるインプレース更新(UPDATE)が行われます。サービスの置換(REPLACEMENT)は発生しません。
動作確認
マネジメントコンソールのECSサービスのデプロイタブで、デプロイ戦略が「BLUE_GREEN」になっていることを確認できます。

切り替え時の注意点
テスト用リスナーのセキュリティグループ
テスト用リスナーはオプションですが、デプロイ中にグリーン環境を別ポートで事前確認できるため、設定しておくと便利です。
リスナーのConstruct IDを維持する
前述の通り、本番用リスナーのConstruct IDは変更前と同じものを使用してください。Construct IDを変更すると、同一ポートのリスナーが重複し、デプロイが失敗します。
おわりに
CDKで作成したECSサービスのデプロイ戦略を、ローリングアップデートからAmazon ECS ブルー/グリーンデプロイメントに切り替えてみました。
CDK L2 Constructでサポートされているため、deploymentStrategyの指定と、ターゲットグループ・リスナールールの追加で切り替えることができます。
ECSサービスやリスナーはインプレース更新されるため、サービスの置換は発生しません。ただし、本番用リスナーのConstruct IDは変更前と同じものを維持する必要がある点にご注意ください。







