改めて ECS Service Connect のトラフィックフローを深堀ってみる
ECS Service Connect のトラフィックフローの概要
ECS Service Connect を利用した際、サイドカーとして Envoy を含む Service Connect エージェントが自動で追加されます。
Amazon ECS Service Connect によるサービス間通信の管理
この際、ECS サービス間通信はエージェント経由で行われますが、その他通信はエージェントを経由しません。
今回はこの辺りの ECS Service Connect の挙動を改めて整理してみました。
リクエストを送って試してみる
下記のような構成を作成します。
クライアント側の ECS サービスでは、「クライアント側のみ」を選択して Service Connect を有効化します。
サーバー側の ECS Service では「クライアントとサーバー」を選択して Service Connect を有効化します。
この設定の場合、ECS のサービスページに存在する「名前空間」にはどちらも登録されますが、サーバー側のみ検出名が設定されます。
Cloud Map のサービスページで確認すると、「クライアントとサーバー」を有効化したサービスのみ、API コールで検出可能な形で登録されています。
この状態でクライアント側のコンテナに ECS Exec で乗り込んでリクエストを送ってみます。
$ aws ecs execute-command \
--cluster masukawa-test-ecs-cluster \
--task 81a963a5567f4ac0923d2bc9246d5326 \
--container app \
--interactive \
--command "/bin/sh"
まずは Service Connect で払い出されたエンドポイント (app.masukawa-test-ecs-namespace
) にリクエストを送ります。
IP としては 127.255.0.1:80
にアクセスしており、サイドカーのエージェント経由で通信を行っていることがわかります。
# curl -v http://app.masukawa-test-ecs-namespace
* Trying 127.255.0.1:80...
* Connected to app.masukawa-test-ecs-namespace (127.255.0.1) port 80 (#0)
> GET / HTTP/1.1
> Host: app.masukawa-test-ecs-namespace
> User-Agent: curl/7.88.1
> Accept: */*
>
< HTTP/1.1 200 OK
< content-type: application/json; charset=utf-8
< date: Sat, 26 Apr 2025 07:43:18 GMT
< content-length: 26
< x-envoy-upstream-service-time: 6
< server: envoy
<
* Connection #0 to host app.masukawa-test-ecs-namespace left intact
{"message":"Hello World!"}
続いて、直接 ECS タスクのプライベート IP にリクエストを送ります。
この場合は、エージェントを経由せずに直接 ECS タスクの IP に接続しにいきます。
ただし、server: envoy
と表示されており、サーバー側はエージェント経由で通信を受けているようです。
# curl -v http://10.0.11.42
* Trying 10.0.11.42:80...
* Connected to 10.0.11.42 (10.0.11.42) port 80 (#0)
> GET / HTTP/1.1
> Host: 10.0.11.42
> User-Agent: curl/7.88.1
> Accept: */*
>
< HTTP/1.1 200 OK
< content-type: application/json; charset=utf-8
< date: Sat, 26 Apr 2025 07:43:42 GMT
< content-length: 26
< x-envoy-upstream-service-time: 0
< server: envoy
<
* Connection #0 to host 10.0.11.42 left intact
{"message":"Hello World!"}
次は ECS から全く関係ない EC2 にリクエストを送ってみます。
この場合もエージェントは経由せずに通信を行います。
# curl -v http://10.0.10.123
* Trying 10.0.10.123:80...
* Connected to 10.0.10.123 (10.0.10.123) port 80 (#0)
> GET / HTTP/1.1
> Host: 10.0.10.123
> User-Agent: curl/7.88.1
> Accept: */*
>
< HTTP/1.1 200 OK
< Server: nginx/1.26.3
< Date: Sat, 26 Apr 2025 07:44:00 GMT
< Content-Type: text/html
< Content-Length: 615
< Last-Modified: Fri, 07 Mar 2025 23:21:40 GMT
< Connection: keep-alive
< ETag: "67cb7f84-267"
< Accept-Ranges: bytes
<
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>
<p><em>Thank you for using nginx.</em></p>
</body>
</html>
* Connection #0 to host 10.0.10.123 left intact
また、EC2 からクライアント側の ECS タスクのプライベート IP にリクエストを投げた場合、レスポンスヘッダーに server: envoy
は含まれません。
sh-4.2$ curl -v http://10.0.10.153
* Trying 10.0.10.153:80...
* Connected to 10.0.10.153 (10.0.10.153) port 80
> GET / HTTP/1.1
> Host: 10.0.10.153
> User-Agent: curl/8.3.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Vary: RSC, Next-Router-State-Tree, Next-Router-Prefetch, Accept-Encoding
< x-nextjs-cache: HIT
< X-Powered-By: Next.js
< Cache-Control: s-maxage=31536000, stale-while-revalidate
< ETag: "25chjnzgk49yf"
< Content-Type: text/html; charset=utf-8
< Content-Length: 12911
< Date: Sat, 26 Apr 2025 07:44:36 GMT
< Connection: keep-alive
< Keep-Alive: timeout=5
=============長いので以下省略=============
上記から ECS Service Connect を有効化したタスクからリクエストを送信する際、「クライアントとサーバー」を有効化した ECS サービスへの通信のみがエージェント経由となることがわかります。
ただし、「クライアントとサーバー」を有効化した ECS サービスはトラフィックを受信する際はどこからリクエストを投げた場合でもエージェント経由でリクエストを受けます。
また、CloudWatch のメトリクスとして記録されるかどうかは、エージェントを経由するかどうかで決まります。
上記 4 回リクエストを送った場合、クライアント側では 1 回分のサービス間通信が記録されます。
「クライアントとサーバー」を有効化した ECS サービスへの通信のみが記録されるためです。
サーバー側では 2 回分のリクエストが記録されます。
「クライアントとサーバー」を有効化した ECS サービスはすべてのリクエストをエージェント経由で受けるため、プライベート IP を指定してアクセスした場合もカウントされると考えられます。
トラフィックの流れを整理する
ECS Service Connect を利用した際の通信フローは下記のようになります。
- アプリケーションが
app.masukawa-test-ecs-namespace
に接続しようとする。 etc/hosts
で名前解決を行った結果ループバックアドレスに変換され、通信が Service Connect エージェントにルーティングされる。- Service Connect エージェントが適切なターゲットにトラフィックを転送する
一方、ECS サービス間通信以外は通常の DNS 解決を経て直接行われます。
この仕組みにより、ECS Service Connect は特定のサービス間通信のみを Service Connect エージェント経由で行うことができています。
上記の流れになることをクライアント側のコンテナに乗り込んで設定ファイルから確認してみます。
まず、今回使用した OS の情報を見てみます。
ホスト OS として利用されているのは Amazon Linux2 です。
# uname -a
Linux ip-10-0-10-153.ap-northeast-1.compute.internal 5.10.235-227.919.amzn2.x86_64 #1 SMP Sat Apr 5 16:59:05 UTC 2025 x86_64 GNU/Linux
コンテナのディストリビューションに関する情報は下記です。
# cat /etc/os-release
PRETTY_NAME="Debian GNU/Linux 12 (bookworm)"
NAME="Debian GNU/Linux"
VERSION_ID="12"
VERSION="12 (bookworm)"
VERSION_CODENAME=bookworm
ID=debian
HOME_URL="https://www.debian.org/"
SUPPORT_URL="https://www.debian.org/support"
BUG_REPORT_URL="https://bugs.debian.org/"
/etc/hosts
ファイルを確認してみます。
# cat /etc/hosts
127.0.0.1 localhost
10.0.10.153 ip-10-0-10-153.ap-northeast-1.compute.internal
127.255.0.1 app.masukawa-test-ecs-namespace
2600:f0f0:0:0:0:0:0:1 app.masukawa-test-ecs-namespace
127.255.0.2 next_app.masukawa-test-ecs-namespace
2600:f0f0:0:0:0:0:0:2 next_app.masukawa-test-ecs-namespace
「クライアントとサーバー」を有効化している他の ECS サービスに関する名前解決を定義した行と、自分宛てへの名前解決を定義した行が存在します。
「クライアント側のみ」で有効化していても自分宛ての通信に関する定義は書き込まれるようです。
とはいえ、自分宛てにアクセスしようとすると 503 レスポンスが返るので、利用目的は不明です。
# curl -v http://next_app.masukawa-test-ecs-namespace
* Trying 127.255.0.2:80...
* Connected to next_app.masukawa-test-ecs-namespace (127.255.0.2) port 80 (#0)
> GET / HTTP/1.1
> Host: next_app.masukawa-test-ecs-namespace
> User-Agent: curl/7.88.1
> Accept: */*
>
< HTTP/1.1 503 Service Unavailable
< content-length: 19
< content-type: text/plain
< date: Sat, 26 Apr 2025 06:02:34 GMT
< server: envoy
<
* Connection #0 to host next_app.masukawa-test-ecs-namespace left intact
no healthy upstream
/etc/nsswitch.conf
も確認してみます。
hosts
行が files dns
となっているので、まず etc/hosts
で名前解決を試みて、だめなら /etc/resolve.conf
に従って DNS 名前解決という流れになります。
# cat /etc/nsswitch.conf
# /etc/nsswitch.conf
#
# Example configuration of GNU Name Service Switch functionality.
# If you have the `glibc-doc-reference' and `info' packages installed, try:
# `info libc "Name Service Switch"' for information about this file.
passwd: files
group: files
shadow: files
gshadow: files
hosts: files dns
networks: files
protocols: db files
services: db files
ethers: db files
rpc: db files
netgroup: nis
/etc/resolv.conf
側では nameserver
として AWS Provided DNS が指定されている状態です。
# cat /etc/resolv.conf
nameserver 10.0.0.2
search ap-northeast-1.compute.internal
また、CloudMap 側が API 呼び出しのみで検出する設定になっているため、AWS Provided DNS 経由で名前解決しようとしてもできません。
# dig app.masukawa-test-ecs-namespace
; <<>> DiG 9.18.33-1~deb12u2-Debian <<>> app.masukawa-test-ecs-namespace
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NXDOMAIN, id: 46371
;; flags: qr rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 1, ADDITIONAL: 1
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;app.masukawa-test-ecs-namespace. IN A
;; AUTHORITY SECTION:
. 3062 IN SOA a.root-servers.net. nstld.verisign-grs.com. 2025042300 1800 900 604800 86400
;; Query time: 4 msec
;; SERVER: 10.0.0.2#53(10.0.0.2) (UDP)
;; WHEN: Wed Apr 23 08:52:01 UTC 2025
;; MSG SIZE rcvd: 135
つまり、ECS Service Connect は Service Connect を有効化したサービスへの通信時のみ、エージェントに通信を流し、後はエージェント良い感じにしてくれる機能ということになります。
etc/hosts
でエージェントに通信を流すことから始まるので、ECS 以外のサービスから名前解決させてアクセスさせることはできません。
ECS サービスで該当する機能を有効化する必要があり、RunTask コマンドで起動するスタンドアロンの ECS タスクには対応していないことも注意が必要です。
Service Connect doesn't support the following:
Windows containers
HTTP 1.0
Standalone tasks
Services that use the blue/green and external deployment types
External container instance for Amazon ECS Anywhere aren't supported with Service Connect.
PPv2
https://docs.aws.amazon.com/AmazonECS/latest/developerguide/service-connect-concepts-deploy.html
また、etc/hosts
に書き込まれるのはタスク起動時なので、タスク起動後に新しく作成されたサービスエンドポイントへの通信を Service Connect 経由にすることはできません。
etc/hosts
の状態という、AWS 設定としては見えない部分で挙動が変わるのは少し気持ち悪さがあります。
例えば、サービスエンドポイントを追加した際、開発環境は夜間停止していたので etc/hosts
に反映されて動作したが、本番環境では上手く動作しなかったといったことも考えられます。
App Mesh の移行先として考えると、この辺りの挙動は結構違うので面食らう部分もあるかもしれません。
App Mesh だと通信が常に Envoy プロキシを通る前提で考える必要があったりするので、必要な通信のみ通るのは考え方が完全に逆でもあります。
AppMesh はデフォルトでは、22 以外のポートは Envoy プロキシを経由して通信するようになっています。
https://chariosan.com/2021/10/31/appmesh_ecs_rds_connect/
とはいえ、通信経路自体は同じなので、ALB や Lattice に移行するよりはレイテンシ増を考えなくて良い分楽かもしれません。
Amazon ECS Service Connect によるサービス間通信の管理
また、設定自体はシンプルなので、移行作業自体はそこまで複雑にはならないと思います。
まとめ
ECS Service Connect のトラフィックルーティングを改めて整理してみました。
順を追って挙動を確認するとシンプルな作りではあるのですが、「AWS がマネージドで提供するシンプルなサービスメッシュみたいなもの」って理解だと戸惑う部分もあるかと思います。
本ブログが ECS Service Connect への理解を深める一助になれば幸いです。