ECS で Blue/Green デプロイ(以下、B/G デプロイ)をしたい場合、CodeDeploy を使うと比較的簡単に構成することができますが、Green 環境をデプロイした後のテスト期間が最大2日間までになります。リリース前のテスト期間をもっと取りたいようなケースに対処するために、ECS のデプロイタイプを外部デプロイにし、デプロイコントローラを AWS Step Functions で実装することで B/G デプロイを実現できそうでしたのでデモアプリとして作ってみました。

(デモアプリは GitHub に公開しています : https://github.com/msysh/aws-demo-blue-green-deploy-with-external-deploy-controller

TOC

(全体的に長くなったので…)

デモアプリ概要

Nginx のコンテナに HTML ファイルを置いただけのものを Web アプリと見立てて、ECS の外部デプロイを使って B/G デプロイするデモです。

デモアプリはパイプライン含めて全体を CDK で構築しました。デプロイすると以下のアーキテクチャでリソースを作成します。

Architecutre overview

デモアプリの前提条件

今回はとりあえずのデモとして前提条件を以下のようにしています。

  • パイプラインの手動承認などは CodePipeline の UI/UX を維持したいので、パイプライン全体としては CodePipeline を利用することにします
  • デモアプリなので Auto Scaling、Rollback については、一旦考慮から外しています(対応方針は最後に記しておきたいと思います)
  • CodeDeploy の B/G デプロイでは Target Group を 2つ作成しデプロイの都度交互に入れ替えるような動作をしますが、このデモアプリではデプロイの都度 Target Group を作成しています(Target Group 名に Commit ID を付記していきます)

デモの流れ

デモの流れをざっくりと(各ステージの詳細はパイプライン詳説で解説します)。

Code Repository に Push すると、CodePipeline の実行がトリガーされます。CodePipeline の各ステージでは以下のことをしています。

  • Code Repository
    • デモ用の CodeCommit リポジトリで、Nginx コンテナに入れる index.html や、Dockerfile、各 CodeBuild ステージで利用する buildspec-xxx.yml ファイルを入れます(デモ用に用意済みです)
  • Build App
    • アプリビルドと称して、index.html ファイルの中に “コミット ID” と “その時点の日時” を埋め込むだけの処理をしています
  • Build Image
    • “Build App” ステージの成果物である、index.html を使って Nginx コンテナのイメージをビルドし、ECR にプッシュします
  • Prepare Deploy
    • ここからやや特殊なステージになってきます。このステージでは次の Step Functions のステートマシーンに入力として渡すための JSON ファイルを作成しています
  • Deploy Green
    • Step Functions を使って Green 環境をデプロイし、テストができる状態までにします(具体的な処理は後述)
  • Manual Approval (1)
    • Green 環境でのテスト期間を確保するために手動承認をここに入れています。テストが問題なければ承認するイメージです
  • Swap
    • Swap と書いていますが、Production 環境に流れていたトラフィックを先ほどデプロイした Green 環境に流れるように向き先を変更します。このステージの完了時点ではテストリスナー経由の接続も可能です
  • Manual Approval (2)
    • 旧本番環境を即消してしまうのではなく、承認が得られるまで残しておけるようにしておきます
  • Clean Up
    • 旧本番環境を削除します

デモアプリ実行方法

デモアプリのセットアップ、実行方法は GitHub の README にも書いていますがこちらにも。 事前に AWS CLI と CDK が使えるようにセットアップしておいてください(AWS CLI / CDK)。

1. 環境のセットアップ

まずはデモアプリの環境を整えます。

1-1. GitHub からクローン

git clone https://github.com/msysh/aws-demo-blue-green-deploy-with-external-deploy-controller.git

1-2. CDK で AWS リソースを作成

cd aws-demo-blue-green-deploy-with-external-deploy-controller
cdk deploy

デプロイが完了すると、出力としてリソースの情報が表示されますので以下のものをメモっておきます。

  • CodeCommitRepositoryUrl : CodeCommit リポジトリの URL
  • EcsClusterName : ECS のクラスタ名
  • EcsServiceName : ECS のサービス名
  • AlbProdDnsAlbTestDns : Web アプリの Production 用 URL (:80) と Test 用 URL (:8080)

1-3. ECS サービスの更新(希望台数の変更)

ECS サービスのデプロイタイプを外部デプロイにした場合、サービス作成時に desired-count (タスクの希望台数)を “0” にしておく必要があったので、このタイミングで “1” に上げておきます。

aws ecs update-service \
  --cluster ${EcsClusterName} \
  --service ${EcsServiceName} \
  --desired-count 1

${EcsClusterName}${EcsServiceName} は CDK デプロイ後の出力で得られる値です。

1-4. デモアプリ リポジトリのセットアップ

デモアプリも同梱しているので、作成された CodeCommit リポジトリをリモート origin としてセットアップします。

cd ./assets/demo-app-repository/
git init
git remote add origin ${CodeCommitRepositoryUrl} -m main

${CodeCommitRepositoryUrl} は CDK デプロイ後の出力で得られる値です。

2. デモの実行

ここからはデモアプリを使って B/G デプロイをしていきます。

2-1. リポジトリにデモアプリを push

cd ./assets/demo-app-repository/
git status
git add .
git commit -m "first commit"
git push origin main

Push できない場合は CodeCommit のドキュメントを参照してみてください。

2-2. パイプラインの実行がトリガーされる

リポジトリに Push すると、CodePipeline が実行されます。前述のデモの流れの通り、1) “Build App”、2) “Build Image”、3) “Prepare Deploy”、4) “Deploy Green” まで実行され、1回目の手動承認のところで待機状態になります。

この時点でテスト環境(http://${ALB の DNS 名}:8080/)に接続できるようになります(タスクの起動を少し待つことになるかもしれません)。

2-3. 本番トラフィックの切り替えのための手動承認

マネジメントコンソールにログインし、CodePipeline を開いたのち、1回目の手動承認ステージにて承認します。

2.4. 本番トラフィックの切り替え

CodePipeline のステージは “Swap” に進み、本番側のトラフィックを先ほどデプロイした新しい環境に転送先を変更します。

この時点で本番環境(http://${ALB の DNS 名}/)に接続できるようになります(ECS タスクは Green 環境として起動済みなのですぐに繋がるはずです)。また、この時点ではテスト側からでも接続が可能な状態です。

2-5. 旧環境削除のための手動承認

再びマネジメントコンソールから CodePipeline を開き、2回目の手動承認ステージを承認します。

2-6. 旧環境の削除

CodePipeline のステージは “Clean up” に進みます。テスト環境側のリスナーの転送設定を削除し、旧環境を削除します。

2-7. 新しいリビジョンのデプロイ

新しいリビジョンをデプロイする場合は index.html を適当に変更します。例えば…

cd assets/demo-app-repository/
sed -i s/background-color:\ \#99c/background-color:\ \#9c9/ index.html

# MacOS の場合は -i の後に '' を追加
# sed -i '' s/background-color:\ \#99c/background-color:\ \#9c9/ index.html

変更を CodeCommit リポジトリに Push します。

git add .
git commit -m "change background color"
git push origin main

以降、2-2. パイプラインの実行がトリガーされる から同様に実行されます。

デモアプリの削除

NAT Gateway など、存在するだけでコストがかかるリソースがあるので、不要になったら削除してください。

削除手順は以下の通りです。

1. ECS タスクの終了

aws ecs update-service --cluster ${EcsClusterName} --service ${EcsServiceName} --desired-count 0

${EcsClusterName}${EcsServiceName} は CDK デプロイ後の出力で得られる値です。

2. CDK リソースの削除

# cd ${ThisProjectRoot}
cdk destroy

3. 残ったリソースの削除

ECR リポジトリ

マネジメントコンソール、もしくは CLI 等で削除してください。CLI の場合は以下。

aws ecr delete-repository --repository-name ${EcrRepositoryUri からドメイン部分を削除した値}

${EcrRepositoryUri} は CDK デプロイ後の出力で得られる値です。そこからドメイン名( 〜.amazonaws.com/ )を削除した値が Repository Name です

Artifact 用 S3 バケット

マネジメントコンソール、もしくは CLI 等で削除してください。CLI の場合は以下(確認ないので気をつけてください)。

aws s3 rm --recursive s3://${ArtifactBucketName}
aws s3api delete-bucket --bucket ${ArtifactBucketName} 

${ArtifactBucketName} は CDK デプロイ後の出力で得られる値です。

ターゲットグループ

マネジメントコンソール、もしくは CLI 等で削除してください。

ターゲットグループ名は tg-{ECSサービス名の先頭 8文字ぐらい}-{コミットIDの先頭 15文字} という構成です。CLI で削除する場合は ARN が必要になります。

# Target Group の一覧取得
aws elbv2 describe-target-groups \
  --query 'TargetGroups[*].{TargetGroupName:TargetGroupName,TargetGroupArn:TargetGroupArn}' --output table

# 不要な Target Group の削除
aws elbv2 delete-target-group --target-group-arn ${不要なターゲットグループ ARN}

パイプライン詳説

パイプラインの技術的な中身についてもう少しく詳しく記しておきたいと思います。フローの流れとして、新しい Commit ID v2 をデプロイするという状況を想定しています。

Pipeline architecture

“Code Repository” 〜 “Build Image” ステージ

ソース、ビルド、コンテナイメージの作成までは特別なことはしていないので割愛します。

“Prepare Deploy” ステージ

このステージでは次の Step Functions のステートマシンに入力として渡すための JSON ファイルを作成しています。 主に以下のパラメータを環境変数でこの CodeBuild プロジェクトに渡しています。

  • ECS クラスタ名サービス名タスク定義
  • ALB Production 用 / Test 用のリスナー ARNリスナールールの ARN
  • ECS タスクに割り当てる VPCサブネットセキュリティグループ

CodeBuild プロジェクトの buildspec ファイルで printf を使って JSON ファイルを作成しているだけのシンプルなものです。

以下は buildspec ファイルの抜粋(該当のファイル : buildspec-prepare-deploy-green.yml [GitHub])。 作成される statemachine-input.json を成果物として次の Step Functions ステートマシンに入力として渡します。

  build:
    commands:
    - |
      printf '{
        "commitId": "%s",
        "clusterName": "%s",
        #    :
        #    :
      }' "${CODEBUILD_RESOLVED_SOURCE_VERSION}" "${clusterName}" ..... > ./statemachine-input.json      
  #   :

CodeBuild プロジェクトへの環境変数の設定は CDK で以下のようにしています(該当箇所 : prepare-deploy-green.ts[GitHub])。以下はその一部抜粋。

const project = new codebuild.PipelineProject(scope, 'ProjectPrepareDeployGreen', {
  // :
  environment :{
    // :
    environmentVariables: {
      'clusterName': {                            // `clusterName` という名前の環境変数をセット
        type: codebuild.BuildEnvironmentVariableType.PLAINTEXT,
        value: workloadParameter.ecsClusterName,  // 作成した ECS のクラスタ名
      },
      // :
    }
  }
})

“Deploy Green” ステージ

このステージでは CodePipeline から Step Functions ステートマシンを起動し、Green 環境をデプロイしていきます。フローを作成するにあたり公式ドキュメントの “外部デプロイワークフロー” を参照します。

ワークフローと B/G デプロイの変化の主要な部分について APNG (Animated PNG) にしてみました。2秒おきに遷移します(全 8ページ)。

Stage for Deploy Green

以下、各番号の補足です。

  1. DescribeServices : ecs:DescribeServices を実行し既存の Task Set を取得します
  2. CountTaskSets : 既存の Task Set の数をカウントします
  3. Choice : Task Set の数に応じて分岐します
    • “0” の時 : 既存の Task Set は無く、初回デプロイの時なので “5. CreateTargetGroup” へ
    • “1” の時 : 既存の Task Set が 1つある時なので “4. CurrentTaskSet” へ(通常時はこのパターンになるはず)
    • “それ以外” の時 : このデモアプリではデプロイ前に Task Set 2つ以上がある時は前回何らかのエラーや手動承認で却下があったとみなして失敗にしています。手動で ACTIVE 状態の Task Set を削除してから再実行します
  4. CurrentTaskSet : 既存の Task Set(この時点では本番稼働中)の Task Set ARN を取得しておきます。Clean up のステージで Task Set を消す際に必要になるからです
  5. CreateTargetGroup : elbv2:CreateTargetGroup を実行し、Target Group を作成します。Target Group 名には Commit ID を付記しています
    • ヘルスチェックの設定もここで設定することになります
  6. CreateTaskSet : ecs:CreateTaskSet により Task Set を新規に作成します。以下のようなパラメータが必要になります
    • キャパシティプロバイダ戦略 (デモアプリでは FARGATE_SPOT を利用)
    • Target Group ARN
    • タスク定義に設定されているリクエストを受け付けるコンテナの名前、ポート番号
    • タスクのネットワーク設定(サブネット、セキュリティグループ)
  7. (Task Set を作成すると自動的にタスクが起動してきます。何台起動させるかは Task Set 作成時の Scale プロパティで指定します。100% と指定すると、既存の PRIMARY Task Set と同じ台数で起動してきます)
    "Scale": {
      "Value": 100,
      "Unit": "PERCENT"
    }
    
  8. ModifyRuleForTest : elbv2:ModifyRule を実行して、テストリスナーに設定されているリスナールールを変更し、5. で作成した Target Group に転送するようにします
    • この時点で Green 環境として接続できるようになり、テストが実施可能になります

各ステートでの実行結果は ResultPath を使って、ステートマシン起動時の入力を維持しつつ後段のステート、ステージでも使えるようにしておきます。以下のようなイメージです。

{
    // ステートマシン起動時の入力パラメータ。Commit ID、ECS クラスタ名、などなど
    "commitId": "...",
    "ecsClusterName" : "...",
    //   :
    //   :
    // ここから各ステートの実行結果
    "describeServices": {
      // DescribeServices ステートでの実行結果
    },
    "countTaskSets" : {
      // CountTaskSets ステートでの実行結果
    },
    //   :
    //   :
}

途中何らかエラーが発生し、作成してしまった Target Group や Task Set が削除可能であれば ecs:DeleteTaskSetelbv2:DeleteTargetGroup で消すようにしています。

補足 : CDK での Step Functions ステートマシン作成

CDK でステートマシンを作成するには従来では非常に辛いものがありましたが、ASL 定義ファイルから読み込んで構築することができるようになったのでこちらを利用します。

const stateMachine = new sfn.StateMachine(scope, 'StateMachineDeployGreen', {
  definitionBody: sfn.DefinitionBody.fromFile('./assets/state-machine/deploy-green.asl.json', {}),
  // :
  // :
});

“Manual Approval”(1回目) ステージ

本番トラフィックを Green 環境に転送するための承認をこのステージで行います。すなわちここで承認待ちになっている間、Green 環境でテストを行うことができます。ただし、CodePipeline の仕様上、待機できる期間は7日間 (上限緩和不可) になります。それ以上経過するとタイムアウトとなりパイプラインは失敗になります。

参考 : https://docs.aws.amazon.com/ja_jp/codepipeline/latest/userguide/approvals.html

アクションが承認されると、パイプラインの実行が再開されます。アクションが拒否されているか、パイプラインの到達および停止のアクションが 7 日間以内に承認または拒否されない場合は、アクションが失敗した時と同じ結果になり、パイプラインの実行は継続されません。

“Swap” ステージ

このステージでも CodePipeline から Step Functions ステートマシンを起動します。本番トラフィックを前のステージでデプロイした Green 環境に切り替えます。

ワークフローと B/G デプロイの変化の主要な部分について APNG (Animated PNG) にしてみました。2秒おきに遷移します(全 4ページ)。

Stage for Swap

以下、各番号の補足です。

  1. Choice : 前の “Deploy Green” ステージで取得した Task Set から初回デプロイかどうか分岐します
  2. DescribeRules : 初回デプロイではない場合、既存の(この時点では本番稼働中の) Target Group ARN を取得しておきます。Clean up のステージで Target Group を消す際に利用します
  3. ModifyRuleForProd : elbv2:ModifyRule を実行して、本番リスナーに設定されているリスナールールを変更し、転送先を新しい Target Group の方に変更します
    • この時点で本番トラフィックが新しい環境にルーティングされるようになります
  4. UpdateServicePrimaryTaskSet : ecs:UpdateServicePrimaryTaskSet を実行して、新しい Task Set を Primary に指定します。旧 Primary だった Tasl Set は自動的に Active に変わります

ちなみに、4番目の絵でも図示されている通り、この時点ではまだ Green 環境からの接続は可能な状態です。

補足 : Step Functions ステートマシンで出力された成果物

Step Functions ステートマシンがワークフローの最後に出力した JSON も CodeBuild と同様に Artifact として出力し、次のステージのソースとして利用することができます。

まずステートマシンからの成果物出力は CDK では以下のようにします(例えば “Deploy Green” ステージの場合 : deploy-green.ts [GitHub])。

const output = new codepipeline.Artifact('DeployGreen');
const action = new codepipeline_actions.StepFunctionInvokeAction({
  //   :
  output: output,
});

Artifact 用の S3 バケットにパイプラインの実行ごとに成果物が出力されます。ステートマシンから出力される JSON ファイルの名前は一律 output.json のようなので、次のステージで入力として参照する場合は以下のようにします(例えば “Swap” ステージの場合 : swap.ts [GitHub])。

const action = new codepipeline_actions.StepFunctionInvokeAction({
  actionName: 'Swap',
  //    :
  stateMachineInput: codepipeline_actions.StateMachineInput.filePath({
    artifact: deployGreenOutput, // "Deploy Green" ステージからの成果物(型: codepipeline.Artiface)
    fileName: '',
    location: 'output.json'
  }),
  //   :
});

“Manual Approval”(2回目) ステージ

本番トラフィック切り替え後、旧環境を削除するための承認をこのステージで行います。すなわちここで承認待ちになっている間、旧環境が維持され、本番側のリスナールールを変更することでロールバックを行うことができます(が、今回のこのデモアプリではロールバックは実装していません)。

“Clean Up” ステージ

このステージでも CodePipeline から Step Functions ステートマシンを起動します。不要になったテスト用のリスナーからの接続を閉じたり、旧環境を削除します。

ワークフローと B/G デプロイの変化の主要な部分について APNG (Animated PNG) にしてみました。2秒おきに遷移します(全 4ページ)。

Stage for Swap

以下、各番号の補足です。

  1. ModifyRuleForCleanUp : elbv2:ModifyRule を実行して、テスト用のリスナーに設定されているリスナールールを変更します。Target Group への転送から固定レスポンスに変更しています
  2. Choice : “Deploy Green” の時に取得した Task Set の数から初回デプロイかどうか分岐します。初回デプロイの場合、消すべきリソースはないのでここで完了です(“Success” ステートへ)
  3. DeleteTaskSet : ecs:DeleteTaskSet を実行して、旧本番環境だった Task Set を削除します。この時パラメータとして指定する Task Set ARN は “Deploy Green” ステージの “4. CurrentTaskSet” の時に取得したものを使います
  4. DeleteTargetGroup : elbv2:DeleteTargetGroup を実行して、旧本番環境だった Target Group を削除します。この時パラメータとして指定する Target Group ARN は “Swap” ステージの “2. DescribeRules” の時に取得したものを使います

考慮するべき点など

オートスケーリング

このデモアプリではオートスケーリングを設定していません。ドキュメントにもありますが、デプロイ中はスケールインを止めておいた方が良いでしょう。今回のデモアプリであれば、“Swap” ステージの最初にスケールインをオフにし、Primary Task Set を変更した後に、スケールインをオンにする感じになるかと思います。

注意点としてはリクエストカウント追跡(ALBRequestCountPerTarget)のような、Target Group のメトリクスをベースにしたスケーリングポリシーの場合、今回のデモアプリでは “Deploy Green” ステージで Target Group を都度作成しているので、合わせてスケーリングポリシーも作成してあげる必要があります。

以前書いた記事も参考になるかもしれません。 “ECS Blue/Green デプロイでもリクエストカウント追跡のオートスケーリングを利用したい

ロールバック

このデモアプリでは 2回目の手動承認の時に却下したとしても、ロールバックはしません。パイプラインとしては失敗の扱いになり、Green 環境が残ったままになります。自動化する場合は、パイプラインに SNS トピックへの通知設定をし、却下時には Lambda などから成果物を参照してロールバック処理を行う、といった方式になるかと思います。

手でロールバックするには例えば CLI だと以下のようになります。

  1. まずリスナールールを変更し、戻す先(旧本番環境)の Target Group を転送先に設定する
    aws elbv2 modify-rule --rule-arn ${本番リスナーに設定しているリスナールールの ARN} \
      --actions '[
        {
          "Type": "forward",
          "ForwardConfig": {
            "TargetGroups":[
              {
                "TargetGroupArn": "${戻す先の Target Group ARN}",
                "Weight": 100
              }
            ]
          }
        }
      ]'
    
  2. 続いて Task Set のステータスを Primary に戻す
    aws ecs update-service-primary-task-set --cluster ${EcsClusterName} --service ${EcsServiceName} --primary-task-set ${TaskSetId}
    

--rule-arn はマネジメントコンソールからも参照できますが、CLI であれば以下で参照できます。

aws elbv2 describe-rules --listener-arn ${リスナーの ARN}

Task Set の ID は以下で取得できます。

aws ecs describe-services --cluster ${EcsClusterName} --service ${EcsServiceName}
# Task Set の ID と ステータスだけ一覧で出すには以下のクエリを追加
#  --query 'services[*].taskSets[*].{id: id, status: status}' --output table

また、Green 環境に Task Set も残ったままになってしまうので削除するには、以下のようになります。ステータスが ACTIVE の Task Set だけ削除できます(PRIMARY のものは削除不可)。

aws ecs delete-task-set --cluster ${EcsClusterName} --service ${EcsServiceName} --task-set ${TaskSetId}

承認までの猶予期間

前述した通り CodePipeline の手動承認の猶予期間は 7日間です。それを過ぎるとタイムアウトとなりパイプラインとしては失敗になります(ドキュメント)。もっと長くしたい場合は、CodePipeline を使わずに全て Step Functions でフローを組み立てる方法もあります。

Step Functions で手動承認を実現する方法は以下のドキュメント、サイトが参考になると思います。

CodePipeline から Step Functions を呼び出した際のタイムアウトは7日間になるので(ドキュメント)、CodePipeline を使わずに Step Functions でパイプラインを構成することになるかと思います。

IaC の管理から外れてしまっているような気がするリソース

CDK 内で作成した ECS リソースや、VPC サブネット、セキュリティグループの ID や ARN を参照して Step Functions から Target Group や Task Set を作成しています。CDK というコードの中に書かれていないリソースをいくつか作成しているという点、ご注意ください。

ざっと列挙しますと、“Deploy Green” ステージにて

  • CreateTargetGroup
    • ターゲットグループを作成しています
    • ヘルスチェックの設定もここで固定的に入っています
    • (ステートマシン定義 : deploy-green.asl.json [GitHub]
  • CreateTaskSet
    • ECS Task Set を作成しています
    • キャパシティプロバイダ戦略を設定しています
    • タスクのネットワーク設定もここでしています(サブネットやセキュリティグループをここで参照しています)
    • (ステートマシン定義 : deploy-green.asl.json [GitHub]

今回のデモアプリでは CDK で 1つの Stack でデプロイしているので、それほど苦労なくコード側の変更が連動していくと思いますが、ワークロードとパイプラインを別の Stack で構成した場合などは注意が必要です。SSM パラメータストアとか使って参照するとかしても良いかもしれません。

まとめ

ECS Blue/Green デプロイでのテスト期間を CodeDeploy での 2日間よりもうちょっと長く取りたい、というモチベーションから ECS 外部デプロイとして B/G デプロイを Step Functions で実装してみました。デモアプリということで機能的に足りないところもありますが、Step Functions の AWS SDK インテグレーションを駆使して Lambda なしで実現できたのもポイントかなと思います。

本番環境でそのまま使うには機能不足かと思いますのでアイデアのベースになれば幸いです🙇‍♂️

付録

CDK でデプロイタイプ EXTERNAL の ECS Service が作成できない

CDK でデプロイタイプを外部デプロイにしてサービスを作ろうと以下のコードを書きました。

const service = new ecs.FargateService(this, 'EcsService', {
  cluster: cluster,
  taskDefinition: taskDefinition,
  desiredCount: 0,
  propagateTags: ecs.PropagatedTagSource.TASK_DEFINITION,
  serviceName: ECS_SERVICE_NAME,
  //   :
  deploymentController: {
    type: ecs.DeploymentControllerType.EXTERNAL
  },
});

cdk deploy すると以下のエラーとなりデプロイできません。

Resource handler returned message: “Invalid request provided: CreateService error: NetworkConfiguration must be null.

vpcSubnets:securityGroups: などネットワークに関するものを除いても、デバッグしてみると NetworkConfiguration がテンプレートに生成されてしまうようでした(確か ecs.BaseService でもできなかったと思います)。 仕方がないのでエスケープハッチで回避することにしました。

const cfnService = service.node.defaultChild as ecs.CfnService;
cfnService.addDeletionOverride('Properties.NetworkConfiguration');

2023-07-10 追記

CDK を 2.87.0 にバージョンアップしたら ECS Service 作成時に再びエラーが出てしまいました。

Alarm based rollback feature is only supported with ECS deployment controller. Update to ECS deployment controller and try again.

同様に Alarms の設定がテンプレートに入ってしまっていたので下記も追加して回避します。

cfnService.addDeletionOverride('Properties.DeploymentConfiguration.Alarms');

2023-07-14 追記

NetworkConfiguration の方は Issue をあげて、Pull Request も送ってみました。

Alarms の方は以下で修正されそうですね。

CDK の新しいリリースに含まれたらこちらのサンプルも変更したいと思います。

最後に・・・

この投稿は個人的なものであり、所属組織を代表するものではありません。ご了承ください。
※本サンプルは、自己責任の範囲でご利用ください。