こんにちは、末廣です。 本ブログでは AWS ECS Service Connect でブルー/グリーンデプロイをやってみます。
最近、ECS のブルー/グリーンデプロイするのに CodeDeploy が不要になりました。
これに伴い ALB ではなく、サービスコネクトを使ってブル/ーグリーンデプロイの実現ができるようになったようです。
AWS 公式ブログ の内容について、実際に通信を送りながらサービスの切り替えが行われていることを確認してみました。
構成
構成図
AWS 公式ブログの通りの構成ですが、簡単におこしました。

backend の ECS サービスにおいて、サービスコネクトを使ってブルー/グリーンで通信の切り替えが起こるように設定します。
ECS リソースの準備
VPC、サブネット、ALB あたりは図のように適当に設定し、ECS のリソースについて詳細を記載します。
frontend タスク定義例
{ "family": "ecs-service-connect-frontend", "networkMode": "awsvpc", "requiresCompatibilities": ["FARGATE"], "cpu": "256", "memory": "512", "executionRoleArn": "arn:aws:iam::xxxx:role/ecs-task-execution-role", "taskRoleArn": "arn:aws:iam::xxxx:role/ecs-task-role", "containerDefinitions": [ { "name": "frontend", "image": "nginx:alpine", "essential": true, "portMappings": [ { "containerPort": 80, "hostPort": 80, "protocol": "tcp", "appProtocol": "http", "name": "frontend-port" } ], "environment": [ { "name": "BACKEND_HOST", "value": "backend.service-connect.local" } ], "command": [ "/bin/sh", "-c", "rm -f /etc/nginx/conf.d/* && echo 'server { listen 80; server_name localhost; location / { proxy_pass http://backend:3000; proxy_http_version 1.1; } location /health { return 200 \"OK\"; add_header Content-Type text/plain; } }' > /etc/nginx/conf.d/default.conf && nginx -g 'daemon off;'" ], "logConfiguration": { "logDriver": "awslogs", "options": { "awslogs-group": "/aws/ecs/task/ecs-service-connect-tasks", "awslogs-region": "ap-northeast-1", "awslogs-stream-prefix": "frontend" } }, "healthCheck": { "command": [ "CMD-SHELL", "wget --quiet --tries=1 --spider http://localhost/health || exit 1" ], "interval": 30, "timeout": 5, "retries": 3, "startPeriod": 60 } } ] }
frontend のタスク定義は、ALB からのリクエストをサービスコネクト経由で backend に転送させています。
- nginx:alpine イメージを利用し、80番ポートでリッスン
/へのリクエストはbackend:3000にリバースプロキシ- Service Connectの名前解決(
backend.service-connect.local)を環境変数で指定
backend タスク定義例
{ "family": "ecs-service-connect-backend", "networkMode": "awsvpc", "requiresCompatibilities": ["FARGATE"], "cpu": "256", "memory": "512", "executionRoleArn": "arn:aws:iam::xxxx:role/ecs-task-execution-role", "taskRoleArn": "arn:aws:iam::xxxx:role/ecs-task-role", "containerDefinitions": [ { "name": "backend", "image": "nginx:alpine", "essential": true, "portMappings": [ { "containerPort": 3000, "hostPort": 3000, "protocol": "tcp", "appProtocol": "http", "name": "backend-port" } ], "environment": [ { "name": "ENV", "value": "production" }, { "name": "SERVICE_NAME", "value": "backend" } ], "command": [ "/bin/sh", "-c", "rm -f /etc/nginx/conf.d/* && echo 'server { listen 3000; server_name localhost; location / { return 200 \"Hello from BACKEND BLUE\"; add_header Content-Type text/plain; } location /health { return 200 \"OK\"; add_header Content-Type text/plain; } }' > /etc/nginx/conf.d/default.conf && nginx -g 'daemon off;'" ], "logConfiguration": { "logDriver": "awslogs", "options": { "awslogs-group": "/aws/ecs/task/ecs-service-connect-tasks", "awslogs-region": "ap-northeast-1", "awslogs-stream-prefix": "backend" } }, "healthCheck": { "command": [ "CMD-SHELL", "wget --quiet --tries=1 --spider http://localhost:3000/health || exit 1" ], "interval": 30, "timeout": 5, "retries": 3, "startPeriod": 60 } } ] }
backendタスク定義は、ブルー環境(本番)とグリーン環境(テスト)用にそれぞれリビジョンを定義しています。
- nginx:alpineイメージを利用し、3000番ポートでリッスン
/へのリクエストにはブルー環境は「Hello from BACKEND BLUE」グリーン環境は「Hello from BACKEND GREEN (v2.0)」と返す- 環境変数でproduction環境・サービス名・バージョン(2.0)を指定
サービスコネクトのテストトラフィックルールという機能(次章で説明)によって、ブルー/グリーン 環境に通信を分岐させるようになっています。 グリーン環境のタスク定義は以下のような例にしています。
{ "family": "ecs-service-connect-backend", "networkMode": "awsvpc", "requiresCompatibilities": ["FARGATE"], "cpu": "256", "memory": "512", "executionRoleArn": "arn:aws:iam::xxxx:role/ecs-task-execution-role", "taskRoleArn": "arn:aws:iam::xxxx:role/ecs-task-role", "containerDefinitions": [ { "name": "backend", "image": "nginx:alpine", "essential": true, "portMappings": [ { "containerPort": 3000, "hostPort": 3000, "protocol": "tcp", "appProtocol": "http", "name": "backend-port" } ], "environment": [ { "name": "ENV", "value": "production" }, { "name": "SERVICE_NAME", "value": "backend" }, { "name": "VERSION", "value": "2.0" } ], "command": [ "/bin/sh", "-c", "echo 'server { listen 3000; server_name localhost; location / { return 200 \"Hello from BACKEND GREEN (v2.0)\"; add_header Content-Type text/plain; } location /health { return 200 \"OK - v2.0\"; add_header Content-Type text/plain; } }' > /etc/nginx/conf.d/default.conf && nginx -g 'daemon off;'" ], "logConfiguration": { "logDriver": "awslogs", "options": { "awslogs-group": "/aws/ecs/task/ecs-service-connect-tasks", "awslogs-region": "ap-northeast-1", "awslogs-stream-prefix": "backend-v2" } }, "healthCheck": { "command": [ "CMD-SHELL", "wget --quiet --tries=1 --spider http://localhost:3000/health || exit 1" ], "interval": 30, "timeout": 5, "retries": 3, "startPeriod": 60 } } ] }
サービスコネクト
名前空間については service-connect.local においてそれぞれ frontend backend で名前解決できるよう設定しています。

テストトラフィックのルーティング
ブルー/グリーンデプロイ中に、テスト目的のために特定のリクエストをグリーン (新規) サービスリビジョンにルーティングするため、テストトラフィックヘッダーのルールを設定できます。デプロイを完了する前に、トラフィックが制御された新しいバージョンを検証できます。
ECS サービスの設定にて、「デプロイ戦略」を「ブルー/グリーン」にし、サービスコネクトを有効にすると以下のような「テストトラフィックルールをオンにする」の項目が出てきます。

これを設定することで、デプロイ中 HTTPヘッダが「x-amzn-ecs-bluegreen-test: test」に一致したリクエストだけを、グリーン環境へルーティングできるようにし、ブルー環境の状態を保持したまま、グリーン環境の状態をテストすることができます。

実際に通信を確認してみる
今回の構成で、ALB 経由でリクエストを送信し、サービスコネクトによるブルー/グリーンデプロイの挙動を見てみます。
デプロイ前
デプロイ前に、通常のリクエストを送信してみます
$ curl -v http://<ALBのDNS名> * Trying <ALBのIPアドレス>:80... * Connected to <ALBのDNS名> (<ALBのIPアドレス>) port 80 > GET / HTTP/1.1 > Host: <ALBのDNS名> > User-Agent: curl/8.7.1 > Accept: */* > < HTTP/1.1 200 OK < Content-Type: application/octet-stream,text/plain < server: envoy < x-envoy-upstream-service-time: 2 < Hello from BACKEND BLUE
当然ですが、「Hello from BACKEND BLUE」とブルー環境の backend からのレスポンスメッセージが返ってきています。 図としてはこの状態です。

ここからリビジョンをグリーン環境のタスク定義のものに変更、ブルー/グリーンデプロイのベイク時間を 5分間、サービスコネクトのテストトラフィック設定をし、サービスの更新を実行します。
デプロイ中
デプロイ中、通常リクエストはブルー環境がレスポンスを返しています。
$ curl -v http://<ALBのDNS名> * Trying <ALBのIPアドレス>:80... * Connected to <ALBのDNS名> (<ALBのIPアドレス>) port 80 > GET / HTTP/1.1 > Host: <ALBのDNS名> > User-Agent: curl/8.7.1 > Accept: */* > < HTTP/1.1 200 OK < Content-Type: application/octet-stream,text/plain < server: envoy < x-envoy-upstream-service-time: 2 < Hello from BACKEND BLUE
ここで、グリーン環境の状態をテストするため、テストトラフィック用のヘッダ x-amzn-ecs-bluegreen-test: test を付与して curl してみます。
curl -v -H "x-amzn-ecs-bluegreen-test: test" http://<ALBのDNS名> * Trying <ALBのIPアドレス>:80... * Connected to <ALBのDNS名> (<ALBのIPアドレス>) port 80 > GET / HTTP/1.1 > Host: <ALBのDNS名> > User-Agent: curl/8.7.1 > Accept: */* > x-amzn-ecs-bluegreen-test: test > < HTTP/1.1 200 OK < Content-Type: application/octet-stream,text/plain < server: envoy < x-envoy-upstream-service-time: 2 < Hello from BACKEND GREEN (v2.0)
「Hello from BACKEND GREEN (v2.0)」と、新しくデプロイしたグリーン環境からのレスポンスが返ってくることが確認できます。図としてはこの状態です。

もちろんデプロイ中はマネジメントコンソールからもブルー/グリーンのタスクがそれぞれ起動している状態になっています。

デプロイ完了後
デプロイが完了すると、通常リクエストもグリーン環境へルーティングされるようになります。
$ curl -v http://<ALBのDNS名> * Trying <ALBのIPアドレス>:80... * Connected to <ALBのDNS名> (<ALBのIPアドレス>) port 80 > GET / HTTP/1.1 > Host: <ALBのDNS名> > User-Agent: curl/8.7.1 > Accept: */* > x-amzn-ecs-bluegreen-test: test > < HTTP/1.1 200 OK < Content-Type: application/octet-stream,text/plain < server: envoy < x-envoy-upstream-service-time: 2 < Hello from BACKEND GREEN (v2.0)
図としてはこの状態です。


ブルー/グリーン方式でのデプロイが無事完了しました。
おまけ:ECS タスク定義 "command" の内容
今回の ECS タスク定義では、"command" に /bin/sh を使ってコンテナ起動時の処理を記述しています。
具体的には、nginx の設定ファイルを動的に生成し、nginx を起動する流れです。
例:frontend タスク定義の "command"
"command": [ "/bin/sh", "-c", "rm -f /etc/nginx/conf.d/* && \ echo 'server { \ listen 80; \ server_name localhost; \ location / { \ proxy_pass http://backend:3000; \ proxy_http_version 1.1; \ } \ location /health { \ return 200 \"OK\"; \ add_header Content-Type text/plain; \ } \ }' > /etc/nginx/conf.d/default.conf && \ nginx -g 'daemon off;'" ]
コマンドの意味
rm -f /etc/nginx/conf.d/*
既存の nginx 設定ファイル(conf.d 配下)をクリーンアップecho ... > /etc/nginx/conf.d/default.conf
nginx の設定内容をdefault.confとして新規作成します。/へのリクエストは backend サービス(backend:3000)へリバースプロキシ/healthへのリクエストはヘルスチェック用のレスポンスを返す
nginx -g 'daemon off;'
設定を新規作成したため、nginx をフォアグラウンド(デーモン化せず)で起動
backend タスク定義の "command"
"command": [ "/bin/sh", "-c", "rm -f /etc/nginx/conf.d/* && \ echo 'server { \ listen 3000; \ server_name localhost; \ location / { \ return 200 \"Hello from BACKEND BLUE\"; \ add_header Content-Type text/plain; \ } \ location /health { \ return 200 \"OK\"; \ add_header Content-Type text/plain; \ } \ }' > /etc/nginx/conf.d/default.conf && \ nginx -g 'daemon off;'" ]
backend の場合は、
- 3000番ポートでリッスン
/へのリクエストで「Hello from BACKEND BLUE」や「Hello from BACKEND GREEN (v2.0)」など、バージョンごとに異なるレスポンスを返す/healthでヘルスチェック用レスポンス
という設定内容を echo で生成しています。
まとめ
今回、AWS ECS サービスコネクトのテストトラフィックを用いたブルー/グリーンデプロイ機能を使って、
本番環境のトラフィックを維持しつつ、新バージョンの検証ができることを確認しました。
CDK (公式ブログの手順)や Terraform (私がやってみた)ではこのあたりは対応していないため、手動でのデプロイが必要になってきますが、 ロードバランサーを使わないコンテナ環境下でも安全なデプロイ体験ができるものではありますが、そもそもサービスコネクトを使っていない場合はそこの設定から…となってしまう点注意が必要そうです。
末廣 満希(執筆記事の一覧)
2022年新卒入社です。ここに何かかっこいい一言を書くことができるエンジニアになれるように頑張ります。