CDKで作成したECSサービスのデプロイ戦略をブルー/グリーンに切り替えてみた

CDKで作成したECSサービスのデプロイ戦略をブルー/グリーンに切り替えてみた

2026.03.08

CDKで作成したECSサービスのデプロイ戦略を、デフォルトのローリングアップデートからAmazon ECS ブルー/グリーンデプロイメントに切り替えてみました。

CDK L2 Constructでサポートされているため、比較的少ない変更で切り替えできます。

※本記事でのブルー/グリーンデプロイメントは、CodeDeployを使わないECSネイティブの方式を指します。公式ドキュメントの記載に合わせて「Amazon ECS ブルー/グリーンデプロイメント」と記載します

Amazon ECS ブルー/グリーンデプロイメントとは

Amazon ECS ブルー/グリーンデプロイメントは、ECSがネイティブにサポートするデプロイ戦略です。

デプロイ時に新しいタスクセット(グリーン)を既存のタスクセット(ブルー)と並行して起動し、ALBのリスナールールを使ってトラフィックを切り替えます。

ECS自身がトラフィックの切り替えを管理するため、シンプルな構成でブルー/グリーンデプロイメントを実現できます。

https://docs.aws.amazon.com/AmazonECS/latest/developerguide/deployment-type-blue-green.html

切り替えに必要な変更点

ローリングアップデート(デフォルト)からブルー/グリーンに切り替えるには、以下の変更が必要です。

項目 説明
deploymentStrategy ecs.DeploymentStrategy.BLUE_GREEN を指定
グリーン用ターゲットグループ ブルー用に加え、グリーン用のターゲットグループを追加
テスト用リスナー(オプション) 本番用リスナーに加え、テスト用リスナーを追加。デプロイ中にグリーン環境を別ポートで事前確認する場合に使用
リスナーのデフォルトアクション リスナールールでルーティングを行うため、リスナーのデフォルトアクションを固定レスポンスに変更
リスナールール リスナーにリスナールールを作成
ECSインフラストラクチャロール AmazonECSInfrastructureRolePolicyForLoadBalancers ポリシーを付与したIAMロール(CDK L2が自動作成)
alternateTarget loadBalancerTarget にグリーンターゲットグループとリスナールールを設定

やってみる

ローリングアップデートで動作しているECSサービスを、ブルー/グリーンデプロイに切り替えます。

コード全体を確認したい場合は、以下のGitHubリポジトリをご確認ください。

https://github.com/msato0731/cdk-sample/tree/main/ecs-bluegreen-native

変更前: ローリングアップデート

まず、変更前のCDKコード(ローリングアップデート)を示します。

ALBとECSサービスの部分を抜粋します。

lib/ecs-stack.ts(変更前)
// 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: '/',
  },
});

この構成では、デプロイ時にローリングアップデートが実行されます。

Cursor_と_サービスデプロイ___Elastic_Container_Service___ap-northeast-1.png

変更後: ブルー/グリーンデプロイメント

以下がブルー/グリーンに切り替えた後のコードです。

lib/ecs-stack.ts(変更後)
// 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);

主な変更点は以下の通りです。

  1. リスナーのデフォルトアクション追加: リスナールールでルーティングするため、defaultAction に固定レスポンス(404)を設定
  2. テスト用リスナーの追加(オプション): ポート8080でテスト用リスナーを追加。デプロイ中にグリーン環境を事前確認する場合に使用
  3. ターゲットグループの分離: ブルー用とグリーン用の2つのターゲットグループを明示的に作成
  4. リスナールールの作成: 本番用・テスト用それぞれにリスナールールを作成
  5. deploymentStrategy の指定: ecs.DeploymentStrategy.BLUE_GREEN を指定
  6. 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)は発生しません。

https://docs.aws.amazon.com/AWSCloudFormation/latest/TemplateReference/aws-resource-ecs-service.html

https://docs.aws.amazon.com/AWSCloudFormation/latest/TemplateReference/aws-resource-elasticloadbalancingv2-listener.html

動作確認

マネジメントコンソールのECSサービスのデプロイタブで、デプロイ戦略が「BLUE_GREEN」になっていることを確認できます。

サービスデプロイ___Elastic_Container_Service___ap-northeast-1.png

切り替え時の注意点

テスト用リスナーのセキュリティグループ

テスト用リスナーはオプションですが、デプロイ中にグリーン環境を別ポートで事前確認できるため、設定しておくと便利です。

リスナーのConstruct IDを維持する

前述の通り、本番用リスナーのConstruct IDは変更前と同じものを使用してください。Construct IDを変更すると、同一ポートのリスナーが重複し、デプロイが失敗します。

おわりに

CDKで作成したECSサービスのデプロイ戦略を、ローリングアップデートからAmazon ECS ブルー/グリーンデプロイメントに切り替えてみました。

CDK L2 Constructでサポートされているため、deploymentStrategyの指定と、ターゲットグループ・リスナールールの追加で切り替えることができます。

ECSサービスやリスナーはインプレース更新されるため、サービスの置換は発生しません。ただし、本番用リスナーのConstruct IDは変更前と同じものを維持する必要がある点にご注意ください。

この記事をシェアする

FacebookHatena blogX

関連記事