はじめに
ECS Service Connectとは
AWS Cloud Mapが提供する名前空間を使用して論理名でECSのサービス(を構成するECSタスク)間を接続することができる機能です。
Service Connectを設定したサービスを作成、または更新するとECSタスクにService Connectプロキシがsidecarとしてデプロイされます。
Service Connectプロキシはアプリケーションからの論理名による他のサービスへの接続を仲介し、サービスを構成するタスク群に対して負荷分散を行います。論理名とタスクのIPアドレスとの紐づけはAWS Cloud Mapで管理されます。
ECS Service ConnectのBlue/Green
ECSではかねてからCodeDeployでBlue/Greenデプロイを行うことができました。しかし、Service Connectには対応しておらず採用の障壁となっていました。そんな中、2025/7にECSネイティブなBlue/Green機能がリリースされ、Service ConnectでもBlue/Greenデプロイが可能になりました。
ECSの従来のBlue/GreenデプロイはELBの機能を利用したもの(Listenerを二つ用意し、Weightの割合を変化させる)でした。それに対して、Service ConnectのBlue/Greenデプロイがどのように機能するのかイメージできなかったので検証してみました。
検証
1. 初期状態
まず、検証した構成について説明します。ECSクラスタで2つのサービスが実行されています。
nginx-server
1つ目のサービスはnginx-server
です。このサービスはService Connectの「クライアントとサーバー」として設定しています。Service Connectのクライアントからhttp://my-app
にアクセスするとProxy経由でBlueのタスクに接続されます。
Blueのタスクは"Blue"という文字列をHTTPレスポンスとして返します。
HTTPレスポンス
<html><body><h1 style=\"color: blue; text-align: center; font-size: 48px;\">Blue</h1></body></html>
Cloud Mapを見てみるとサービス属性が2つ設定されています。AWS_ECS_ROUTE_PROD
とAWS_ECS_ROUTE_TEST
です。前者はヘッダなしで接続する場合、後者はx-canary-test: beta-version
というヘッダを付与して接続する場合のルールです(ヘッダのkey/valueは任意で設定可能です)。現在は両方Blueのタスクに紐づいています。
nginx-client
2つ目のサービスはnginx-client
です。このサービスはService Connectの「クライアント」として設定しています。
http://my-app
に対して5秒おきにヘッダなし/あり両方のパターンで接続するように設定しています。下図はサービスのログです。ヘッダなしの接続が本番トラフィック
、ヘッダありの接続がテストトラフィック
です。現在は両方Blueのタスクに接続しています。
hook-function
検証構成の右上にあるのはECSサービスデプロイのLifeCyclehook用のLambda関数です。ECSのBlue/Greenデプロイの各段階で呼び出すことが可能で、レスポンスによってデプロイの成否を制御することができます。現在は常に成功hookStatus: SUCCEDED
を返すよう設定しています。
import json
import boto3
def handler(event, context):
print(f"{json.dumps(event)}")
print(event['lifecycleStage'])
return {
'hookStatus': 'SUCCEEDED'
#'hookStatus': 'FAILED'
}
2. サービスの更新
ではBlue/Greenの挙動を見ていきましょう。タスク定義を新たに作成し、Blue
ではなくGreen
という文字列をレスポンスとして返すよう設定します。そのタスク定義を用いてサービス: nginx-server
を更新します。
2-1. Hook: PRE_SCALE_UP (18:25:03)
まず起こるのはLambdaの実行です。新しいタスクがデプロイされる前に実行されます。LifeCycleのステージはPRE_SCALE_UP
です。
Lambda Log:
YYYY-MM-DDT09:25:03.383Z
{"executionDetails": {"testTrafficWeights": {}, "productionTrafficWeights": {}, "serviceArn": "arn:aws:ecs:us-west-2:123456789012:service/blue-green-cluster/nginx-server", "targetServiceRevisionArn": "arn:aws:ecs:us-west-2:123456789012:service-revision/blue-green-cluster/nginx-server/1573819219887221329"}, "executionId": "52761202-4fbb-4468-b574-aa4591649806", "lifecycleStage": "PRE_SCALE_UP", "resourceArn": "arn:aws:ecs:us-west-2:123456789012:service-deployment/blue-green-cluster/nginx-server/p2DSH1QN1B1P5c7hsazwf"}
YYYY-MM-DDT09:25:03.383Z
PRE_SCALE_UP
2-2. Greenタスク起動(18:26:24)
そして次に新しいタスク定義に基づくGreenのタスクが起動します。
ECS Event Log:
YYYY年MM月DD日 18:25 (UTC+9:00)
service nginx-server has started 1 tasks: task a3e692948db94f7e9eca4832ad4002a2
nginx-server Log:
YYYY年MM月DD日 18:26
2025/08/06 09:26:24 [notice] 1#1: start worker process 7
2025/08/06 09:26:24 [notice] 1#1: start worker process 8
2025/08/06 09:26:24 [notice] 1#1: using the "epoll" event method
2-3. Hook: POST_SCALE_UP (18:27:09)
Greenタスクの起動が完了すると再度Lambdaが実行されます。LifeCycleのステージはPOST_SCALE_UP
です。
Lambda Log:
YYYY-MM-DDT09:27:09.129Z
{"executionDetails": {"testTrafficWeights": {}, "productionTrafficWeights": {}, "serviceArn": "arn:aws:ecs:us-west-2:123456789012:service/blue-green-cluster/nginx-server", "targetServiceRevisionArn": "arn:aws:ecs:us-west-2:123456789012:service-revision/blue-green-cluster/nginx-server/1573819219887221329"}, "executionId": "2bbdca3f-078f-49c8-b4bb-2fd0a82a7649", "lifecycleStage": "POST_SCALE_UP", "resourceArn": "arn:aws:ecs:us-west-2:123456789012:service-deployment/blue-green-cluster/nginx-server/p2DSH1QN1B1P5c7hsazwf"}
YYYY-MM-DDT09:27:09.129Z
POST_SCALE_UP
3. テストトラフィックのルーティング
次の段階ではテストトラフィックのルーティングが変更されます。
3-1. サービス属性の更新(18:27:25)
まずCloud Mapのサービス属性AWS_ECS_ROUTE_TEST
の値が変更されます。タスクの紐づきがBlueからGreenに変更されました。
Service Discovery Log:
{
"eventTime": "YYYY-MM-DDT09:27:25Z",
"eventSource": "servicediscovery.amazonaws.com",
"eventName": "UpdateServiceAttributes",
"requestParameters": {
"serviceId": "srv-bhdknysg5s3cyjop",
"attributes": {
"AWS_ECS_ROUTE_TEST": "{\"name\":\"test\",\"type\":\"HTTP\",\"priority\":0,\"match\":{\"headers\":[{\"name\":\"x-canary-test\",\"value\":{\"exact\":\"beta-version\"}}]},\"weightedTargets\":[{\"weight\":100,\"target\":[\"arn:aws:ecs:us-west-2:123456789012:service-revision/blue-green-cluster/nginx-server/1573819219887221329\"]}]}"
}
}
}
3-2. Hook: TEST_TRAFFIC_SHIFT(18:27:25)
サービス属性の変更とほぼ時を同じくしてLambdaが実行されます。LifeCycleのステージはTEST_TRAFFIC_SHIFT
です。
Lambda Log:
YYYY-MM-DDT09:27:25.050Z
{"executionDetails": {"testTrafficWeights": {"arn:aws:ecs:us-west-2:123456789012:service-revision/blue-green-cluster/nginx-server/5592795917093244871": 0, "arn:aws:ecs:us-west-2:123456789012:service-revision/blue-green-cluster/nginx-server/1573819219887221329": 100}, "productionTrafficWeights": {}, "serviceArn": "arn:aws:ecs:us-west-2:123456789012:service/blue-green-cluster/nginx-server", "targetServiceRevisionArn": "arn:aws:ecs:us-west-2:123456789012:service-revision/blue-green-cluster/nginx-server/1573819219887221329"}, "executionId": "d96e0dc6-26e9-4c41-a401-225cc072dbea", "lifecycleStage": "TEST_TRAFFIC_SHIFT", "resourceArn": "arn:aws:ecs:us-west-2:123456789012:service-deployment/blue-green-cluster/nginx-server/p2DSH1QN1B1P5c7hsazwf"}
YYYY-MM-DDT09:27:25.050Z
TEST_TRAFFIC_SHIFT
3-3. テストトラフィックのルーティング変更(18:27:33)
サービス属性の更新から8秒後にnginx-client
のテストトラフィックがGreenにフォワードされはじめました。
nginx-client Log:
YYYY-MM-DD 09:27:28
本番トラフィック: Blue
テストトラフィック: Blue
YYYY-MM-DD 09:27:33
本番トラフィック: Blue
テストトラフィック: Green
nginx-server Log:
YYYY年MM月DD日 18:27
127.0.0.1 - - [06/Aug/2025:09:27:33 +0000] "GET / HTTP/1.1" 200 100 "-" "curl/8.3.0" "-"
8秒ほどのタイムラグはいつもあるものなのかな?と、別途nginx-client
のタスクを10個に増やし、1s間隔でルーティングの変化を確認する検証をしてみました。
結果、すべてのタスクでタイムラグが確認できました。また、ルーティングが変更されるまでの時間はタスクによって最大14sの差がありました。
各々のタスク(のService Connect Proxy)がCloud Mapと非同期的に設定を同期していそうです。
表. サービス属性が更新されてからルーティングが変更されるまでの時間
タスクNo. | #01 | #02 | #03 | #04 | #05 | #06 | #07 | #08 | #09 | #10 |
---|---|---|---|---|---|---|---|---|---|---|
時間 | 08s | 12s | 13s | 16s | 17s | 04s | 03s | 11s | 13s | 15s |
4. テストトラフィックの移行完了
サービス属性の更新からおよそ40秒経った頃にLambdaが実行されました。LifeCycleのステージはPOST_TEST_TRAFFIC_SHIFT
で、テストトラフィックが完全に移行したことを示しています。
4-1. Hook: POST_TEST_TRAFFIC_SHIFT(18:28:11)
Lambda Log:
YYYY-MM-DDT09:28:11.220Z
{"executionDetails": {"testTrafficWeights": {}, "productionTrafficWeights": {}, "serviceArn": "arn:aws:ecs:us-west-2:123456789012:service/blue-green-cluster/nginx-server", "targetServiceRevisionArn": "arn:aws:ecs:us-west-2:123456789012:service-revision/blue-green-cluster/nginx-server/1573819219887221329"}, "executionId": "ad6df153-cfe7-4442-9574-ffdfe06454a7", "lifecycleStage": "POST_TEST_TRAFFIC_SHIFT", "resourceArn": "arn:aws:ecs:us-west-2:123456789012:service-deployment/blue-green-cluster/nginx-server/p2DSH1QN1B1P5c7hsazwf"}
YYYY-MM-DDT09:28:11.220Z
POST_TEST_TRAFFIC_SHIFT
5. 本番トラフィックのルーティング
本番トラフィックのルーティングは先の「3.テストトラフィックのルーティング」と同じ形で進みます。
5-1. サービス属性の更新(18:28:26)
CloudMapのAWS_ECS_ROUTE_PROD
のサービス属性が更新され、紐づくタスクがBlueからGreenに変わります。
Service Discovery Log:
{
"eventTime": "YYYY-MM-DDT09:28:26Z",
"eventSource": "servicediscovery.amazonaws.com",
"eventName": "UpdateServiceAttributes",
"requestParameters": {
"serviceId": "srv-bhdknysg5s3cyjop",
"attributes": {
"AWS_ECS_ROUTE_PROD": "{\"name\":\"prod\",\"type\":\"HTTP\",\"priority\":1,\"weightedTargets\":[{\"weight\":100,\"target\":[\"arn:aws:ecs:us-west-2:123456789012:service-revision/blue-green-cluster/nginx-server/1573819219887221329\"]}]}"
}
}
}
5-2. Hook: PRODUCTION_TRAFFIC_SHIFT(18:28:27)
サービス属性の変更とほぼ時を同じくしてLambdaが実行されます。LifeCycleのステージはPRODUCTION_TRAFFIC_SHIFT
です。
Lambda Log:
YYYY-MM-DDT09:28:27.002Z
{"executionDetails": {"testTrafficWeights": {}, "productionTrafficWeights": {"arn:aws:ecs:us-west-2:123456789012:service-revision/blue-green-cluster/nginx-server/5592795917093244871": 0, "arn:aws:ecs:us-west-2:123456789012:service-revision/blue-green-cluster/nginx-server/1573819219887221329": 100}, "serviceArn": "arn:aws:ecs:us-west-2:123456789012:service/blue-green-cluster/nginx-server", "targetServiceRevisionArn": "arn:aws:ecs:us-west-2:123456789012:service-revision/blue-green-cluster/nginx-server/1573819219887221329"}, "executionId": "abc779f1-7462-4727-beba-ebfeec44e41f", "lifecycleStage": "PRODUCTION_TRAFFIC_SHIFT", "resourceArn": "arn:aws:ecs:us-west-2:123456789012:service-deployment/blue-green-cluster/nginx-server/p2DSH1QN1B1P5c7hsazwf"}
YYYY-MM-DDT09:28:27.002Z
PRODUCTION_TRAFFIC_SHIFT
5-3. 本番トラフィックのルーティング変更(18:28:38)
サービス属性の変更から11秒して、nginx-clientの本番トラフィックの接続先がGreenのタスクに変わりました。
nginx-client Log:
## Client Log
YYYY-MM-DD 09:28:38
本番トラフィック: Green
テストトラフィック: Green
YYYY-MM-DD 09:28:33
本番トラフィック: Blue
テストトラフィック: Green
6. ベイク期間
ECSの本番トラフィックがすべて切り替わると、ベイク期間に入る。ベイク期間はBlueとGreenの環境が並行して存在する期間である。ベイク期間の長さは任意に設定することができ、ここでは3分に設定しています。
6-1. Hook: POST_PRODUCTION_TRAFFIC_SHIFT(18:29:13)
ECSの本番トラフィックがすべて切り替わると、POST_PRODUCTION_TRAFFIC_SHIFT
のライフサイクルステージがキックされLambdaが起動する。
Lambda Log:
YYYY-MM-DDT09:29:13.527Z
{
"executionId": "603a3cb8-7203-4291-a853-d8997818e0e9",
"lifecycleStage": "POST_PRODUCTION_TRAFFIC_SHIFT"
}
7. Blue環境の削除
7-1. インスタンスの登録解除(18:32:31)
Blue環境のタスクがService Discoveryから登録解除され、新しいリクエストが送信されないようになります。この操作により、Blue環境は完全にトラフィックから切り離されます。
Service Discovery Log:
{
"eventTime": "YYYY-MM-DDT09:32:31Z",
"eventSource": "servicediscovery.amazonaws.com",
"eventName": "DeregisterInstance",
"awsRegion": "us-west-2",
"sourceIPAddress": "ecs.amazonaws.com",
"userAgent": "ecs.amazonaws.com",
"requestParameters": {
"serviceId": "srv-bhdknysg5s3cyjop",
"instanceId": "f6d38933c38847b79a670d71c25d75a8"
},
}
7-2. Blueタスク停止(18:32:55)
最後にBlueのタスクが停止して完了です。
ECS Event Log:
YYYY年MM月DD日 18:32 (UTC+9:00)
service nginx-server has stopped 1 running tasks: task f6d38933c38847b79a670d71c25d75a8
YYYY年MM月DD日 18:34 (UTC+9:00)
service nginx-server deployment ecs-svc/1573819219887221329 deployment completed.
service nginx-server has reached a steady state.
nginx-server Log:
YYYY年MM月DD日 18:32
2025/08/06 09:32:55 [notice] 9#9: exit
検証(ロールバック発生時)
せっかくなのでロールバックの時の挙動も確認しようと思います。先の図の6-1. POST_PRODUCTION_TRAFFIC_SHIFT
で失敗を意味するhookStatus: FAILED
がLambdaから返ってきたとします。
7f. 本番トラフィックのロールバック
では説明していきます。hookStatus: FAILED
が返却されるとECSはロールバックを開始します。
ロールバックが開始されたことはマネコンから確認可能です。
7f-1. サービス属性の変更(00:18:44)
まずCloudMapのAWS_ECS_ROUTE_PROD
のサービス属性が更新され、紐づくタスクがGreenからBlueに戻ります。
Service Discovery Log:
{
"eventTime": "YYYY-MM-DDT15:18:44Z",
"eventSource": "servicediscovery.amazonaws.com",
"eventName": "UpdateServiceAttributes",
"awsRegion": "us-west-2",
"sourceIPAddress": "ecs.amazonaws.com",
"userAgent": "ecs.amazonaws.com",
"requestParameters": {
"serviceId": "srv-bhdknysg5s3cyjop",
"attributes": {
"AWS_ECS_ROUTE_PROD": "{\"name\":\"prod\",\"type\":\"HTTP\",\"priority\":1,\"weightedTargets\":[{\"weight\":100,\"target\":[\"arn:aws:ecs:us-west-2:551312704990:service-revision/blue-green-cluster/nginx-server/7308461668976403387\"]}]}"
}
}
7f-2. hook: PRODUCTION_TRAFFIC_SHIFT(00:18:44)
サービス属性の変更とほぼ時を同じくしてLambdaが実行されます。LifeCycleのステージはPRODUCTION_TRAFFIC_SHIFT
です。
Lambda Log:
YYYY-MM-DDT15:18:44.160Z
{"executionDetails": {"testTrafficWeights": {}, "productionTrafficWeights": {"arn:aws:ecs:us-west-2:551312704990:service-revision/blue-green-cluster/nginx-server/9126904758940771806": 0, "arn:aws:ecs:us-west-2:551312704990:service-revision/blue-green-cluster/nginx-server/7308461668976403387": 100}, "serviceArn": "arn:aws:ecs:us-west-2:551312704990:service/blue-green-cluster/nginx-server", "targetServiceRevisionArn": "arn:aws:ecs:us-west-2:551312704990:service-revision/blue-green-cluster/nginx-server/9126904758940771806"}, "executionId": "7f3302f7-250d-4e95-be64-d76ce2720b3d", "lifecycleStage": "PRODUCTION_TRAFFIC_SHIFT", "resourceArn": "arn:aws:ecs:us-west-2:551312704990:service-deployment/blue-green-cluster/nginx-server/JnAjY1hkpfbWXUdPp-DK5"}
YYYY-MM-DDT15:18:44.160Z
PRODUCTION_TRAFFIC_SHIFT
7f-3. 本番トラフィックのルーティング変更(00:18:57)
サービス属性の変更から13秒して、nginx-clientの本番トラフィックの接続先が再びBluenのタスクに変わりました。
nginx-client Log:
## Client Log
YYYY-MM-DD 15:18:57
本番トラフィック: Blue
テストトラフィック: Green
YYYY-MM-DD 15:18:52
本番トラフィック: Green
テストトラフィック: Green
8f. テストトラフィックのロールバック
8f-1. サービス属性の変更(00:19:30)
CloudMapのAWS_ECS_ROUTE_TEST
のサービス属性が更新され、紐づくタスクがGreenからBlueに戻ります。
Service Discovery Log:
{
"eventTime": "YYYY-MM-DDT15:19:30Z",
"eventSource": "servicediscovery.amazonaws.com",
"eventName": "UpdateServiceAttributes",
"awsRegion": "us-west-2",
"sourceIPAddress": "ecs.amazonaws.com",
"userAgent": "ecs.amazonaws.com",
"requestParameters": {
"serviceId": "srv-bhdknysg5s3cyjop",
"attributes": {
"AWS_ECS_ROUTE_TEST": "{\"name\":\"test\",\"type\":\"HTTP\",\"priority\":0,\"match\":{\"headers\":[{\"name\":\"x-canary-test\",\"value\":{\"exact\":\"beta-version\"}}]},\"weightedTargets\":[{\"weight\":100,\"target\":[\"arn:aws:ecs:us-west-2:123456789012:service-revision/blue-green-cluster/nginx-server/7308461668976403387\"]}]}"
}
},
8f-2. hook: TEST_TRAFFIC_SHIFT(00:19:30)
サービス属性の変更とほぼ時を同じくしてLambdaが実行されます。LifeCycleのステージはTEST_TRAFFIC_SHIFT
です。
Lambda Log:
YYYY-MM-DDT15:19:30.575Z
{"executionDetails": {"testTrafficWeights": {"arn:aws:ecs:us-west-2:123456789012:service-revision/blue-green-cluster/nginx-server/9126904758940771806": 0, "arn:aws:ecs:us-west-2:123456789012:service-revision/blue-green-cluster/nginx-server/7308461668976403387": 100}, "productionTrafficWeights": {}, "serviceArn": "arn:aws:ecs:us-west-2:123456789012:service/blue-green-cluster/nginx-server", "targetServiceRevisionArn": "arn:aws:ecs:us-west-2:123456789012:service-revision/blue-green-cluster/nginx-server/9126904758940771806"}, "executionId": "f79df137-6f4a-4095-a965-245f03b3bf3a", "lifecycleStage": "TEST_TRAFFIC_SHIFT", "resourceArn": "arn:aws:ecs:us-west-2:123456789012:service-deployment/blue-green-cluster/nginx-server/JnAjY1hkpfbWXUdPp-DK5"}
YYYY-MM-DDT15:19:30.575Z
TEST_TRAFFIC_SHIFT
8f-3. テストトラフィックのルーティング変更(00:19:57)
サービス属性の変更から30秒ほどして、nginx-clientの本番トラフィックの接続先が再びBlueのタスクに変わりました。
nginx-client Log:
## Client Log
YYYY-MM-DD 15:19:57
本番トラフィック: Blue
テストトラフィック: Blue
YYYY-MM-DD 15:18:57
本番トラフィック: Blue
テストトラフィック: Green
8f-4. Greenタスクの削除(00:22)
最後にGreenタスクが削除され元の状態に戻ります
ECS Event log:
YYYY年MM月DD日 00:22 (UTC+9:00)
service nginx-server has reached a steady state.
YYYY年MM月DD日 00:22 (UTC+9:00)
service nginx-server deployment ecs-svc/7308461668976403387 deployment completed.
YYYY年MM月DD日 00:20 (UTC+9:00)
service nginx-server has stopped 1 running tasks: task 2b6d3ee95cce45f5aea1769be8610036.
まとめ、所感など
- Cloud Mapの設定が変更されてから、実際にService Connectプロキシが新しいタスクにトラフィックをルーティングするまでにタイムラグがありました。タイムラグの大きさはタスクによって異なりました
- 各々のタスク(Service Connect Proxy)が非同期にCloud Mapの設定を見に行って自身の設定を更新しているように思いました
- 分散アーキテクチャの設計として納得感がありつつ、その場合、本番トラフィックは一気にではなく徐々に切り替わることになりますね
- ロールバック発生時も本番トラフィックが元に戻るまでタイムラグがありました。障害を検知したら可能な限り速やかにロールバックしたい場合にはリスナールールの変更だけで済むALBを利用したほうが良さそうです
- 各々のタスク(Service Connect Proxy)が非同期にCloud Mapの設定を見に行って自身の設定を更新しているように思いました
- ロールバック発生時にもライフサイクルフックが呼び出されますが、仮にそれが失敗した場合にどうなるのかは改めて調べてみたいです
おわりに
本記事がどなたかのお役に立てば幸いです!