ECS on Fargate の本番構築で、最も詰まるのはネットワークレイヤです。タスクが起動しても ALB に繋がらない、ECR からイメージを pull できない、サービス間の通信が不安定——こうした問題の根には、Fargate 固有の awsvpc ネットワークモードに対する理解の不足があります。
私は経済産業大臣賞を受賞した木材流通 B2B SaaS で、API Gateway → NLB → ALB → ECS on Fargate という構成を設計・実装・本番運用してきました(221 エンドポイント、プライベートサブネット運用)。このスタックを安定させるうえで、ネットワーク設計が最大の差別化要因でした。
この記事では、awsvpc の本質から ALB/NLB 接続・セキュリティグループ連鎖・プライベートサブネット設計・VPCエンドポイント閉域化・Service Connect によるサービス間通信まで、本番品質で組むための設計判断と Terraform 実装を一気通貫で示します。Fargate の基本(タスク定義・デプロイ・コスト・セキュリティ)は ECS on Fargate 本番運用ガイド を先に読んでください。本稿はネットワーク層に特化します。
全体像:リクエストはどこを通るか
まず実際のリクエスト経路をアーキテクチャ図で把握します。
インターネット
│
▼
┌─────────────────────────────────────────────┐
│ Public Subnet (ap-northeast-1a / 1c) │
│ ┌──────────────┐ ┌──────────────────┐ │
│ │ ALB │ │ NAT Gateway │ │
│ │ (SG: alb) │ │ (プライベート │ │
│ └──────┬───────┘ │ サブネットの │ │
│ │ │ 出口) │ │
└─────────│───────────└──────────────────┘───┘
│ ▲
│ target_type=ip │ 外向き通信
▼ │
┌─────────────────────────────────────────────┐
│ Private Subnet (ap-northeast-1a / 1c) │
│ ┌──────────────────────────────────────┐ │
│ │ ECS Task (ENI + Private IP) │ │
│ │ SG: task (from alb-sg:8080 only) │ │
│ │ ┌────────────┐ ┌────────────────┐ │ │
│ │ │ app:8080 │ │ sidecar(Envoy) │ │ │
│ │ └────────────┘ └────────────────┘ │ │
│ └──────────────────────────────────────┘ │
│ │
│ VPC Endpoints (Interface / Gateway) │
│ ecr.api / ecr.dkr / s3 / logs / │
│ secretsmanager / ssmmessages │
└─────────────────────────────────────────────┘
│
▼
AWS サービス (ECR / CloudWatch / Secrets Manager)
木材流通 SaaS では、この手前にさらに API Gateway → NLB が加わり(lumber-industry-dx 事例 参照)、外部公開と内部 L7 ルーティングの責務を分離しています。この記事では ALB 以降の ECS ネットワーク層を対象にします。
awsvpc の本質:タスクが ENI を持つとは何を意味するか
Fargate は networkMode: awsvpc 固定です。他のネットワークモード(bridge・host)は選べません。これは制約ではなく、Fargate が提供するタスク単位の分離境界の構造的な帰結です。
Each Fargate task has its own isolation boundary and does not share the underlying kernel, CPU resources, memory resources, or elastic network interface with another task.(— AWS Fargate)
awsvpc モードでは:
- タスクごとに 専用の ENI(Elastic Network Interface) が割り当てられる
- ENI にはサブネット内のプライベート IP アドレスが付与される
- ホストポートマッピングが存在しない。EC2 の
bridgeモードのように「ホストの 80 番を コンテナの 8080 番に転送」という概念がない
この「ホストポートマッピングなし」の帰結が ALB 設定に直結します。
なぜ ALB は target_type="ip" でなければならないか
通常の EC2 インスタンスを ALB のターゲットに登録するとき、target_type="instance" を使います。これはインスタンス ID でトラフィックを送り、EC2 側のポートフォワーディングが残りをやります。
Fargate タスクにはホストの概念がありません。タスクは ENI に紐づいたプライベート IPとしてしか存在しません。だから ALB はIP アドレス直接でターゲットを登録しなければなりません。これが target_type="ip" の理由です。
resource "aws_lb_target_group" "app" {
# ...
target_type = "ip" # Fargate では必ずこれ。"instance" は機能しない
}
target_type="instance" のままにすると ALB ターゲットの登録に失敗するか、タスクが起動してもターゲットが unhealthy のまま維持されます。本番でハマる代表的なミスです。
ロードバランサ接続:ALB vs NLB の選び方
Fargate サービスは ALB・NLB・GWLB のいずれにも対応します。実務の判断基準は次の通りです。
| 観点 | ALB(L7) | NLB(L4) |
|---|---|---|
| プロトコル | HTTP/HTTPS | TCP / UDP / TLS |
| ルーティング | パスベース・ホストヘッダ・HTTP メソッド | IP + ポートのみ |
| SSL 終端 | ALB が担う | NLB パススルー or TLS 終端 |
| WebSocket | 対応 | 対応(TCP) |
| UDP | 非対応 | 対応(PV 1.4 以降) |
| 固定 IP | 非対応(DNS のみ) | 対応(EIP 割当可) |
| 典型ユース | REST API・Web アプリ | gRPC・ゲーム・IoT・社内 NLB→ALB 多段 |
木材流通 SaaS では NLB → ALB → ECS という 2 段構成を採っています。NLB に固定 IP を割り当てて API Gateway のプライベート統合エンドポイントとし、ALB で HTTP ルーティングを行うパターンです。REST API が主体のサービスなら通常は ALB 1 段で十分です。
ALB 完全 Terraform:LB + ターゲットグループ + SG 連鎖 + サービス
# ── ALB 本体 ──────────────────────────────────────────────────────────────
resource "aws_lb" "app" {
name = "prod-alb"
internal = false # パブリック向け。内部 ALB なら true
load_balancer_type = "application"
subnets = var.public_subnet_ids
security_groups = [aws_security_group.alb.id]
# アクセスログを S3 へ(本番必須)
access_logs {
bucket = var.alb_log_bucket
prefix = "alb/prod"
enabled = true
}
}
# ── ALB セキュリティグループ ───────────────────────────────────────────────
resource "aws_security_group" "alb" {
name_prefix = "prod-alb-"
vpc_id = var.vpc_id
lifecycle { create_before_destroy = true }
}
# インターネットから HTTPS を受ける
resource "aws_vpc_security_group_ingress_rule" "alb_https" {
security_group_id = aws_security_group.alb.id
ip_protocol = "tcp"
from_port = 443
to_port = 443
cidr_ipv4 = "0.0.0.0/0"
}
resource "aws_vpc_security_group_ingress_rule" "alb_https_v6" {
security_group_id = aws_security_group.alb.id
ip_protocol = "tcp"
from_port = 443
to_port = 443
cidr_ipv6 = "::/0"
}
# ALB → タスクへのアウトバウンド(タスクの SG と対になる)
resource "aws_vpc_security_group_egress_rule" "alb_to_tasks" {
security_group_id = aws_security_group.alb.id
referenced_security_group_id = aws_security_group.task.id
ip_protocol = "tcp"
from_port = 8080
to_port = 8080
}
# ── タスクセキュリティグループ:ALB の SG からのみ受ける ──────────────────
resource "aws_security_group" "task" {
name_prefix = "prod-task-"
vpc_id = var.vpc_id
lifecycle { create_before_destroy = true }
}
# インバウンド:ALB の SG からアプリポートのみ。0.0.0.0/0 は開けない
resource "aws_vpc_security_group_ingress_rule" "task_from_alb" {
security_group_id = aws_security_group.task.id
referenced_security_group_id = aws_security_group.alb.id
ip_protocol = "tcp"
from_port = 8080
to_port = 8080
}
# アウトバウンド:外向き全開(NAT 経由で ECR / CloudWatch / Secrets Manager へ)
# VPCエンドポイントを使う場合は HTTPS(443) のみに絞ってもよい
resource "aws_vpc_security_group_egress_rule" "task_egress" {
security_group_id = aws_security_group.task.id
ip_protocol = "-1"
cidr_ipv4 = "0.0.0.0/0"
}
# ── ターゲットグループ:Fargate は必ず target_type = "ip" ─────────────────
resource "aws_lb_target_group" "app" {
name = "prod-app"
port = 8080
protocol = "HTTP"
vpc_id = var.vpc_id
target_type = "ip" # ← Fargate の核心。絶対に "instance" にしない
# 接続ドレイン(タスク交代時の in-flight を待つ時間)
# デフォルト 300 秒は過剰。stopTimeout と合わせて短くする
deregistration_delay = 60
health_check {
path = "/healthz"
protocol = "HTTP"
port = "traffic-port"
healthy_threshold = 2
unhealthy_threshold = 3
interval = 15
timeout = 5
matcher = "200"
}
}
# ── HTTPS リスナー ─────────────────────────────────────────────────────────
resource "aws_lb_listener" "https" {
load_balancer_arn = aws_lb.app.arn
port = 443
protocol = "HTTPS"
ssl_policy = "ELBSecurityPolicy-TLS13-1-2-2021-06"
certificate_arn = var.acm_certificate_arn
default_action {
type = "forward"
target_group_arn = aws_lb_target_group.app.arn
}
}
# HTTP → HTTPS リダイレクト
resource "aws_lb_listener" "http_redirect" {
load_balancer_arn = aws_lb.app.arn
port = 80
protocol = "HTTP"
default_action {
type = "redirect"
redirect {
port = "443"
protocol = "HTTPS"
status_code = "HTTP_301"
}
}
}
# ── ECS サービス ───────────────────────────────────────────────────────────
resource "aws_ecs_service" "app" {
name = "web-api"
cluster = var.cluster_id
task_definition = var.task_definition_arn
desired_count = 2
launch_type = "FARGATE"
platform_version = "LATEST"
# デプロイ中は常に 2 タスク健全に保ち、最大 4 タスクまで起動してから旧版を落とす
deployment_minimum_healthy_percent = 100
deployment_maximum_percent = 200
deployment_circuit_breaker {
enable = true
rollback = true
}
# プライベートサブネットに配置。パブリック IP は不要
network_configuration {
subnets = var.private_subnet_ids
security_groups = [aws_security_group.task.id]
assign_public_ip = false # プライベートサブネット + NAT の場合は false
}
load_balancer {
target_group_arn = aws_lb_target_group.app.arn
container_name = "app"
container_port = 8080
}
# デプロイ直後のヘルスチェック誤検知を防ぐ猶予時間
# コンテナの startPeriod と合わせて設定する
health_check_grace_period_seconds = 30
enable_execute_command = true # ECS Exec によるブレークグラス
}
health_check_grace_period_seconds と deregistration_delay の役割
この 2 つは混同されがちですが、フェーズが違います。
health_check_grace_period_seconds:タスク起動直後に ALB がヘルスチェックを開始するまでの猶予時間。アプリの初期化(DB 接続確立・キャッシュウォームアップ)が完了する前にunhealthyと判定されて強制終了されるのを防ぐ。deregistration_delay:タスクを ALB ターゲットから解除するときに in-flight の処理を待つ時間。デプロイ・スケールイン・タスク停止のたびに発動する。デフォルトは 300 秒だが、ほとんどの API は秒単位で完了するため過剰。stopTimeoutと揃えて 30〜60 秒が現実的です。
セキュリティグループ連鎖:0.0.0.0/0 を開けない設計
Fargate ネットワークで最も重要なセキュリティ設計がセキュリティグループの連鎖です。
[インターネット]
│ TCP 443
▼
[alb-sg] ── egress: task-sg:8080 のみ
│ TCP 8080
▼
[task-sg] ── ingress: alb-sg からのみ許可
│
[タスク ENI:8080]
ポイントはシングル SG の CIDR より、SG 参照(referenced_security_group_id)を使うことです。CIDR ベースの許可だと IP が変わったときに設定漏れが起きますが、SG 参照は「その SG が付いたリソース」から来るトラフィックを動的に許可するため、ALB のスケールアウト(IP 追加)にも自動追従します。
タスクの SG のインバウンドに 0.0.0.0/0 を開けてはいけません。 これをやってしまうと、プライベートサブネットにいても VPC 内の他リソースから直接アクセスできてしまいます。
サービスが複数あって相互に通信する場合も同じ原則です。「サービス A の SG → サービス B の SG(アプリポートのみ)」と連鎖させます。後述の Service Connect を使えば、この SG 設計はさらに整理できます。
サブネット設計:プライベート + NAT vs パブリック直置き
プライベートサブネット + NAT Gateway(本番の定石)
本番の定石はプライベートサブネットにタスクを置き、外向き通信を NAT Gateway 経由にするパターンです。
プライベートサブネット
└── ECS タスク(assign_public_ip = false)
└── → NAT Gateway(パブリックサブネット)
└── → インターネット(ECR / CloudWatch / Secrets Manager)
利点は攻撃面の最小化です。タスクにパブリック IP がないため、インターネットから直接到達できません。ALB を経由したリクエストのみが届きます。
パブリック直置き(assign_public_ip = ENABLED)
開発環境やプロトタイプでは assign_public_ip = true + パブリックサブネット に置く選択もあります。タスクにパブリック IP が付くため NAT なしで ECR・CloudWatch に到達できます。NAT Gateway のコスト(データ処理料 + 時間課金)がかかりません。
ただし本番には推奨しません。タスクのパブリック IP が直接インターネットに露出し、SG を誤設定した瞬間にリスクが増大します。また Fargate のプラットフォームが assign_public_ip = ENABLED で起動するためにパブリックサブネットが必要で、VPC 設計が複雑になります。
判断の指針:本番はプライベート + NAT 一択。NAT のコストが気になるなら後述の VPC エンドポイントで削減する方向を取る。
VPC エンドポイント:NAT を減らす・閉域化する
プライベートサブネットの Fargate タスクが ECR からイメージを pull したり CloudWatch にログを送ったりするとき、デフォルトでは NAT Gateway 経由でパブリックエンドポイントへ通信します。VPC エンドポイントを設置すれば、この通信を VPC 内で閉じられます。
Fargate が必要とする VPC エンドポイント一覧
| エンドポイント | タイプ | 用途 | 必須度 |
|---|---|---|---|
com.amazonaws.<region>.ecr.api | Interface | ECR API(イメージメタデータ取得) | ECR 使用時 必須 |
com.amazonaws.<region>.ecr.dkr | Interface | ECR からのイメージレイヤ pull(Docker Registry API) | ECR 使用時 必須 |
com.amazonaws.<region>.s3 | Gateway | ECR イメージレイヤは S3 に格納されている。ecr.dkr だけでは不足 | ECR 使用時 必須 |
com.amazonaws.<region>.logs | Interface | awslogs ドライバによる CloudWatch Logs への送信 | CloudWatch Logs 使用時 必須 |
com.amazonaws.<region>.secretsmanager | Interface | secrets[].valueFrom による Secrets Manager 注入 | シークレット注入時 必須 |
com.amazonaws.<region>.ssmmessages | Interface | ECS Exec(SSM Session Manager 経由) | ECS Exec 有効時 必須 |
注意:ECR エンドポイント(
ecr.api・ecr.dkr)だけ用意して S3 ゲートウェイエンドポイントを省くと、イメージレイヤの pull が NAT 経由になります。ECR のイメージレイヤは S3 に格納されているため、S3 ゲートウェイは ECR エンドポイントとセットで必須です。
Fargate タスク自身の ECS コントロールプレーン通信(
ecs・ecs-agent・ecs-telemetry)はエンドポイントなしで動作しますが、ECR・Secrets Manager・CloudWatch Logs は用意しないとトラフィックがパブリックエンドポイントへ流れます。
Terraform:VPC エンドポイント一式
# ── Interface エンドポイント用 SG ─────────────────────────────────────────
resource "aws_security_group" "vpc_endpoints" {
name_prefix = "vpc-endpoints-"
vpc_id = var.vpc_id
lifecycle { create_before_destroy = true }
}
# プライベートサブネットからのみ HTTPS を受け付ける
resource "aws_vpc_security_group_ingress_rule" "endpoint_https" {
security_group_id = aws_security_group.vpc_endpoints.id
ip_protocol = "tcp"
from_port = 443
to_port = 443
cidr_ipv4 = var.vpc_cidr
}
resource "aws_vpc_security_group_egress_rule" "endpoint_egress" {
security_group_id = aws_security_group.vpc_endpoints.id
ip_protocol = "-1"
cidr_ipv4 = "0.0.0.0/0"
}
# ── S3 ゲートウェイエンドポイント(ECR レイヤ pull に必須) ──────────────
resource "aws_vpc_endpoint" "s3" {
vpc_id = var.vpc_id
service_name = "com.amazonaws.${var.region}.s3"
vpc_endpoint_type = "Gateway"
route_table_ids = var.private_route_table_ids
}
# ── Interface エンドポイント群 ────────────────────────────────────────────
locals {
interface_endpoints = [
"ecr.api",
"ecr.dkr",
"logs",
"secretsmanager",
"ssmmessages",
]
}
resource "aws_vpc_endpoint" "interface" {
for_each = toset(local.interface_endpoints)
vpc_id = var.vpc_id
service_name = "com.amazonaws.${var.region}.${each.value}"
vpc_endpoint_type = "Interface"
subnet_ids = var.private_subnet_ids
security_group_ids = [aws_security_group.vpc_endpoints.id]
private_dns_enabled = true # DNS 解決を VPC 内で完結させる
}
private_dns_enabled = true が重要です。これにより ecr.ap-northeast-1.amazonaws.com などのパブリック DNS 名が VPC 内の ENI IP に解決されるため、アプリやタスク定義を一切変更せずに閉域化できます。
NAT Gateway が不要になるか
VPC エンドポイントをすべて揃えても、NAT Gateway を完全に廃止できるとは限りません。タスクが AWS 以外の外部 API(Stripe・SendGrid など)を呼ぶ場合は引き続き NAT が必要です。ユースケースが「完全に AWS サービスのみ」なら NAT なし閉域構成も可能ですが、稀なケースです。
現実的な判断:NAT Gateway は残しつつ VPC エンドポイントで ECR・CloudWatch・Secrets Manager のトラフィックを内部に落とし、NAT のデータ処理コストと帯域依存を減らす方向が本番での定石です。
サービス間通信:ECS Service Connect(推奨)
マイクロサービス構成では、サービス A がサービス B を呼ぶ内部通信が必要になります。選択肢は複数あります。
| 方法 | 仕組み | 長所 | 短所 |
|---|---|---|---|
| 内部 ALB | 各サービスに内部 ALB を立てる | ALB のルーティング機能が使える | リソース増・コスト・管理複雑化 |
| Cloud Map(DNS) | Route 53 プライベートホストゾーンで DNS 解決 | シンプル | リトライ・タイムアウト・メトリクスがない |
| Service Connect | Envoy をサイドカーとして注入、論理名で解決 | DNS + 自動リトライ + メトリクス。ECS マネージド | 同一 ECS クラスタ内での通信が前提 |
Service Connect が推奨です。内部 ALB を増やさずに疎結合を実現し、Envoy が接続メトリクスを自動収集して CloudWatch Namespace AWS/ECS/ManagedScaling に流すため、可観測性も向上します。
Service Connect の仕組み
Amazon ECS Service Connect provides management of service-to-service communication as Amazon ECS configuration. It builds both service discovery and a service mesh in Amazon ECS.(— ECS Service Connect)
Service Connect は ECS がサイドカーとして Envoy プロキシを自動注入します(明示的にコンテナ定義に追加する必要はない)。クライアント側のタスクは論理名(http://order-api:8080/orders)で呼べば、Envoy が名前解決・ロードバランシング・リトライ・タイムアウトを処理します。
Terraform + タスク定義での設定
Service Connect の設定は ECS サービスの service_connect_configuration ブロックで行います。
# ── サービス A(order-api)が Service Connect でサービスを公開する ──────────
resource "aws_ecs_service" "order_api" {
name = "order-api"
cluster = var.cluster_id
task_definition = var.order_api_task_definition_arn
desired_count = 2
launch_type = "FARGATE"
platform_version = "LATEST"
network_configuration {
subnets = var.private_subnet_ids
security_groups = [aws_security_group.task.id]
assign_public_ip = false
}
service_connect_configuration {
enabled = true
namespace = var.cloud_map_namespace_arn # 事前に aws_service_discovery_http_namespace で作成
# このサービスが公開するエンドポイント
service {
port_name = "http" # タスク定義の portMappings[].name と一致させる
discovery_name = "order-api" # DNS 名として使われる論理名
client_alias {
port = 8080
dns_name = "order-api" # 他タスクはこれで到達できる
}
}
}
}
# ── サービス B(inventory-api)が order-api を呼ぶ側 ──────────────────────
resource "aws_ecs_service" "inventory_api" {
name = "inventory-api"
cluster = var.cluster_id
task_definition = var.inventory_api_task_definition_arn
desired_count = 2
launch_type = "FARGATE"
platform_version = "LATEST"
network_configuration {
subnets = var.private_subnet_ids
security_groups = [aws_security_group.task.id]
assign_public_ip = false
}
service_connect_configuration {
enabled = true
namespace = var.cloud_map_namespace_arn
# クライアント側は service ブロックを省略(公開しない場合)
# 自動注入された Envoy が order-api:8080 への通信を仲介する
}
}
タスク定義側では portMappings に name を付けることが必要です。
{
"containerDefinitions": [
{
"name": "app",
"portMappings": [
{
"containerPort": 8080,
"protocol": "tcp",
"name": "http",
"appProtocol": "http"
}
]
}
]
}
これで inventory-api 内から http://order-api:8080/orders に HTTP リクエストを投げると、Envoy が interceptor として動作し、接続プーリング・リトライ・タイムアウトを自動で処理します。
Service Connect でサービス間 SG を整理する
Service Connect を使う場合でも、SG 設計は必要です。同一クラスタ内でサービス同士が通信するとき、発信側タスクの SG から受信側タスクの SG に対して当該ポートを許可します。
# inventory-api → order-api への通信を許可
resource "aws_vpc_security_group_ingress_rule" "order_from_inventory" {
security_group_id = aws_security_group.order_task.id
referenced_security_group_id = aws_security_group.inventory_task.id
ip_protocol = "tcp"
from_port = 8080
to_port = 8080
}
Cloud Map との使い分け
Cloud Map は Route 53 プライベートホストゾーンを使ったシンプルな DNS ベースのサービスディスカバリです。ECS サービスが起動・停止するたびに A レコードを更新します。
- リトライ・タイムアウト・メトリクスが不要な単純な DNS 解決だけでよい場合は Cloud Map で十分です
- 接続の可観測性・サーキットブレーカー・細かいタイムアウト制御が欲しければ Service Connect を選ぶ
新規構築なら Service Connect を推奨します。Cloud Map より設定は増えますが、Envoy が提供するメトリクス(接続数・エラー率・レイテンシ)は本番の観測にそのまま使えます。
NLB を使う場合の注意点
L4 通信(gRPC・WebSocket over TCP・UDP)や固定 IP が必要な場合は NLB を使います。Fargate + NLB 固有の注意点をまとめます。
resource "aws_lb" "internal" {
name = "prod-nlb"
internal = true
load_balancer_type = "network"
subnets = var.private_subnet_ids
}
resource "aws_lb_target_group" "nlb_app" {
name = "nlb-app"
port = 8080
protocol = "TCP"
vpc_id = var.vpc_id
target_type = "ip" # NLB でも Fargate は ip 必須
deregistration_delay = 30
health_check {
protocol = "HTTP"
path = "/healthz"
healthy_threshold = 2
unhealthy_threshold = 2
interval = 10
}
}
UDP を使う場合は platform_version = "LATEST"(= 1.4.0)が必須です。それ以前のプラットフォームバージョンでは UDP がサポートされません。
また NLB はクライアント IP を保持するため、タスクの SG で NLB のサブネット CIDR からのトラフィックを許可する必要があります(ALB のように SG 参照ができないため CIDR での許可になる)。
# NLB のクライアント IP 保持によりサブネット CIDR を許可する
resource "aws_vpc_security_group_ingress_rule" "task_from_nlb_cidr" {
security_group_id = aws_security_group.task.id
ip_protocol = "tcp"
from_port = 8080
to_port = 8080
cidr_ipv4 = var.vpc_cidr # NLB を置いたサブネットの CIDR
}
WAF を使ってトラフィックをフィルタリングするには ALB との組み合わせが必要です。詳しくは WAF 多層防御ガイド を参照してください。
設計チェックリスト
本番にリリースする前に必ず確認する項目です。
awsvpc / ALB 基本
- タスク定義の
networkModeがawsvpc(Fargate は固定だが明示確認) - ALB/NLB ターゲットグループの
target_type = "ip"になっているか - ALB と Fargate タスクが別々の SGを持ち、SG 参照で連鎖しているか
- タスクの SG のインバウンドに
0.0.0.0/0がないか -
health_check_grace_period_secondsを設定し、アプリの初期化時間を考慮しているか -
deregistration_delayをstopTimeoutと揃えて短くしているか(デフォルト 300 秒は過剰)
サブネット / ルーティング
- タスクをプライベートサブネットに配置し
assign_public_ip = falseか - NAT Gateway が各 AZ に存在するか(シングル AZ の NAT は SPOF)
- プライベートルートテーブルに NAT Gateway へのルートがあるか
VPCエンドポイント
- ECR を使うなら
ecr.api・ecr.dkr・S3 ゲートウェイの 3 点セットが揃っているか - CloudWatch Logs を使うなら
logsエンドポイントがあるか -
secrets[].valueFromを使うならsecretsmanagerエンドポイントがあるか - ECS Exec を有効にするなら
ssmmessagesエンドポイントがあるか - Interface エンドポイントの SG がプライベートサブネット CIDR から TCP 443 を許可しているか
-
private_dns_enabled = trueになっているか
Service Connect / サービス間通信
- 複数サービスがある場合、内部 ALB を増やさずに Service Connect or Cloud Map で解決しているか
- Service Connect を使うなら Cloud Map namespace(HTTP namespace)が作成されているか
- タスク定義の
portMappingsにnameとappProtocolが設定されているか - サービス間の SG で相互通信が許可されているか(SG 参照で連鎖)
まとめ
Fargate のネットワークは awsvpc の一点から全てが展開します。
awsvpc+target_type=ipは最初に押さえる絶対ルール- SG 連鎖(ALB SG → タスク SG) で
0.0.0.0/0をタスクに一切開けない - プライベートサブネット + NAT が本番の安全側。VPCエンドポイントで主要 AWS サービスへの通信を閉域化してコストと攻撃面を削る
- Service Connect でサービス間通信を疎結合にし、内部 ALB の増殖を防ぐ
私が 221 エンドポイントを抱える木材流通 SaaS を API Gateway → NLB → ALB → ECS on Fargate で安定運用できているのは、この設計を各レイヤで徹底しているからです。ネットワーク層を正しく組めば、アプリ層の問題とインフラ層の問題を明確に分離でき、トラブルシューティングの速度も上がります。
コスト最適化(Spot・Graviton・Savings Plans)は ECS on Fargate コスト最適化ガイド へ、タスクが停止した際の原因調査は ECS on Fargate トラブルシューティングガイド へ。このポートフォリオ上の受賞 SaaS の全体構成は lumber-industry-dx 事例 で詳しく紹介しています。Fargate 本番基盤の設計・構築を一緒に進めたい場合は、そちらからご相談ください。