ECS が VPC Lattice 経由で公開されているサービスの blue/green デプロイに対応したので試してみた
はじめに
2026年10月2日の告知で、Amazon ECS の VPC Lattice 対応に、組み込みの blue/green、linear、canary が加わりました。新規・既存どちらの ECS サービスでも有効にできます。
ECS サービスに Lattice のターゲットグループを設定するだけでよく、ALB を別に作る必要はありません。一方、CodeDeploy(CODE_DEPLOY デプロイコントローラー)による blue/green は、Lattice では未対応です。ドキュメントには、linear と canary も blue/green と同じ Lattice のリソースで動くと書かれています。
この記事では、ALB を置かずに Nginx の Fargate サービスを作り、v1 から v2 へ blue/green デプロイしました。デプロイ中に ECS が Lattice のルールの重みをどう書き換え、クライアントの応答がどう変わるかを時系列で確認します。
Lattice と ECS の構成
Lattice 側に用意するもの
Lattice 側には次を用意しました。作成コマンドは、公式ドキュメントにある同じ構成の例を使いました。
- ターゲットグループ2つ(IP タイプ、HTTP:80。どちらも ACTIVE にしておく)
- Lattice サービスとリスナー(既定アクションは固定の 404)
- 本番ルール(パス
/のプレフィックス一致) - テストルール(ヘッダー
X-Environment: test、優先度 10)
ルールのアクションは forward のみで、fixedResponse のルールは ECS が拒否します。どちらのルールにも2つのターゲットグループを含め、現在のトラフィックを受ける側を重み 100、もう一方を 0 にします。テストルールは本番ルールとは別のリクエストにマッチさせ、先に評価されるよう優先度の数字を小さくします。
デプロイ中は ECS がルールの forward を書き換えます。外部から変更するとデプロイが失敗するため、ECS に任せます。2つのターゲットグループはデプロイごとに役割が入れ替わります。最初の段階的デプロイでは、本番ルールが重み 100 で転送している側(今回は targetGroupArn に指定した側)ではなく、もう一方へトラフィックを移します。以降、targetGroupArn に指定した側を TG-A、代替側を TG-B と呼びます。
タスクのセキュリティグループで受信を許可したのは、Lattice のマネージドプレフィックスリストからのポート80だけです。プレフィックスリスト名は com.amazonaws.<region>.vpc-lattice で、<region> は利用するリージョン名に置き換えます。
ECS 側の設定
ECS 側では、vpcLatticeConfigurations に advancedConfiguration を追加します。roleArn には、ECS が VPC Lattice のリソースを管理するためのインフラストラクチャロールの ARN を指定します。alternateTargetGroupArn には2つ目のターゲットグループを、productionListenerRule と testListenerRule には本番・テストのルールを指定します。deploymentConfiguration の strategy は BLUE_GREEN にします。
rolling で動いているサービスの切り替え方は、公式ドキュメントに書かれています。advancedConfiguration は、strategy を変更する更新と同時か、それ以前の更新で追加しておきます。今回は新規にサービスを作る create-service で確かめました。
次の JSON を svc.json として保存し、aws ecs create-service --cli-input-json file://svc.json に渡しました。ID は例示値に置き換えています。
{
"cluster": "lattice-bg",
"serviceName": "lattice-bg-web",
"taskDefinition": "lattice-bg-nginx:1",
"desiredCount": 2,
"launchType": "FARGATE",
"networkConfiguration": {
"awsvpcConfiguration": {
"subnets": [
"subnet-0123456789abcdef0",
"subnet-0fedcba9876543210"
],
"securityGroups": [
"sg-0123456789abcdef0"
],
"assignPublicIp": "ENABLED"
}
},
"vpcLatticeConfigurations": [
{
"roleArn": "arn:aws:iam::123456789012:role/lattice-bg-ecs-infra",
"targetGroupArn": "arn:aws:vpc-lattice:ap-northeast-1:123456789012:targetgroup/tg-0123456789abcdef0",
"portName": "web",
"advancedConfiguration": {
"alternateTargetGroupArn": "arn:aws:vpc-lattice:ap-northeast-1:123456789012:targetgroup/tg-0fedcba9876543210",
"productionListenerRule": "arn:aws:vpc-lattice:ap-northeast-1:123456789012:service/svc-0123456789abcdef0/listener/listener-0123456789abcdef0/rule/rule-0123456789abcdef0",
"testListenerRule": "arn:aws:vpc-lattice:ap-northeast-1:123456789012:service/svc-0123456789abcdef0/listener/listener-0123456789abcdef0/rule/rule-0fedcba9876543210"
}
}
],
"deploymentController": {
"type": "ECS"
},
"deploymentConfiguration": {
"strategy": "BLUE_GREEN",
"maximumPercent": 200,
"minimumHealthyPercent": 100,
"bakeTimeInMinutes": 2
}
}
タスク定義とクライアント
タスク定義には Nginx 公式イメージを使いました。コンテナは起動時に、ECS のタスクメタデータ(ECS_CONTAINER_METADATA_URI_V4/task)からタスク ARN、リビジョン、アベイラビリティゾーンを取得します。それを index.html に書き出し、その後 Nginx を起動します。v1 は nginx:1.27 と APP_VERSION=v1、v2 は nginx:1.28 と APP_VERSION=v2 で、それ以外は同じです。v2 の全文を td2.json として保存し、aws ecs register-task-definition --cli-input-json file://td2.json で登録しました。
タスク定義 v2(td2.json)
{
"family": "lattice-bg-nginx",
"networkMode": "awsvpc",
"requiresCompatibilities": [
"FARGATE"
],
"cpu": "256",
"memory": "512",
"executionRoleArn": "arn:aws:iam::123456789012:role/lattice-bg-task-exec",
"containerDefinitions": [
{
"name": "web",
"image": "public.ecr.aws/docker/library/nginx:1.28",
"essential": true,
"entryPoint": [
"/bin/sh",
"-c"
],
"command": [
"M=$(curl -s \"$ECS_CONTAINER_METADATA_URI_V4/task\")\ng(){ echo \"$M\" | sed -n \"s/.*\\\"$1\\\":\\\"\\([^\\\"]*\\)\\\".*/\\1/p\" | head -1; }\nprintf 'app_version=%s\\ntask_arn=%s\\ntaskdef_revision=%s\\navailability_zone=%s\\ncontainer_ip=%s\\nnginx=%s\\n' \"$APP_VERSION\" \"$(g TaskARN)\" \"$(g Revision)\" \"$(g AvailabilityZone)\" \"$(hostname -i)\" \"$(nginx -v 2>&1)\" > /usr/share/nginx/html/index.html\nexec nginx -g 'daemon off;'\n"
],
"portMappings": [
{
"name": "web",
"containerPort": 80,
"protocol": "tcp",
"appProtocol": "http"
}
],
"environment": [
{
"name": "APP_VERSION",
"value": "v2"
}
],
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"awslogs-group": "/lattice-bg",
"awslogs-region": "ap-northeast-1",
"awslogs-stream-prefix": "web"
}
}
}
]
}
Lattice のサービスは、サービスネットワークまたは VPC 関連付けを通してクライアントから呼び出します。今回のクライアントは、サービスネットワークを関連付けた VPC(タスクと同じ VPC)に置いた Lambda です。ヘッダーなしの本番経路と X-Environment: test を付けたテスト経路の2本に、約13秒間隔でリクエストを送りました。
環境は ap-northeast-1 で、Fargate 0.25 vCPU / 0.5 GB の2タスク、ターゲットグループのヘルスチェック間隔は10秒です。
初回のサービス作成
デプロイの完了は、describe-service-deployments の status が SUCCESSFUL になったことで判断しました。以下の時刻は、いずれも記録した時点の時刻です。
新規に作ったサービスでも、最初のデプロイは blue/green として動きました。サービスは 00:41:31 に作成しました。タスク2つは、targetGroupArn に指定した TG-A ではなく代替の TG-B に登録され、HEALTHY になりました。2つとも HEALTHY になったのは 00:42:45 で、TG-A は最初に記録した 00:42:05 の時点から空でした。
ECS サービスの rolloutState(describe-services)は、00:43:25 の時点で COMPLETED でした。その時点でも、本番ルールの重みは作成時のまま TG-A 100 / TG-B 0 でした。最初にステージを観測した 00:45:49 の時点では POST_SCALE_UP でした(それより前のステージ遷移の記録はありません)。その後、TEST_TRAFFIC_SHIFT(00:46:39)、PRODUCTION_TRAFFIC_SHIFT(00:48:18)を観測しました。本番ルールが TG-A 0 / TG-B 100 に変わったのは 00:48:20 で、サービスデプロイが SUCCESSFUL になったのは 00:49:55 でした。
v1 から v2 へのデプロイ
update-service で v2 のタスク定義を渡しました。bakeTimeInMinutes は 2 のままです。v1 のタスクは TG-B に登録されており、v2 のタスクは TG-A に登録されました。
次の表の時刻は 2026-10-04 の JST です。ECS のステージの時刻は、describe-service-deployments を4秒間隔で取得し、各ステージを最初に観測した時刻です。ルールの重みとクライアントの応答は、Lambda のリクエストと同時に約13秒間隔で記録したものです。
| 時刻 | ECS のステージ | Lattice とクライアントで観測したこと |
|---|---|---|
| 01:12:39 | (update-service を実行) |
本番・テストとも v1 が応答 |
| 01:12:53 | SCALE_UP | v2 のタスクが TG-A に登録される(1件目は 01:13:19、2件とも HEALTHY になったのは 01:13:59) |
| 01:14:07 | POST_SCALE_UP | 本番・テストとも v1 が応答 |
| 01:17:16 | TEST_TRAFFIC_SHIFT | テストルールが TG-A 100 / TG-B 0 に変わる(01:17:19)。テスト経路の最初の v2 応答は 01:17:22。本番経路は v1 のまま |
| 01:18:51 | POST_TEST_TRAFFIC_SHIFT | テスト経路は v2、本番経路は v1 |
| 01:18:56 | PRODUCTION_TRAFFIC_SHIFT | 本番ルールが TG-A 100 / TG-B 0 に変わる(01:19:05)。本番経路の最初の v2 応答は 01:19:08 |
| 01:20:31 | BAKE_TIME | 本番・テストとも v2 が応答 |
| 01:22:33 | CLEAN_UP | 本番・テストとも v2 が応答 |
| 01:22:38 | SUCCESSFUL | TG-B は 01:23:33 の時点で DRAINING、01:23:46 の時点で空 |
テスト経路で初めて v2 の応答が返った 01:17:22 から、本番経路で初めて v2 の応答が返った 01:19:08 まで、1分46秒ありました。
update-service の実行(01:12:39)からサービスデプロイの完了(API が返した完了時刻 01:22:37)までは9分58秒でした。PRIMARY の rolloutState が COMPLETED と記録されたのは、SUCCESSFUL を観測した 01:22:38 より後の 01:24:26 です。
ステージ間の待ち時間は、POST_SCALE_UP から TEST_TRAFFIC_SHIFT までが3分9秒、TEST_TRAFFIC_SHIFT から POST_TEST_TRAFFIC_SHIFT までと PRODUCTION_TRAFFIC_SHIFT から BAKE_TIME までがどちらも1分35秒、BAKE_TIME から CLEAN_UP までが2分2秒でした。これらは、公式ドキュメントに書かれた POST_SCALE_UP 後の約3分、各重み変更後の約90秒、設定した bakeTimeInMinutes の2分に対応します。
応答本文の抜粋を v1 と v2 で1件ずつ示します(アカウントID、タスクID、コンテナの IP アドレスは置換しています)。
app_version=v1
task_arn=arn:aws:ecs:ap-northeast-1:<account-id>:task/lattice-bg/<task-id>
taskdef_revision=1
availability_zone=ap-northeast-1a
container_ip=<container-ip>
nginx=nginx version: nginx/1.27.5
app_version=v2
task_arn=arn:aws:ecs:ap-northeast-1:<account-id>:task/lattice-bg/<task-id>
taskdef_revision=2
availability_zone=ap-northeast-1c
container_ip=<container-ip>
nginx=nginx version: nginx/1.28.3
約13秒間隔で本番とテストに各56回(計112回)送ったリクエストは、すべて HTTP 200 で、エラー応答や接続エラーはありませんでした。本番経路の応答に含まれていたタスク ARN は、v1 が2つ、v2 が2つでした。
まとめ
2025年7月にリリースされた ECS の組み込み blue/green デプロイが、今回のアップデートで Lattice 連携にも対応しました。
ECS の組み込みデプロイは、モニタリングの強化など改善が進み、利用しやすくなっています。
Lattice を使う ECS サービスで、blue/green のためだけに ALB を併用していた方は、今回のアップデートをぜひお試しください。





