FURYU Tech Blog - フリュー株式会社

フリュー株式会社の開発者が技術情報を発信するブログです。

ECS標準のカナリアデプロイをCDKで利用する(L2 Constructサポート前)

はじめに

こんにちは、フリューのソフトウェアエンジニアkitajimaです。ピクトリンクの開発を担当しています。

2025年10月、Amazon ECS標準のカナリアデプロイメント/リニアデプロイメント機能がサポートされました。

aws.amazon.com

つい先日、2025年7月にECS標準のブルー/グリーンデプロイメントがサポートされたところです。 CodeDeployをデプロイコントローラーとした各種デプロイ戦略が、徐々にECS標準に置き換わっていく流れを感じます。

今回はカナリアデプロイメントに注目し、L2 Constructに実装される前のCDKで利用した事例を紹介します。

Amazon ECS カナリアデプロイメントとは

カナリアデプロイメントは、新バージョンへのトラフィックを段階的に増やしていくデプロイ戦略です。

Amazon ECSにおいては、最初に5%といった小さな割合(Canary percentage)のトラフィックだけを新バージョンに流し、一定時間(Canary bake time)問題がないことを確認してから残りのトラフィックを切り替えます。 さらに、新バージョンに100%切り替えた後も一定時間(Deployment bake time)様子を見て、問題があればデプロイのロールバックをして旧バージョンに戻すことができます。

ECS標準のカナリアデプロイメントの設定オプション

また、デプロイをロールバックするための複数の方法が提供されています。

  • ECSのデプロイサーキットブレーカー
    • ECSタスクの起動に失敗し、しばらくサービスが安定状態にならないならロールバック(従来通り)
  • CloudWatch アラーム
    • 監視したい条件でアラームを作成しておき、発報されたらロールバック
  • ライフサイクルフック
    • 「本番トラフィックシフト後」など、各種ライフサイクルで実行されるLambda関数を用意できる。失敗したらロールバック
  • 手動
    • マネジメントコンソール ECSサービスの「ロールバック」ボタンで手動でロールバックを実行

デプロイをロールバックするための設定

docs.aws.amazon.com

CDKで実装したい場合

ECSマネジメントコンソールではデプロイ戦略「Canary」が選択可能です。

4つのデプロイ戦略が選べるようになったマネジメントコンソール

しかし、本記事執筆現在の最新であるCDK v2.222.0ではまだサポートされていません。

よって、CDKで実装したい場合現時点では2つの選択肢が考えられます。

A. CDK L2 Constructでのサポートを待つ

特に急がない限り、こちらをおすすめします。 ブルー/グリーンデプロイメントは2025年7月に発表され、約1ヶ月後の8月にCDKでサポートされました。

また、L2 ConstructをサポートするPRも既に上げていただいており、近いうちにリリースされると思います。

feat(ecs): add built-in Linear and Canary deployments by mazyu36 · Pull Request #35981 · aws/aws-cdk · GitHub

B. CDK上でCFnのプロパティを上書きする形で実装する

ちょうどECSでカナリアデプロイメントを試したい事情があったため、CDK上でCloudFormationのプロパティを上書きする形で検証してみました。

必要なリソース

カナリアデプロイメントを実現するには、以下のリソースが必要でした。

代替ターゲットグループ(Alternate Target Group)

通常のターゲットグループに加えて、新バージョン用の代替ターゲットグループが必要です。

1点注意として、代替ターゲットグループはデプロイ時の一時的なものではありません。 1度デプロイすると代替ターゲットグループが利用され続けます。デプロイごとに利用されるターゲットグループが入れ替わり続けるわけです。

デプロイ完了後も代替ターゲットグループ(alt)への転送が100%のままである

リスナールール

明示的なリスナールールを作成し、weightedForwardを使って2つのターゲットグループを指定します。 ECSがルーティングの重みをデプロイイベント中に動的に変更してくれるおかげで、カナリアデプロイメントが実現される仕組みです。

IAMロール

ECSにルーティングの重みをいじってもらえるように、IAMロールが必要です。今回はAmazonECSInfrastructureRolePolicyForLoadBalancersマネージドポリシーをアタッチしましたが、実際は最小権限の原則に基づいてカスタムポリシーを作成するのが望ましいです。

ECSサービスの設定

ECSサービスに対して、以下のプロパティを設定します。 それぞれCloudFormationのL1リソースプロパティを直接上書きする形で設定します。

実装

実装は専用のスタックとして切り出しました。 propsで既存のECSサービスやALBリスナー、ターゲットグループを受け取る形にしています。

export class CanaryDeploymentResourcesStack extends Stack {
    constructor(scope: Construct, id: string, props: Props) {
        super(scope, id, props);

        // 代替ターゲットグループ
        const alternateTargetGroup = new ApplicationTargetGroup(
            this,
            `sample-ecs-alt-targetGroup`,
            {
                vpc: Vpc.fromVpcAttributes(this, ...),
                targetGroupName: `sample-ecs-alt-tg`,
            }
        );

        // リスナールール
        const listenerRule = new ApplicationListenerRule(
            this,
            `sample-listener-rule`,
            {
                listener: props.listener,
                action: ListenerAction.weightedForward([
                    {
                        targetGroup: props.originalTargetGroup,
                        weight: 100, // 振る舞いに変化が無いように100%を元のTGに割り振るが、ECSがデプロイ時に動的に変更してくれる
                    },
                    {
                        targetGroup: alternateTargetGroup,
                        weight: 0, // 同上
                    },
                ]),
                priority: 1,
                conditions: [
                    // 必須項目だが特に条件は不要なため、すべてのパスにマッチさせる
                    ListenerCondition.pathPatterns(['/*']),
                ],
            }
        );

        // ECSがELBを操作するためのIAMロール
        const canaryDeploymentExecutionRole = new Role(
            this,
            `sample-canary-deploy-role`,
            {
                assumedBy: new ServicePrincipal("ecs.amazonaws.com"),
                managedPolicies: [
                    ManagedPolicy.fromAwsManagedPolicyName(
                        "AmazonECSInfrastructureRolePolicyForLoadBalancers"
                    ),
                ],
            }
        );

        // ECSサービスのCFnリソースを取得してプロパティを上書きする
        const cfnService = props.ecsService.node.defaultChild as CfnService;

        if (props.enableCanaryDeployment) {
            cfnService.addPropertyOverride('DeploymentConfiguration', {
                'Strategy': 'CANARY',
                'BakeTimeInMinutes': 10,
                'CanaryConfiguration': {
                    'CanaryBakeTimeInMinutes': 10,
                    'CanaryPercent': 5,
                },
            });
            cfnService.addPropertyOverride('LoadBalancers.0.AdvancedConfiguration', {
                'AlternateTargetGroupArn': alternateTargetGroup.targetGroupArn,
                'ProductionListenerRule': listenerRule.listenerRuleArn,
                'RoleArn': canaryDeploymentExecutionRole.roleArn,
            });
        }
    }
}

なお、リリース担当者がデプロイ戦略を選べるように、Context変数でカナリアデプロイメントの有効化フラグを受け取るようにしました。

const enableCanaryDeployment = app.node.tryGetContext('enableCanaryDeployment') === 'true';

new CanaryDeploymentResourcesStack(app, `${env}-${projectName}-canary-deployment-resources-stack`, {
    // ...その他のプロパティ
    enableCanaryDeployment: enableCanaryDeployment,
});

さいごに

ECS標準のカナリアデプロイメントをCDKで利用してみました。CDKでのL2 Constructのサポートはまだですが、CloudFormationのプロパティを直接上書きすることで実現できました。

ただ、正直なところCDKでのサポートを待つのが一番楽で安全だと思います。最新の機能を先取りできるのは良い反面、L2 Constructへのリファクタリングも後々やりたくなってきます。 どうしてもCDKでのサポートが待てない場合は、ぜひ参考にしてみてください。