改めて ECS Service Connect のトラフィックフローを深堀ってみる

改めて ECS Service Connect のトラフィックフローを深堀ってみる

ECS Service Connect のトラフィックフローの概要

ECS Service Connect を利用した際、サイドカーとして Envoy を含む Service Connect エージェントが自動で追加されます。

ecs-sc3.png

Amazon ECS Service Connect によるサービス間通信の管理

この際、ECS サービス間通信はエージェント経由で行われますが、その他通信はエージェントを経由しません。

ecs-sc.png

今回はこの辺りの ECS Service Connect の挙動を改めて整理してみました。

リクエストを送って試してみる

下記のような構成を作成します。

ecs-sc5.png

クライアント側の ECS サービスでは、「クライアント側のみ」を選択して Service Connect を有効化します。

sc1.png

サーバー側の ECS Service では「クライアントとサーバー」を選択して Service Connect を有効化します。

sc2.png

この設定の場合、ECS のサービスページに存在する「名前空間」にはどちらも登録されますが、サーバー側のみ検出名が設定されます。

sc3.png

Cloud Map のサービスページで確認すると、「クライアントとサーバー」を有効化したサービスのみ、API コールで検出可能な形で登録されています。

sc4.png

この状態でクライアント側のコンテナに 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) にリクエストを送ります。

sc7.png

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 にリクエストを送ります。

sc8.png

この場合は、エージェントを経由せずに直接 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 にリクエストを送ってみます。

sc9.png

この場合もエージェントは経由せずに通信を行います。

# 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 は含まれません。

sc19.png

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 サービスへの通信のみが記録されるためです。

sc5.png

サーバー側では 2 回分のリクエストが記録されます。
「クライアントとサーバー」を有効化した ECS サービスはすべてのリクエストをエージェント経由で受けるため、プライベート IP を指定してアクセスした場合もカウントされると考えられます。

sc6.png

トラフィックの流れを整理する

ECS Service Connect を利用した際の通信フローは下記のようになります。

  1. アプリケーションが app.masukawa-test-ecs-namespace に接続しようとする。
  2. etc/hosts で名前解決を行った結果ループバックアドレスに変換され、通信が Service Connect エージェントにルーティングされる。
  3. 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 経由にすることはできません。

https://dev.classmethod.jp/articles/tsnote-ecs-service-connect-name-resolution-failure/

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 に移行するよりはレイテンシ増を考えなくて良い分楽かもしれません。

ecs-sc4.png

Amazon ECS Service Connect によるサービス間通信の管理

また、設定自体はシンプルなので、移行作業自体はそこまで複雑にはならないと思います。

まとめ

ECS Service Connect のトラフィックルーティングを改めて整理してみました。
順を追って挙動を確認するとシンプルな作りではあるのですが、「AWS がマネージドで提供するシンプルなサービスメッシュみたいなもの」って理解だと戸惑う部分もあるかと思います。
本ブログが ECS Service Connect への理解を深める一助になれば幸いです。

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.