CDKで構築したECS Fargateのデプロイをecspressoに移行してみた(RemovalPolicy.RETAINパターン)

CDKで構築したECS Fargateのデプロイをecspressoに移行してみた(RemovalPolicy.RETAINパターン)

2026.04.15

CDKで構築したECS Fargateサービスを、ecspressoでデプロイする構成に移行してみました。CDKのRemovalPolicy.RETAINを使って、稼働中のサービスを止めずにecspressoへ引き継ぐ手順を紹介します。

背景

CDKでECSをデプロイしている場合、タスク定義の更新時にCloudFormationのクロススタック参照エラーが出ることがあります。ECSのデプロイ部分をecspressoに切り出すことで、この問題を回避しつつ速くデプロイできます。

前提

  • AWS CDK(TypeScript)でECS Fargateを構築した経験がある
  • ecspresso v2
  • サンプルアプリ: nginx(最小構成)
  • CDKの2スタック構成(InfraStack + EcsServiceStack)から移行します

CDKでECS Fargate環境を構築する

CDK TypeScriptプロジェクトを作成して、2スタック構成でデプロイします。

  • InfraStack: VPC、ALB、ECSクラスター、IAMロールなど(移行後もCDKでの管理を継続)
  • EcsServiceStack: タスク定義とFargateService(ecspressoに移行する対象)

bin/app.ts

bin/app.ts
#!/usr/bin/env node
import * as cdk from "aws-cdk-lib";
import { InfraStack } from "../lib/infra-stack";
import { EcsServiceStack } from "../lib/ecs-service-stack";

const app = new cdk.App();

const infra = new InfraStack(app, "EcspressoDemoInfraStack", {
  env: {
    account: process.env.CDK_DEFAULT_ACCOUNT,
    region: "ap-northeast-1",
  },
});

new EcsServiceStack(app, "EcspressoDemoEcsServiceStack", {
  env: {
    account: process.env.CDK_DEFAULT_ACCOUNT,
    region: "ap-northeast-1",
  },
  cluster: infra.cluster,
  blueTargetGroup: infra.blueTargetGroup,
  greenTargetGroup: infra.greenTargetGroup,
  prodListenerRule: infra.prodListenerRule,
  blueGreenRole: infra.blueGreenRole,
  taskExecutionRole: infra.taskExecutionRole,
  taskRole: infra.taskRole,
  logGroup: infra.logGroup,
  containerSecurityGroup: infra.containerSecurityGroup,
  privateSubnets: infra.privateSubnets,
});

lib/infra-stack.ts

lib/infra-stack.ts
import * as cdk from "aws-cdk-lib";
import { Construct } from "constructs";
import * as ec2 from "aws-cdk-lib/aws-ec2";
import * as ecs from "aws-cdk-lib/aws-ecs";
import * as elbv2 from "aws-cdk-lib/aws-elasticloadbalancingv2";
import * as iam from "aws-cdk-lib/aws-iam";
import * as logs from "aws-cdk-lib/aws-logs";

export class InfraStack extends cdk.Stack {
  public readonly cluster: ecs.Cluster;
  public readonly blueTargetGroup: elbv2.ApplicationTargetGroup;
  public readonly greenTargetGroup: elbv2.ApplicationTargetGroup;
  public readonly prodListenerRule: elbv2.ApplicationListenerRule;
  public readonly blueGreenRole: iam.Role;
  public readonly taskExecutionRole: iam.Role;
  public readonly taskRole: iam.Role;
  public readonly logGroup: logs.LogGroup;
  public readonly containerSecurityGroup: ec2.SecurityGroup;
  public readonly privateSubnets: ec2.ISubnet[];

  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    // VPC
    const vpc = new ec2.Vpc(this, "Vpc", {
      maxAzs: 2,
      natGateways: 1,
    });

    // ECS Cluster
    this.cluster = new ecs.Cluster(this, "Cluster", {
      vpc,
      clusterName: "ecspresso-demo-cluster",
    });

    // Security Groups
    const albSecurityGroup = new ec2.SecurityGroup(this, "AlbSg", {
      vpc,
      description: "ALB Security Group",
    });
    albSecurityGroup.addIngressRule(
      ec2.Peer.anyIpv4(),
      ec2.Port.tcp(80),
      "Allow HTTP"
    );
    albSecurityGroup.addIngressRule(
      ec2.Peer.anyIpv4(),
      ec2.Port.tcp(8080),
      "Allow test listener"
    );

    this.containerSecurityGroup = new ec2.SecurityGroup(
      this,
      "ContainerSg",
      {
        vpc,
        description: "ECS Container Security Group",
      }
    );
    this.containerSecurityGroup.addIngressRule(
      albSecurityGroup,
      ec2.Port.tcp(80),
      "Allow from ALB"
    );

    // ALB
    const alb = new elbv2.ApplicationLoadBalancer(this, "Alb", {
      vpc,
      internetFacing: true,
      securityGroup: albSecurityGroup,
    });

    // ブルー/グリーン用ターゲットグループ
    this.blueTargetGroup = new elbv2.ApplicationTargetGroup(this, "BlueTg", {
      vpc,
      port: 80,
      protocol: elbv2.ApplicationProtocol.HTTP,
      targetType: elbv2.TargetType.IP,
      healthCheck: {
        path: "/",
        interval: cdk.Duration.seconds(5),
        timeout: cdk.Duration.seconds(2),
        healthyThresholdCount: 2,
      },
      deregistrationDelay: cdk.Duration.seconds(5),
    });

    this.greenTargetGroup = new elbv2.ApplicationTargetGroup(this, "GreenTg", {
      vpc,
      port: 80,
      protocol: elbv2.ApplicationProtocol.HTTP,
      targetType: elbv2.TargetType.IP,
      healthCheck: {
        path: "/",
        interval: cdk.Duration.seconds(5),
        timeout: cdk.Duration.seconds(2),
        healthyThresholdCount: 2,
      },
      deregistrationDelay: cdk.Duration.seconds(5),
    });

    // 本番用リスナー・リスナールール
    const prodListener = alb.addListener("ProdListener", {
      port: 80,
      defaultAction: elbv2.ListenerAction.fixedResponse(404),
    });

    this.prodListenerRule = new elbv2.ApplicationListenerRule(this, "ProdRule", {
      listener: prodListener,
      priority: 1,
      conditions: [elbv2.ListenerCondition.pathPatterns(["*"])],
      targetGroups: [this.blueTargetGroup],
    });

    // テスト用リスナー・リスナールール
    const testListener = alb.addListener("TestListener", {
      port: 8080,
      defaultAction: elbv2.ListenerAction.fixedResponse(404),
    });

    new elbv2.ApplicationListenerRule(this, "TestRule", {
      listener: testListener,
      priority: 1,
      conditions: [elbv2.ListenerCondition.pathPatterns(["*"])],
      targetGroups: [this.blueTargetGroup],
    });

    // IAM Roles
    this.taskExecutionRole = new iam.Role(this, "TaskExecutionRole", {
      assumedBy: new iam.ServicePrincipal("ecs-tasks.amazonaws.com"),
      managedPolicies: [
        iam.ManagedPolicy.fromAwsManagedPolicyName(
          "service-role/AmazonECSTaskExecutionRolePolicy"
        ),
      ],
    });

    this.taskRole = new iam.Role(this, "TaskRole", {
      assumedBy: new iam.ServicePrincipal("ecs-tasks.amazonaws.com"),
    });

    // B/Gデプロイ用ロール(ECSがリスナールールのターゲットグループを切り替える際に使用)
    this.blueGreenRole = new iam.Role(this, "AlternateTargetRole", {
      assumedBy: new iam.ServicePrincipal("ecs.amazonaws.com"),
      managedPolicies: [
        iam.ManagedPolicy.fromAwsManagedPolicyName(
          "AmazonECSInfrastructureRolePolicyForLoadBalancers"
        ),
      ],
    });

    // CloudWatch Logs
    this.logGroup = new logs.LogGroup(this, "LogGroup", {
      logGroupName: "/ecs/ecspresso-demo",
      removalPolicy: cdk.RemovalPolicy.DESTROY,
      retention: logs.RetentionDays.ONE_WEEK,
    });

    this.privateSubnets = vpc.privateSubnets;

    // Outputs(ecspressoのcloudformationプラグインから参照する)
    new cdk.CfnOutput(this, "ClusterName", {
      value: this.cluster.clusterName,
      exportName: "ecspresso-demo-cluster-name",
    });

    new cdk.CfnOutput(this, "PrivateSubnet1Id", {
      value: vpc.privateSubnets[0].subnetId,
      exportName: "ecspresso-demo-private-subnet-1-id",
    });

    new cdk.CfnOutput(this, "PrivateSubnet2Id", {
      value: vpc.privateSubnets[1].subnetId,
      exportName: "ecspresso-demo-private-subnet-2-id",
    });

    new cdk.CfnOutput(this, "ContainerSecurityGroupId", {
      value: this.containerSecurityGroup.securityGroupId,
      exportName: "ecspresso-demo-container-sg-id",
    });

    new cdk.CfnOutput(this, "TaskExecutionRoleArn", {
      value: this.taskExecutionRole.roleArn,
      exportName: "ecspresso-demo-task-execution-role-arn",
    });

    new cdk.CfnOutput(this, "TaskRoleArn", {
      value: this.taskRole.roleArn,
      exportName: "ecspresso-demo-task-role-arn",
    });

    new cdk.CfnOutput(this, "LogGroupName", {
      value: this.logGroup.logGroupName,
      exportName: "ecspresso-demo-log-group-name",
    });

    new cdk.CfnOutput(this, "BlueTargetGroupArn", {
      value: this.blueTargetGroup.targetGroupArn,
      exportName: "ecspresso-demo-blue-tg-arn",
    });

    new cdk.CfnOutput(this, "GreenTargetGroupArn", {
      value: this.greenTargetGroup.targetGroupArn,
      exportName: "ecspresso-demo-green-tg-arn",
    });

    new cdk.CfnOutput(this, "ProdListenerRuleArn", {
      value: this.prodListenerRule.listenerRuleArn,
      exportName: "ecspresso-demo-prod-listener-rule-arn",
    });

    new cdk.CfnOutput(this, "BlueGreenRoleArn", {
      value: this.blueGreenRole.roleArn,
      exportName: "ecspresso-demo-blue-green-role-arn",
    });

    new cdk.CfnOutput(this, "AlbDnsName", {
      value: alb.loadBalancerDnsName,
    });
  }
}

lib/ecs-service-stack.ts

lib/ecs-service-stack.ts
import * as cdk from "aws-cdk-lib";
import { Construct } from "constructs";
import * as ec2 from "aws-cdk-lib/aws-ec2";
import * as ecs from "aws-cdk-lib/aws-ecs";
import * as elbv2 from "aws-cdk-lib/aws-elasticloadbalancingv2";
import * as iam from "aws-cdk-lib/aws-iam";
import * as logs from "aws-cdk-lib/aws-logs";

interface EcsServiceStackProps extends cdk.StackProps {
  cluster: ecs.Cluster;
  blueTargetGroup: elbv2.ApplicationTargetGroup;
  greenTargetGroup: elbv2.ApplicationTargetGroup;
  prodListenerRule: elbv2.ApplicationListenerRule;
  blueGreenRole: iam.Role;
  taskExecutionRole: iam.Role;
  taskRole: iam.Role;
  logGroup: logs.LogGroup;
  containerSecurityGroup: ec2.SecurityGroup;
  privateSubnets: ec2.ISubnet[];
}

export class EcsServiceStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props: EcsServiceStackProps) {
    super(scope, id, props);

    const taskDefinition = new ecs.FargateTaskDefinition(this, "TaskDef", {
      cpu: 256,
      memoryLimitMiB: 512,
      executionRole: props.taskExecutionRole,
      taskRole: props.taskRole,
      family: "ecspresso-demo",
    });

    taskDefinition.addVolume({ name: "nginx-cache" });
    taskDefinition.addVolume({ name: "nginx-run" });
    taskDefinition.addVolume({ name: "tmp" });

    const container = taskDefinition.addContainer("nginx", {
      image: ecs.ContainerImage.fromRegistry(
        "public.ecr.aws/nginx/nginx:1.27"
      ),
      portMappings: [{ containerPort: 80 }],
      logging: ecs.LogDrivers.awsLogs({
        streamPrefix: "nginx",
        logGroup: props.logGroup,
      }),
      readonlyRootFilesystem: true,
    });

    container.addMountPoints(
      { sourceVolume: "nginx-cache", containerPath: "/var/cache/nginx", readOnly: false },
      { sourceVolume: "nginx-run", containerPath: "/var/run", readOnly: false },
      { sourceVolume: "tmp", containerPath: "/tmp", readOnly: false },
    );

    const service = new ecs.FargateService(this, "Service", {
      cluster: props.cluster,
      taskDefinition,
      desiredCount: 1,
      serviceName: "ecspresso-demo-service",
      assignPublicIp: false,
      securityGroups: [props.containerSecurityGroup],
      vpcSubnets: { subnets: props.privateSubnets },
      deploymentStrategy: ecs.DeploymentStrategy.BLUE_GREEN,
      bakeTime: cdk.Duration.minutes(15),
    });

    const target = service.loadBalancerTarget({
      containerName: "nginx",
      containerPort: 80,
      protocol: ecs.Protocol.TCP,
      alternateTarget: new ecs.AlternateTarget("AlternateTarget", {
        alternateTargetGroup: props.greenTargetGroup,
        productionListener:
          ecs.ListenerRuleConfiguration.applicationListenerRule(
            props.prodListenerRule
          ),
        role: props.blueGreenRole,
      }),
    });

    target.attachToApplicationTargetGroup(props.blueTargetGroup);
  }
}

デプロイ

cdk deploy --all

デプロイ完了後に、CfnOutputに表示されるALBのDNS名にアクセスするとnginxのデフォルトページが表示されます。

Welcome_to_nginx_.png

CDKだけでECSを管理する場合の課題

VPCやIAMロールなどのインフラリソースは、一度構築したらあまり変更しません。一方、ECSにデプロイするアプリケーションは日常的に更新します。この2つはライフサイクルが異なりますが、CDKで一括管理するとその違いが見えにくくなります。

イメージタグを変えるだけでもCloudFormationスタックの更新が走り、数分かかります。ecspressoでECSのデプロイを分離すると、ECS APIを直接呼ぶため速くデプロイできます。

cdk-ecspresso-ecs-fargate.png

ecspressoの紹介とインストール

ecspressoはKayac社製のECSデプロイツールです。ECSのサービスとタスク定義をJSON/YAMLで管理し、ECS APIを直接呼んでデプロイします。

CDKでインフラ基盤(VPC、ALB、IAMロールなど)を管理して、ecspressoでタスク定義とサービスのデプロイを担当する構成になります。

インストールはHomebrewで行います。

brew install kayac/tap/ecspresso

ecspresso init で設定を自動生成

ecspressoには、既存のECSサービスから設定ファイルを自動生成する機能があります。

mkdir ecspresso && cd ecspresso

ecspresso init \
  --cluster ecspresso-demo-cluster \
  --service ecspresso-demo-service \
  --region ap-northeast-1

3つのファイルが生成されます。

  • ecspresso.yml: クラスター名・サービス名・リージョンなどの基本設定
  • ecs-service-def.json: サービス定義(ネットワーク設定、デプロイ設定、ターゲットグループ)
  • ecs-task-def.json: タスク定義(コンテナ定義、CPU/メモリ、IAMロール)

cloudformationプラグインの設定

生成された設定ファイルにはサブネットIDやセキュリティグループIDがハードコードされています。CDKのCfnOutputから動的に取得するよう、cloudformationプラグインを設定します。

ecspresso.ymlにプラグインを追加します。timeoutはデフォルトの10分ではB/GデプロイのBakeフェーズ(15分)中にタイムアウトするため、25分に変更しています。

ecspresso.yml
 region: ap-northeast-1
 cluster: ecspresso-demo-cluster
 service: ecspresso-demo-service
 service_definition: ecs-service-def.json
 task_definition: ecs-task-def.json
 timeout: "25m0s"
+plugins:
+  - name: cloudformation

ecs-service-def.jsonのハードコード値をプラグインのテンプレートに置き換えます。

B/G構成ではloadBalancersadvancedConfigurationが含まれます。ハードコード値をcloudformationプラグインのテンプレートに置き換えます。

ecs-service-def.json
   "loadBalancers": [
     {
       "advancedConfiguration": {
-        "alternateTargetGroupArn": "arn:aws:elasticloadbalancing:ap-northeast-1:123456789012:targetgroup/green-...",
+        "alternateTargetGroupArn": "{{ cfn_output `EcspressoDemoInfraStack` `GreenTargetGroupArn` }}",
-        "productionListenerRule": "arn:aws:elasticloadbalancing:ap-northeast-1:123456789012:listener-rule/...",
+        "productionListenerRule": "{{ cfn_output `EcspressoDemoInfraStack` `ProdListenerRuleArn` }}",
-        "roleArn": "arn:aws:iam::123456789012:role/EcspressoDemoEcsServiceSt-ServiceAlternateTargetRol-..."
+        "roleArn": "{{ cfn_output `EcspressoDemoInfraStack` `BlueGreenRoleArn` }}"
       },
       "containerName": "nginx",
       "containerPort": 80,
-      "targetGroupArn": "arn:aws:elasticloadbalancing:ap-northeast-1:123456789012:targetgroup/blue-..."
+      "targetGroupArn": "{{ cfn_output `EcspressoDemoInfraStack` `BlueTargetGroupArn` }}"
     }
   ],
   "networkConfiguration": {
     "awsvpcConfiguration": {
       "assignPublicIp": "DISABLED",
       "securityGroups": [
-        "sg-0123456789abcdef0"
+        "{{ cfn_output `EcspressoDemoInfraStack` `ContainerSecurityGroupId` }}"
       ],
       "subnets": [
-        "subnet-0123456789abcdef0",
-        "subnet-0123456789abcdef1"
+        "{{ cfn_output `EcspressoDemoInfraStack` `PrivateSubnet1Id` }}",
+        "{{ cfn_output `EcspressoDemoInfraStack` `PrivateSubnet2Id` }}"
       ]
     }
   },

ecs-task-def.jsonも同様に書き換えます。

ecs-task-def.json
       "logConfiguration": {
         "logDriver": "awslogs",
         "options": {
-          "awslogs-group": "/ecs/ecspresso-demo",
+          "awslogs-group": "{{ cfn_output `EcspressoDemoInfraStack` `LogGroupName` }}",
           "awslogs-region": "ap-northeast-1",
           "awslogs-stream-prefix": "nginx"
         }
       },
   // ...省略...
-  "executionRoleArn": "arn:aws:iam::123456789012:role/EcspressoDemoInfraStack-TaskExecutionRole...",
+  "executionRoleArn": "{{ cfn_output `EcspressoDemoInfraStack` `TaskExecutionRoleArn` }}",
   "family": "ecspresso-demo",
   // ...省略...
-  "taskRoleArn": "arn:aws:iam::123456789012:role/EcspressoDemoInfraStack-TaskRole..."
+  "taskRoleArn": "{{ cfn_output `EcspressoDemoInfraStack` `TaskRoleArn` }}",

ecspressoの基本操作

設定ファイルを準備したら、ecspressoの基本的なコマンドを試してみます。

差分確認

ecspresso diff

CloudFormationスタックから値を取得し、現在のECSサービスとの差分を表示します。プラグイン設定が正しければ差分はなく、INFO行のみ出力されます。

2026-04-13T17:19:32.086+09:00 [INFO] ecspresso version [version:v2.8.0]

デプロイ

イメージタグをnginx:1.27からnginx:1.26に変えてデプロイしてみます。ecs-task-def.jsonimageを書き換えてecspresso diffを実行すると、変更箇所が表示されます。

$ ecspresso diff
--- arn:aws:ecs:ap-northeast-1:123456789012:task-definition/ecspresso-demo:1
+++ ecs-task-def.json
@@ -4,7 +4,7 @@
       "cpu": 0,
       "dockerLabels": {},
       "essential": true,
-      "image": "public.ecr.aws/nginx/nginx:1.27",
+      "image": "public.ecr.aws/nginx/nginx:1.26",
       "logConfiguration": {
         "logDriver": "awslogs",
         "options": {

差分を確認したらデプロイします。B/Gデプロイではbakeフェーズ(15分)があるため、--no-waitでデプロイを投入します。

ecspresso deploy --no-wait

デプロイ後にecspresso diffを実行して差分がなければ、設定通りに反映されています。

ロールバック

ecspresso rollback

直前のタスク定義リビジョンに戻します。nginx:1.27に戻ったことを確認できます。

状態確認

ecspresso status

サービスのrunningCount、desiredCount、最新のデプロイメント状態などを確認できます。

ecspresso verify

サービスの設定が正しいかを検証します。ターゲットグループのヘルスチェックやIAMロールの権限なども確認されます。

CDKからecspressoへの移行(DeletionPolicy=Retainパターン)

稼働中のECSサービスを止めずに、CDK管理からecspresso管理へ切り替えます。

移行は3ステップで行います。

CDKでRemovalPolicy.RETAINを設定する

ecs-service-stack.tsのタスク定義とサービスにRemovalPolicy.RETAINを設定します。

lib/ecs-service-stack.ts
 const taskDefinition = new ecs.FargateTaskDefinition(this, "TaskDef", {
   cpu: 256,
   memoryLimitMiB: 512,
   executionRole: props.taskExecutionRole,
   taskRole: props.taskRole,
   family: "ecspresso-demo",
 });

+taskDefinition.applyRemovalPolicy(cdk.RemovalPolicy.RETAIN);

 taskDefinition.addVolume({ name: "nginx-cache" });
 // ...省略...

 const service = new ecs.FargateService(this, "Service", {
   // ...省略...
   deploymentStrategy: ecs.DeploymentStrategy.BLUE_GREEN,
 });

+service.applyRemovalPolicy(cdk.RemovalPolicy.RETAIN);

 const target = service.loadBalancerTarget({
   // ...省略...
 });

デプロイします。

cdk deploy EcspressoDemoEcsServiceStack

このデプロイではリソースの動作に変更はありません。CloudFormationテンプレートのDeletionPolicyがDeleteからRetainに変わるだけです。

CDKからタスク定義とサービスを削除する

ecs-service-stack.tsからタスク定義とFargateServiceの記述を削除します。

lib/ecs-service-stack.ts
 export class EcsServiceStack extends cdk.Stack {
   constructor(scope: Construct, id: string, props: EcsServiceStackProps) {
     super(scope, id, props);

-    const taskDefinition = new ecs.FargateTaskDefinition(this, "TaskDef", {
-      // ...タスク定義の全体を削除...
-    });
-
-    // ...コンテナ、マウントポイント、サービスの定義をすべて削除...
-
-    service.attachToApplicationTargetGroup(props.targetGroup);
+    // タスク定義とサービスを削除済み
+    // ecspressoで管理する
   }
 }

デプロイします。

cdk deploy EcspressoDemoEcsServiceStack

CloudFormationはスタックからタスク定義とサービスを削除しますが、Retainが設定されているため実リソースは削除されません。ALB経由でnginxにアクセスできることを確認します。

curl http://<ALBのDNS>/

nginxのレスポンスが返れば、リソースが残っていることが確認できます。

ecspressoでデプロイする

CloudFormationの管理から外れたECSサービスを、ecspressoで引き継ぎます。

ecspresso diff

ecspressoが既存のサービスとタスク定義を認識していることを確認します。

イメージタグを変えてデプロイしてみます。ecs-task-def.jsonimagepublic.ecr.aws/nginx/nginx:1.26に変更し、デプロイします。

ecspresso deploy --no-wait

ecspresso diffで差分がなくなれば、ecspressoでの管理に移行できています。

ecspresso run でワンオフタスク実行

ecspressoにはワンオフタスク実行の機能もあります。DBマイグレーションやバッチ処理で使います。

overridesをJSONファイルに切り出しておくと便利です。

overrides-demo.json
{
  "containerOverrides": [
    {
      "name": "nginx",
      "command": ["sh", "-c", "echo Migration started && nginx -v && echo Done"]
    }
  ]
}
ecspresso run --overrides-file overrides-demo.json --watch-container nginx

--watch-containerを指定すると、タスクのログがリアルタイムに表示されます。タスクが完了すると終了コードが返ります(成功=0、失敗=非0)。

ネットワーク設定(サブネット、セキュリティグループ)はサービス定義のnetworkConfigurationがそのまま適用されます。常駐サービスとは違い、コマンド実行後にタスクは自動で停止します。CIからDBマイグレーションを実行するような用途で便利です。

クリーンアップ

検証が終わったらリソースを削除します。

# ECSサービスを停止・削除
ecspresso scale --tasks 0
ecspresso delete --force

# インフラリソースを削除
cdk destroy --all

おわりに

CDKでインフラ基盤を管理しつつ、ECSのデプロイはecspressoに任せる構成にすると、クロススタック参照の問題を回避できます。RemovalPolicy.RETAINパターンで、稼働中のサービスを止めずに移行できました。

この記事をシェアする

関連記事