Amazon ECS の新機能 「ECS Gateway」 を試してみた

Amazon ECS の新機能 「ECS Gateway」 を試してみた

Amazon ECS Gateway でパブリック用とプライベート用のゲートウェイを作り、サービスの入口を分けました。接続したサービスには、AWS が管理するドメイン(on.aws)の HTTPS エンドポイントが割り当てられました。
2026.11.01

はじめに

2026年10月20日、Amazon ECS Gateway が発表されました。

https://aws.amazon.com/jp/about-aws/whats-new/2026/10/announcing-amazon-ecs-gateway/

ECS Gateway は、ECS サービスの入口となる ALB と関連リソースを ECS が管理する機能で、1台の ALB を最大25のサービスで共有できます。

https://docs.aws.amazon.com/AmazonECS/latest/developerguide/gateway-overview.html

今回は、東京リージョンの同じクラスターにパブリック用とプライベート用のゲートウェイを用意し、それぞれにサービスを関連付けて FQDN を調べました。

検証環境

項目 構成
リージョン ap-northeast-1(東京)
ネットワーク デフォルト VPC。1a、1c、1d のパブリックサブネットを使用
ゲートウェイ パブリック(gw-public)とプライベート(gw-private)を1つずつ作成。IP アドレスタイプは IPv4
ECS サービス nginx を Fargate で実行し、gw-public と gw-private に接続
タスク 各サービスに1個。0.25 vCPU / 0.5 GB、パブリック IP あり
実行ツール AWS CLI 2.39.3

ゲートウェイの作成とサービスの接続

パブリックとプライベートのゲートウェイ

ゲートウェイには、ECS が ALB などを作るためのインフラストラクチャロールを渡します。信頼ポリシーを trust-ecs.json として保存し、ロールを作ってマネージドポリシーを付けます。

インフラストラクチャロールの作成

trust-ecs.json:

{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":{"Service":"ecs.amazonaws.com"},"Action":"sts:AssumeRole"}]}
aws iam create-role --role-name gwv-gateway-infra --assume-role-policy-document file://trust-ecs.json
aws iam attach-role-policy --role-name gwv-gateway-infra --policy-arn arn:aws:iam::aws:policy/AmazonECSInfrastructureRoleForGatewayHostSharedALB

次のコマンドでプライベートのゲートウェイを作り、クラスターに関連付けました。パブリックのゲートウェイは、--name を gw-public に、scheme を internet-facing に変えて同じように作り、gw-verify に関連付けました。コンソールでは、次の画面で同じ設定を指定できます。

ECS コンソールのゲートウェイ作成画面

aws ecs create-gateway \
  --name gw-private \
  --infrastructure-role-arn arn:aws:iam::<ACCOUNT_ID>:role/gwv-gateway-infra \
  --configuration '{"hostSharedAlb":{"scheme":"internal","networkConfiguration":{"subnets":["<subnet-id-1a>","<subnet-id-1c>","<subnet-id-1d>"],"ipAddressType":"ipv4"}}}'

aws ecs associate-gateway-with-cluster --gateway gw-private --cluster gw-verify

scheme の既定は internet-facing です。ipAddressType は networkConfiguration の中に書きます。既定は dualstack で、今回は ipv4 を指定しました。

作成直後に gw-private を describe-gateway で取得した応答です(gatewayArn と infrastructureRoleArn の2行は省いています)。

{
    "gateway": {
        "name": "gw-private",
        "status": "ACTIVE",
        "deletionProtection": true,
        "configuration": {
            "hostSharedAlb": {
                "networkConfiguration": {
                    "subnets": [
                        "subnet-xxxxxxxxxxxxxxxxx",
                        "subnet-xxxxxxxxxxxxxxxxx",
                        "subnet-xxxxxxxxxxxxxxxxx"
                    ],
                    "ipAddressType": "ipv4"
                },
                "scheme": "internal",
                "port": 443,
                "protocol": "HTTPS",
                "maxAlbCount": 10,
                "currentAlbCount": 0,
                "httpDesync": "defensive"
            }
        },
        "accessType": "PRIVATE",
        "propagateTags": "GATEWAY",
        "tags": [],
        "createdAt": "2026-10-31T22:16:22.076000+09:00",
        "updatedAt": "2026-10-31T22:16:22.076000+09:00"
    }
}

currentAlbCount は0で、ゲートウェイを作っただけでは ALB は作られません。deletionProtection は指定しなくても true になりました。リスナーは HTTPS 443 だけで、ポートやプロトコルは変えられません。作成後、gw-public の accessType は PUBLIC、gw-private は PRIVATE になりました。

サービスの接続先

サービスをゲートウェイに接続するコネクタは、タスク定義の名前付きポートを portName で参照します。タスク定義の portMappings に name を付けておきます。

名前付きポートを持つタスク定義

taskdef.json として保存して登録します。タスク実行ロール gwv-task-exec は、AmazonECSTaskExecutionRolePolicy を付けた通常のロールです。

{
  "family": "gwv-web",
  "networkMode": "awsvpc",
  "requiresCompatibilities": ["FARGATE"],
  "cpu": "256",
  "memory": "512",
  "executionRoleArn": "arn:aws:iam::<ACCOUNT_ID>:role/gwv-task-exec",
  "containerDefinitions": [
    {
      "name": "web",
      "image": "public.ecr.aws/docker/library/nginx:stable",
      "essential": true,
      "portMappings": [
        {"name": "web", "containerPort": 80, "protocol": "tcp", "appProtocol": "http"}
      ]
    }
  ]
}
aws ecs register-task-definition --cli-input-json file://taskdef.json

サービスの作成時に --gateway-connectors を付け、接続先を filters.accessTypes で選びます。PRIVATE を指定したサービスはプライベートのゲートウェイだけに、PUBLIC を指定したサービスはパブリックのゲートウェイだけに接続されました。コンソールでは、サービス作成画面で接続先のゲートウェイを指定します。

ECS コンソールのサービス作成画面でゲートウェイを指定する様子

aws ecs create-service --cluster gw-verify --service-name svc-private-01 \
  --task-definition gwv-web --desired-count 1 --launch-type FARGATE \
  --network-configuration "awsvpcConfiguration={subnets=[<subnet-id-1a>,<subnet-id-1c>,<subnet-id-1d>],assignPublicIp=ENABLED}" \
  --gateway-connectors '[{"portName":"web","filters":{"accessTypes":["PRIVATE"]}}]'

assignPublicIp=ENABLED は、タスクがコンテナイメージを取得できるよう、インターネットへの経路を持たせるために指定しました。接続するゲートウェイの種類は、この設定ではなく filters.accessTypes で決まります。

svc-public-01 と svc-private-01 の作成から約12秒後、パブリック側に internet-facing ALB、プライベート側に internal ALB が1台ずつ作られました。

filters を省略すると、同じ VPC にあり、サービスが使うすべての AZ をカバーするゲートウェイへ接続されます。後からパブリックのゲートウェイをクラスターに関連付けた場合も、次のデプロイで接続対象になります。意図しない公開を防ぐため、インターネットに公開しないサービスには PRIVATE を明示します。

https://docs.aws.amazon.com/AmazonECS/latest/developerguide/gateway-connect-service.html

FQDN と IP アドレス

FQDN の形

サービスリビジョンの ARN を describe-service-revisions に渡すと、serviceUrl を取得できます。

aws ecs describe-service-revisions --service-revision-arns <サービスリビジョンのARN> \
  --query 'serviceRevisions[0].ecsManagedResources.gatewayConnections[].[accessType,status,serviceUrl]' --output text

パブリックとプライベートのサービスから取得した結果です。URL の ID はマスクしています。

サービス accessType status serviceUrl
svc-private-01 PRIVATE ACTIVE https://sv-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.ecs.<リージョン>.on.aws
svc-public-01 PUBLIC ACTIVE https://sv-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.ecs.<リージョン>.on.aws

URL は https://sv-<32桁の16進数>.ecs.<リージョン>.on.aws の形でした。ID はランダムに生成されるため、同じ名前でサービスを作り直しても URL は変わります。

名前解決の結果

VPC の外にある PC(以下、手元の PC)から、各 FQDN の A レコードを取得しました。

svc-private-01: 172.31.x.a 172.31.x.b 172.31.x.c
svc-public-01:  x.x.x.p    x.x.x.q    x.x.x.r

svc-private-01 には internal ALB のプライベート IP、svc-public-01 には internet-facing ALB のパブリック IP が返りました。

どちらの FQDN も CNAME を挟まず A レコードが直接返り、TTL は60秒でした。プライベートの FQDN も手元の PC から名前解決できましたが、返ったのは VPC 内のプライベート IP でした。

Express Mode との比較

共通の構成

別クラスター gw-verify-express に、Express Mode でパブリックのサービス svc-express-01 を1つ作りました。その ALB まわりのリソースを gw-public のものと比べました。次の点が同じでした。

  • エンドポイントの形(https://sv-<32桁の16進数>.ecs.<リージョン>.on.aws)
  • HTTPS 443 のリスナー(TLS ポリシー ELBSecurityPolicy-TLS13-1-2-Ext1-PQ-2025-09、デフォルトアクションは固定応答 404 Not Found)
  • host-header 条件で転送するルール(優先度はどちらも 44990)
  • ターゲットグループ(ターゲットタイプ ip、HTTP 80、ヘルスチェックのパス /、間隔30秒、成功コード 200)
  • サービスごとの ACM 証明書(AMAZON_ISSUED、EC-prime256v1、DNS 検証)
  • セキュリティグループの組み方(ALB 用は 80 と 443 を 0.0.0.0/0 から許可、サービス用はインバウンドで ALB 用のセキュリティグループから TCP 80 を許可)
  • ALB、セキュリティグループ、ターゲットグループに付くタグ AmazonECSManaged=true

Express Mode と異なる点

ECS Gateway が作るのは、ALB、リスナー、ルール、ターゲットグループ、証明書、ホスト名、セキュリティグループなどの入口に関するリソースです。Express Mode とは異なり、タスク定義、ログ、オートスケーリングは自分で設定します。

Express Mode のサービスは ECS Gateway に接続できません。同じ VPC で併用しても、それぞれに別の ALB が作られます。

ゲートウェイの削除

サービスを削除し、ルートと ALB が消えた後、クラスターとの関連付けと削除保護を解除してゲートウェイを削除しました。

aws ecs disassociate-gateway-from-cluster \
  --gateway gw-private \
  --cluster gw-verify

aws ecs update-gateway \
  --gateway gw-private \
  --infrastructure-role-arn arn:aws:iam::<ACCOUNT_ID>:role/gwv-gateway-infra \
  --no-deletion-protection \
  --configuration '{"hostSharedAlb":{"maxAlbCount":10}}'

aws ecs delete-gateway --gateway gw-private

検証時の update-gateway では、削除保護だけを変更する場合も --infrastructure-role-arn と --configuration が必要でした。また、delete-gateway はステータスが UPDATE_COMPLETE になってから実行します。

まとめ

ECS Gateway を使うと、ALB、HTTPS リスナー、証明書、ホストベースのルーティング、セキュリティグループといった、サービスの入口に必要なリソースをまとめて作成・管理できます。1台の ALB を最大25のサービスで共有するため、サービスごとに ALB を立てる構成に比べて ALB の維持費を抑えられます。証明書は、AWS が管理するドメイン(on.aws)に対する ACM 証明書が追加料金なしで使え、HTTPS リスナーもゲートウェイが用意します。コンソールのサービス作成画面からは、CloudFront や WAF もあわせて設定できます。

基盤チームがパブリック用とプライベート用のゲートウェイを用意してネットワーク構成を管理し、アプリケーションチームは名前付きポートと accessType を指定して接続するだけ、という分担にできます。タスク定義やログ、オートスケーリングはアプリケーション側に残るため、入口の管理と役割を分けられます。

この記事をシェアする

AWSのお困り事はクラスメソッドへ

関連記事