Network/VPC設計入門 Transit Gateway VPC Lattice PrivateLink 統合実装ガイド

目次

1. なぜNetwork設計か — 全8軸からの架橋 + AWS本番運用第9軸結節点

関連シリーズ ナビゲーション (AWS本番運用第9軸 / 全8軸基盤)

  • Network/VPC設計入門 Vol1 (本記事): Transit Gateway × VPC Lattice × PrivateLink ← 第9軸結節点
  • Network/VPC設計入門 Vol2 (近日公開予告)

IAM入門4巻: Vol1 / Vol2 / Vol3 / Vol4

EKS本番運用3巻: Vol1 / Vol2 / Vol3

復旧・運用編4巻: Vol1 / Vol2 / Vol3 / Vol4

AIシリーズ Vol1+Vol2: Vol1 Bedrock Agents / Vol2 Knowledge Bases × RAG

セキュリティ本格運用 Vol1: セキュリティ運用入門 Vol1

コスト最適化 Vol1: Cost Explorer × Budgets × Compute Optimizer

マルチアカウント運用 Vol1: Organizations × Control Tower × Landing Zone

Observability実践 Vol1: Application Signals × SLO × X-Ray

AWS本番環境を構成するすべてのリソースは、例外なく VPC (Virtual Private Cloud) 上で動作する。EKSクラスターのPodはVPC CIDRからIPアドレスを直接割り当てられ、BedrockのナレッジベースはInterface Endpoint経由でプライベート接続を確立し、マルチアカウント構成ではTransit GatewayがVPC間ルーティングを担う。セキュリティグループのルール、Route Tableの経路、NATゲートウェイの配置——これらはすべて「Network設計」の産物だ。

Network設計は多くのエンジニアが後回しにしがちな分野だ。IAMポリシーを完璧に設計し、EKSクラスターを本番品質で構築し、Observabilityダッシュボードを整備したとしても、VPCのCIDRが重複していたり、Transit GatewayのRoute Tableが誤設定されていたりすれば、本番障害が発生する。Network設計は「後から直せる」ものではなく、プロジェクト初期に確定しなければならない基盤だ。

本章では、全8軸シリーズがNetworkの上にどのように成り立っているかを整理し、本記事の到達ゴール5点を明確にする。

2026年のAWS本番環境においてNetwork設計の重要性が増している背景には、3つのトレンドがある。

トレンド1: マルチアカウント構成の標準化: Organizations × Control Towerの普及により、Production / Staging / Shared / Log Archive の複数アカウント構成が標準となった。各アカウントのVPCをTransit Gatewayで接続し、Route Tableで通信を制御する設計は、中堅エンジニアが習得すべき標準スキルとなっている。マルチアカウント構成のNetworkを誤設計した場合、後から変更できない制約が生まれるため、最初の設計品質が長期的な運用コストに直結する。

トレンド2: AIサービスのVPC統合深化: Bedrock・Kendra・ComprehendなどのAIサービスが本番環境でVPC Endpoint経由のプライベート接続を要求するケースが増えている。AI Vol1 (Bedrock Agents)AI Vol2 (Knowledge Bases × RAG) で実装したアーキテクチャも、Interface Endpointの正しい設計なしには本番セキュリティ要件(データのVPC外流出禁止)を満たせない。AIサービスの本番利用にはNetwork設計の知識が前提条件になりつつある。

トレンド3: Network起因のコスト問題: NAT Gatewayのデータ処理料金($0.045/GB)やTGWのデータ転送料金が積み重なり、月数万〜数十万円規模のコストになるケースがある。VPC Endpoint Gateway(S3/DynamoDB)への切替、AZ配置の最適化、不要なInterface Endpointの整理といったNetwork設計の改善が、直接的なコスト削減につながる。コスト最適化の観点でも、Network設計の習熟は優先度が高い。

1-1. 全8軸がVPCの上に乗っている——Network設計が土台である理由

これまでの8軸シリーズを振り返ると、各シリーズの裏でNetworkが重要な役割を担っていることがわかる。以下の表に、各軸とNetworkの関係を整理する。

Networkとの主な接点代表的な構成要素
IAM (Vol1〜Vol4)VPC Endpoint Policy / Interface EndpointS3 Endpoint Policy、VPC内API呼び出し制御
EKS (Vol1〜Vol3)VPC CNI / ALB Ingress / Subnet設計Pod IPアドレス割り当て、ALB配置サブネット
復旧・運用 (Vol1〜Vol4)TGW Inter-Region Peering / Route 53Multi-Region Active-ActiveのVPC間ルーティング
AI (Vol1〜Vol2)Bedrock VPC Endpoint / PrivateLinkBedrock・Knowledge BasesのVPC内閉鎖接続
セキュリティ (Vol1)VPC Flow Logs / Security GroupL3/L4トラフィック観測・通信フィルタリング
コスト最適化 (Vol1)NATゲートウェイ / TGWデータ処理料金クロスAZ転送コスト、S3 Endpoint Gatewayへの切替
マルチアカウント (Vol1)TGW RAM共有 / Route Table分離Production/Staging VPC通信の分離設計
Observability (Vol1)VPC Flow Logs可視化 / X-RayL3/L4観測 + 分散トレーシングとのNetwork層補完

IAM軸との接点: VPC Endpoint Policyは、S3・DynamoDB・Bedrockなどへのアクセスを「VPC内からのみ」に制限する仕組みだ。IAM Vol3(権限棚卸自動化)でLambdaからS3にアクセスする場合、VPC Endpoint Gateway (S3) を通じてインターネットを経由せずにアクセスできる。aws:SourceVpc 条件キーをポリシーに組み込むことで、特定VPCからのアクセスのみを許可するきめ細かな制御が実現できる。

EKS軸との接点: Amazon VPC CNIはPodに対してVPC CIDRのIPアドレスを割り当てる。3 AZ構成のEKSクラスターでは、各AZのPrivateサブネットからNodeとPodがIPアドレスを消費するため、/19以上(8,192 IP以上)のサブネットが推奨される。ALB Ingress Controllerはタグ kubernetes.io/role/elb: "1" が付いたPublicサブネットにロードバランサーを配置する。EKS Vol1/Vol2/Vol3の実装はすべて、正しいSubnet設計とRoute Table設定を前提としている。

復旧・運用軸との接点: 復旧Vol4(Multi-Region Active-Active/Active-Passive)では、リージョン間フェイルオーバーにRoute 53フェイルオーバールーティングを用いた。より高度なActive-Active構成では、TGW Inter-Region PeeringでリージョンをまたぐVPC間ルーティングを確立する必要がある。Network設計なしには、Multi-Region復旧構成を本番品質で実現することはできない。

AI軸 (Vol1 Bedrock Agents / Vol2 Knowledge Bases × RAG) との接点: BedrockはデフォルトでAWSのマネージドエンドポイントへとアクセスするが、本番環境ではトラフィックをVPC内に閉じ込める要件が生じる。com.amazonaws.{region}.bedrock-runtime のInterface Endpointを設置することで、LambdaやECSタスクからのBedrock APIコールはVPC内のENI経由でAWSバックボーンへ転送される。AI Vol1 (Bedrock Agents)AI Vol2 (Knowledge Bases × RAG) で実装したアーキテクチャのNetwork基盤は、本記事§5(PrivateLink)の内容と直結している。

マルチアカウント軸との接点: マルチアカウントVol1(Organizations × Control Tower)では、複数AWSアカウントにまたがるVPC通信を実現する必要がある。VPC Peeringは推移ルーティング(Transitive Routing)をサポートしないため、10 VPCを相互接続するには45本のPeeringが必要になる。Transit Gatewayを使えば1つのハブに全VPCを接続でき、Route Tableで通信制御を細かく設計できる。RAM(Resource Access Manager)でTGWをマルチアカウントに共有する設計が、マルチアカウント構成の「Network版」だ。

コスト最適化軸との接点: コストVol1では、NAT Gatewayのデータ処理コスト($0.045/GB)が想定外に膨らむ事例を取り上げた。さらにNAT Gatewayと異なるAZに配置されたリソースからアクセスする場合、クロスAZ転送料金 ($0.01/GB) が加算される。S3・DynamoDBへのアクセスをVPC Endpoint Gateway(無料)に切り替えるだけで、毎月数十〜数百ドルのコスト削減が可能だ。Network設計の改善がコスト削減に直結する典型例だ。

Observability軸との接点: ObservabilityVol1(Application Signals × SLO × X-Ray)では、分散トレーシングによるL7(アプリケーション層)の観測を学んだ。VPC Flow LogsはすべてのENI(Elastic Network Interface)を通過するIPトラフィックをL3/L4レベルで記録する。Flow LogsのデータをCloudWatch Logs経由でLog InsightsやAthenaに送ることで、X-Rayが捉えられないネットワーク層の異常通信を検知できる。ObservabilityはL7だけで完結せず、NetworkレイヤーのFlow Logs観測と組み合わせて初めて全体像が見える。


全8軸のどのシリーズも、VPC・Subnet・Route Table・NATゲートウェイ・VPC Endpointを前提として動作している。Network設計は「追加機能」ではなく、全シリーズを支える共通基盤(インフラのインフラ) だ。

全8軸に共通する「Network設計の3基盤」は、プロジェクト初期に確立しておくことで後の手戻りを最小化できる。

設計基盤関連する軸後回しにした場合のリスク
CIDR設計 (RFC1918準拠・スケーラブル)全8軸マルチアカウント統合時にVPC再作成・本番ダウンタイム
Route Table環境分離 (Production/Staging分割)EKS / マルチアカウント / 復旧Staging → Production リソースへの意図しない通信到達
VPC Endpoint活用 (Gateway/Interface 適切選択)AI / IAM / コスト最適化インターネット経由コスト増大・データ流出リスク

これら3点は本記事§2(CIDR設計)・§3(Route Table分離)・§5(Endpoint活用)でそれぞれ詳解する。

1-2. 「なんとなく動いていた」Networkを体系化する意義

8軸シリーズの実装を進める中で、多くのエンジニアは「とりあえず動いている」Network構成を積み上げてきた。Subnetを3つ作ってRoute Tableを設定したら動いた、TGWにはとりあえず全VPCをアタッチした——この「動く構成」には、後から顕在化するリスクが潜んでいる。

リスク1: CIDR設計の後悔

Multi-Account統合を進めたとき、初めて「VPC AとVPC Bが同じCIDR (10.0.0.0/16) を使っていた」と気づくケースがある。VPC PeeringはCIDRが重複すると接続不可になり、Transit GatewayもオーバーラップするCIDRのルーティングを正しく処理できない。後からCIDRを変更するには、場合によってはVPCを再作成する必要があり、本番ダウンタイムが発生する。本記事§2ではRFC1918の割り当て指針と、Multi-Account展開を見越したCIDRブロック設計テンプレートを解説する。

リスク2: Transit Gateway Route Tableの混在

Production・Staging・Shared(DNS/監視)のVPCをすべて同一のTGW Route Tableで管理すると、StagingのEC2インスタンスがProductionのRDS Private Endpointに到達できてしまうことがある。TGW Route Tableを環境ごとに分離し、Blackhole Routeを設定して不要な通信を遮断する設計は、後から追加すると既存Attachmentの再設定が必要になる。設計初期に分離しておくことが最も安全だ。本記事§3でRoute Table分離パターンを詳解する。

リスク3: VPC Latticeの見落とし

2024年にGeneral AvailabilityとなったVPC Latticeは、サービス間通信の新しい選択肢だ。VPC Latticeを使うと、VPCの境界やアカウントを越えてサービス間通信を抽象化し、IAM Principalベースの認可をサービス単位で設定できる。EKSマイクロサービスとLambda・ALBが混在する環境でのサービスメッシュ代替として、App Meshより運用負荷が低い選択肢になりうる。Transit Gatewayでは解決できないアプリケーション層の通信制御をLatticeが担う。本記事§4でLatticeの位置付けと実装方法を整理する。

体系的なNetwork設計の知識を身につけることで、「動いているから触らない」から「意図して設計している」状態へ移行できる。これが本記事を第9軸の起点として位置付ける理由だ。

1-3. 本記事の到達ゴール5点

本記事を読み終えた時点で、以下の5点を達成できる状態を目指す。

(a) VPC基本6要素 + CIDR設計指針の習得

Subnet(Public/Private/Isolated)/ Route Table / NATゲートウェイ / Internet Gateway / VPC Endpoint Gateway(S3/DynamoDB)/ Interface Endpointの6要素を統合理解し、3 AZ × 3層のSubnetテンプレートを設計できるようになる。Multi-Account展開を見越したCIDR割り当て指針(RFC1918 / RFC6598)と、EKSクラスターおよびセキュリティ要件を満たすSubnetサイジングも習得する。

(b) Transit Gatewayの本番品質設計

Transit GatewayとVPC Peeringの選定軸(規模・コスト・管理性の3軸マトリクス)を理解し、Attachment種別(VPC/VPN/Direct Connect GW/Connect/Peering)を適切に使い分ける。Production・Staging・Shared向けにRoute Tableを分離する設計パターンと、RAM共有によるマルチアカウントTGW展開、Inter-Region Peeringによるリージョン間接続を実装できるようになる。

(c) VPC Latticeの実践実装

Service Network / Service / Auth Policy / Target Groupの3層構造を理解し、IAM Principalベースの認可をサービス単位で設定できる。Lambda・ALB・EC2・KubernetesのServiceをTarget Groupとして登録するパターンと、App Mesh・ALB・API Gatewayとの使い分け判断軸を習得する。

(d) PrivateLinkの本番実装

Endpoint Service(Provider / NLBベース公開)とInterface Endpoint(Consumer / VPC内ENI配置)の仕組みを理解し、Cross-Account接続およびSaaS接続のパターンを実装できる。AI Vol1/Vol2のBedrockプライベート接続をNetwork基盤として支えられる状態を目指す。

(e) 詰まりポイント7選の回避

CIDR重複 / Transit Gateway経路ループ / NATゲートウェイ過剰課金 / VPC Endpoint選定ミス / TGW上限到達 / VPC Lattice Auth Policy評価エラー / DNS Resolverの詰まり——この7パターンを図解で理解し、設計段階で回避できるようになる。


5点を達成すれば、全8軸の基盤となるNetwork層を本番品質で設計・運用できる状態になる。IAMのEndpoint Policy設計、EKSのCNI Subnet割り当て、BedrockのVPC Endpoint接続、マルチアカウントのTGW RAM共有——これらはすべて本記事で学ぶNetwork設計の上に成立している。次の§2からは、VPC基本構成の6要素整理とCIDR設計指針から具体的な実装に入る。

本記事の活用方法は学習目的に応じて3パターンに分かれる。

  • 全体把握を優先する場合: §1→§2→§3→§4→§5→§6→§7→§8の順に通読。各§末尾のep-boxが設計ポイントをまとめているため、2回目以降の参照用としても活用できる。
  • Transit Gateway設計を重点的に学ぶ場合: §3を中心に、§2のCIDR設計と§6のTGW詰まりポイントを組み合わせて参照する。マルチアカウント環境のVPC接続設計に課題がある場合に特に効果的だ。
  • BedrockなどAIサービスのプライベート接続を実装する場合: §5(PrivateLink)に集中し、Interface EndpointのCross-Account設計パターンを重点的に学ぶ。§5を理解した上でAI Vol1/Vol2のアーキテクチャを見直すと、Networkレイヤーの全体像が把握できる。

全8軸シリーズの実装中にNetworkの疑問が生じた場合は、§6(詰まりポイント7選)を直接参照することを推奨する。CIDR重複・経路ループ・DNS Resolver設定の問題は§6で体系的に整理している。


2. VPC基本構成整理 — Subnet / Route Table / NAT GW / IGW / VPC Endpoint + CIDR設計指針

VPC全体アーキテクチャ — Subnet/Gateway/TGW統合図
図1: VPC全体アーキテクチャ — Public/Private Subnet・IGW・NAT GW・ALB・VPC Endpoint・TGW統合

2-1. VPC 基本6要素整理

VPC を構成する6要素を把握しておくことは、あらゆる AWS アーキテクチャの出発点となる。以下の比較表で各要素の役割と使い所を整理する。

要素役割外部通信コスト典型ユースケース
Internet Gateway (IGW)VPC ↔ インターネット双方向通信あり (Public IP 必須)無料Public Subnet のインバウンド/アウトバウンド
NAT GatewayPrivate Subnet → インターネット一方向通信出のみ有料 (時間課金 + データ転送)Private Subnet からの外部アクセス
Route TableSubnet ごとの経路定義無料Public は 0.0.0.0/0 → IGW、Private は 0.0.0.0/0 → NAT
VPC Endpoint (Gateway)S3/DynamoDB をプライベート接続なし (AWS バックボーン)無料EC2 → S3 バックアップ、DynamoDB アクセス
VPC Endpoint (Interface)AWS サービスを ENI 経由でプライベート接続なし (ENI 経由)有料 (ENI 時間課金 + データ転送)ECR/Secrets Manager/Kinesis 等
SubnetAZ 内の IP アドレス範囲を論理分割種別次第無料Public/Private/Isolated の 3 層構成

Subnet の 3 層構造 (標準テンプレート)

本番環境では 3 AZ × 3 層 (Public/Private/Isolated) の計 9 Subnet 構成が基準となる。

VPC (10.0.0.0/16)
├── ap-northeast-1a
│├── Public 10.0.1.0/24 (ALB / NAT GW)
│├── Private10.0.11.0/24(EC2 / EKS Node)
│└── Isolated  10.0.21.0/24(RDS / ElastiCache)
├── ap-northeast-1c
│├── Public 10.0.2.0/24
│├── Private10.0.12.0/24
│└── Isolated  10.0.22.0/24
└── ap-northeast-1d
 ├── Public 10.0.3.0/24
 ├── Private10.0.13.0/24
 └── Isolated  10.0.23.0/24

EKS を使用する場合、EKS Node 用の Private Subnet に加えて Pod CIDR が必要になるため、VPC 設計段階でアドレス空間を余裕をもって確保しておく (EKS 本番運用 Vol1: VPC CNI 設計 参照)。


2-2. VPC Endpoint 2種類の使い分け

VPC Endpoint は Gateway 型Interface 型の 2 種類があり、選択を誤るとコスト増大やセキュリティリスクに直結する。

比較軸Gateway 型Interface 型
対象サービスS3 / DynamoDB のみECR / Secrets Manager / Kinesis / SNS / SQS 等
コスト無料ENI 時間課金 ($0.01/h) + データ転送課金
仕組みRoute Table に prefix-list エントリを追加Subnet 内に ENI (Private IP) を配置
DNS変更不要private_dns_enabled = true で既存 DNS 名が ENI に向く
Security Group設定不可Security Group で通信制御可能
アクセス制御Endpoint Policy (IAM) のみEndpoint Policy + Security Group
高可用性AWS 管理 (AZ 自動冗長)各 AZ の Subnet に ENI を配置する必要あり

Gateway 型の Terraform 実装 (S3 — 無料)

resource "aws_vpc_endpoint" "s3" {
  vpc_id= aws_vpc.main.id
  service_name= "com.amazonaws.ap-northeast-1.s3"
  vpc_endpoint_type = "Gateway"

  route_table_ids = [
 aws_route_table.private_1a.id,
 aws_route_table.private_1c.id,
 aws_route_table.private_1d.id,
  ]

  policy = jsonencode({
 Statement = [{
Effect = "Allow"
Principal = "*"
Action = ["s3:GetObject", "s3:PutObject", "s3:ListBucket"]
Resource  = [
  "arn:aws:s3:::${var.bucket_name}",
  "arn:aws:s3:::${var.bucket_name}/*"
]
 }]
  })

  tags = { Name = "s3-gateway-endpoint" }
}

Interface 型の Terraform 実装 (ECR — 有料)

ECR を使う EKS / Fargate 環境では ECR API と ECR Docker の 2 エンドポイントが必要になる。

resource "aws_vpc_endpoint" "ecr_api" {
  vpc_id  = aws_vpc.main.id
  service_name  = "com.amazonaws.ap-northeast-1.ecr.api"
  vpc_endpoint_type= "Interface"
  subnet_ids = [
 aws_subnet.private_1a.id,
 aws_subnet.private_1c.id,
 aws_subnet.private_1d.id,
  ]
  security_group_ids  = [aws_security_group.endpoints.id]
  private_dns_enabled = true

  tags = { Name = "ecr-api-interface-endpoint" }
}

resource "aws_vpc_endpoint" "ecr_dkr" {
  vpc_id  = aws_vpc.main.id
  service_name  = "com.amazonaws.ap-northeast-1.ecr.dkr"
  vpc_endpoint_type= "Interface"
  subnet_ids = [
 aws_subnet.private_1a.id,
 aws_subnet.private_1c.id,
 aws_subnet.private_1d.id,
  ]
  security_group_ids  = [aws_security_group.endpoints.id]
  private_dns_enabled = true

  tags = { Name = "ecr-dkr-interface-endpoint" }
}

選択の原則: S3/DynamoDB は必ず Gateway 型を使う。それ以外は Interface 型を検討するが、ENI 1 本あたり約 $7/月のコストが発生するため、事前に試算してから導入する。


2-3. CIDR 設計指針

CIDR 設計のミスは後から修正が極めて困難であり、Multi-Account 環境では VPC 間の経路統合時に致命的な問題を引き起こす。設計段階で以下の指針を徹底する。

RFC1918 プライベートアドレス空間

アドレス範囲CIDR利用可能 IP 数推奨度
10.0.0.0 ~ 10.255.255.25510.0.0.0/8約 1,677 万★★★ 最推奨
172.16.0.0 ~ 172.31.255.255172.16.0.0/12約 104 万★★
192.168.0.0 ~ 192.168.255.255192.168.0.0/16約 6.5 万★ (小規模のみ)

AWS では 10.0.0.0/8 の範囲を /16 単位で払い出す設計が標準的。1 VPC = /16 を基本単位とし、Subnet = /24 で切り出す。

CIDR 設計の 4 原則

原則 1 — VPC は /16 以上を確保する

/24 で VPC を作ると Subnet 分割で即座に枯渇する。EKS Pod CIDR (ノード 1 台あたり最大 110 Pod) や将来の Secondary CIDR 追加を見越して /16 以上を確保する。

原則 2 — AZ 分散を均等に割り当てる

3 AZ 構成では各 AZ に均等な CIDR ブロックを割り当てる。例: VPC が /16 の場合、各 AZ に /18 を割り当てて 3 層 (Public/Private/Isolated) を /24 で切り出す。

原則 3 — 将来拡張を見越した予約

アカウントごとに /16 ブロックを 2-3 個予約しておく。VPC は Secondary CIDR を後から追加できるが (最大 /16 を 5 つまで)、隣接ブロックが他 VPC に使用されていると拡張できない。

原則 4 — Multi-Account の CIDR 重複禁止

Transit Gateway で複数 VPC を統合する場合、CIDR が重複すると Route Table が機能しない。AWS IPAM (VPC IP Address Manager) または管理台帳で全アカウント分の割り当てを一元管理する。

Multi-Account CIDR 割り当て設計例

# 本番 Account
VPC-Prod-Tokyo  10.0.0.0/16
VPC-Prod-Osaka  10.1.0.0/16

# ステージング Account
VPC-Staging-Tokyo  10.2.0.0/16
VPC-Staging-Osaka  10.3.0.0/16

# 共有サービス Account (DNS / NTP / Bastion)
VPC-Shared-Services10.4.0.0/16

# 開発 Account
VPC-Dev-1 10.10.0.0/16
VPC-Dev-2 10.11.0.0/16
VPC-Dev-3 10.12.0.0/16

# 予約済み (将来のアカウント追加用)
# 10.20.0.0/16 ~ 10.29.0.0/16

2-4. NAT Gateway の落とし穴とコスト最適化

NAT Gateway は構成を誤ると月次コストが大幅に膨らむ。主な落とし穴は AZ をまたぐデータ転送不要な NAT 経由通信の 2 つだ。

落とし穴 1: AZ をまたぐ NAT Gateway 転送

NAT Gateway を 1 AZ のみに配置し、他の AZ の Private Subnet がクロス AZ 経由で利用するケースは課金の罠になる。

[アンチパターン]
ap-northeast-1a: Private Subnet → NAT GW (1a) → Internet  ← AZ 内転送 = 無料
ap-northeast-1c: Private Subnet → NAT GW (1a) → Internet  ← AZ 越え = $0.01/GB !!!
ap-northeast-1d: Private Subnet → NAT GW (1a) → Internet  ← AZ 越え = $0.01/GB !!!
# [正解パターン] 各 AZ に NAT Gateway を 1 台配置
resource "aws_nat_gateway" "nat" {
  for_each = {
 "1a" = aws_subnet.public["1a"].id
 "1c" = aws_subnet.public["1c"].id
 "1d" = aws_subnet.public["1d"].id
  }
  allocation_id = aws_eip.nat[each.key].id
  subnet_id  = each.value
  tags = { Name = "nat-gw-${each.key}" }
}

# 各 AZ の Private Route Table は同 AZ の NAT GW を参照
resource "aws_route" "private_nat" {
  for_each = {
 "1a" = aws_route_table.private["1a"].id
 "1c" = aws_route_table.private["1c"].id
 "1d" = aws_route_table.private["1d"].id
  }
  route_table_id= each.value
  destination_cidr_block = "0.0.0.0/0"
  nat_gateway_id= aws_nat_gateway.nat[each.key].id
}

落とし穴 2: S3/DynamoDB への不要な NAT 経由通信

S3 や DynamoDB への通信が NAT Gateway 経由になっている場合、Gateway 型 VPC Endpoint に切り替えるだけでその分の NAT データ転送コストをゼロにできる

VPC Endpoint 導入前後のコスト比較や Compute Optimizer を使ったコスト削減手法については コスト最適化 Vol1: Cost Explorer × Budgets × Compute Optimizer で詳しく解説している。


2-5. VPC Flow Logs でネットワーク通信を可視化する

VPC Flow Logs を全 VPC で有効化することで、不審なトラフィックパターンや過剰な NAT 経由通信を可視化できる。

resource "aws_flow_log" "vpc_main" {
  vpc_id = aws_vpc.main.id
  traffic_type = "ALL"
  iam_role_arn = aws_iam_role.flow_logs.arn
  log_destination = aws_cloudwatch_log_group.flow_logs.arn
}

resource "aws_cloudwatch_log_group" "flow_logs" {
  name  = "/aws/vpc/flowlogs/${var.environment}"
  retention_in_days = 30
}

VPC Flow Logs の分析・可視化手法 (Athena クエリ、CloudWatch Logs Insights) については セキュリティ本格運用 Vol1 を参照されたい。Network 観測は Security 観測と連携することで、異常なポートスキャンや大量データ流出を早期検知できる。


VPC 6要素設計指針 — まとめ

  • Subnet 設計: 3 AZ × 3 層 (Public/Private/Isolated) を基本構成とし、EKS 使用時は Pod CIDR 分を事前計算して確保する
  • VPC Endpoint Gateway: S3/DynamoDB は必ず Gateway 型を使い、NAT Gateway 経由を排除する (無料かつ AWS バックボーン経由でレイテンシも低い)
  • VPC Endpoint Interface: ECR/Secrets Manager 等は Interface 型を使いプライベート接続を確立するが、ENI 1本 ≒ $7/月のコスト試算を事前に行う
  • CIDR 設計 4 原則: ① VPC は /16 以上を確保 ② AZ 均等分散 ③ 将来拡張ブロックを予約 ④ Multi-Account で CIDR 重複禁止 (AWS IPAM で一元管理)
  • NAT Gateway 2 大落とし穴: ① AZ をまたぐ配置によるデータ転送課金 ($0.01/GB) → 各 AZ に 1 台配置で回避 ② S3/DynamoDB への不要な NAT 経由通信 → Gateway Endpoint で無料化
  • VPC Flow Logs: 全 VPC で有効化し CloudWatch Logs Insights/Athena で可視化。不審トラフィックとコスト要因通信を早期検知する

3. Transit Gateway 設計実践 (山場1) — Attachment / Route Table / Multi-Account / Inter-Region

Transit Gateway 設計図 — Attachment種別/Route Table分離
図2: Transit Gateway 設計図 — Attachment種別・Route Table分離・Multi-Account接続
TGW Route Table 伝搬フロー
図3: TGW Route Table 伝搬フロー — Dev-RT/Prod-RT/Shared-RT の分離設計

3-1. なぜ Transit Gateway が必要か — VPC Peering の限界

VPC Peering は2つの VPC を1対1で直接接続する仕組みです。接続数が少ない段階では有効ですが、VPC数が増えると N×(N−1)/2 本の接続が必要になり管理が破綻します。また VPC Peering は推移ルーティング (A→B→C) に対応していないため、Hub VPC 経由でトラフィックを集約する設計が取れません。

比較軸VPC PeeringTransit Gateway
接続モデル1対1 (推移ルーティング不可)ハブ&スポーク (集中管理)
スケール目安≤10 VPC最大 5,000 Attachment
推移ルーティング不可可 (Route Table で制御)
管理コストVPC 数の 2乗で増大TGW 1台で一元管理
通信コストクロス AZ $0.01/GBAttachment $0.05/h + $0.02/GB
Route 設定各 VPC の Route Table を個別更新TGW Route Table で一元定義
Multi-AccountRAM 共有で可 (接続数上限あり)RAM 共有 + 一元 Route Table 管理
適用目安VPC 数 ≤10 / 推移経路不要VPC 数 >10 / Multi-Account / 拠点接続

選定基準: VPC 数が 10 を超える、あるいはオンプレ接続 (VPN / Direct Connect) を統合する場合は Transit Gateway を選択します。EKS 本番クラスタを複数アカウントに展開する構成(EKS本番運用 Vol1)でも、VPC 数が増加した時点で Transit Gateway への移行を検討してください。

3-2. Attachment 4種類と用途

Transit Gateway は Attachment を介して各リソースと接続します。Attachment の種別を正確に把握することが設計の第一歩です。

Attachment 種別接続先主な用途
VPC Attachment同一アカウント / RAM 共有先の VPCアプリ VPC のハブ接続
VPN AttachmentCustomer Gateway (オンプレ VPN 機器)サイト間 VPN (IPsec)
Direct Connect Gateway AttachmentDirect Connect Gateway 経由専用線によるオンプレ接続
Peering Attachment他リージョンの Transit GatewayInter-Region トラフィック集約
# VPC Attachment の作成 (AWS CLI)
aws ec2 create-transit-gateway-vpc-attachment \
  --transit-gateway-id tgw-0123456789abcdef0 \
  --vpc-id vpc-0prod123456789 \
  --subnet-ids subnet-0a1b2c3d subnet-0e5f6a7b \
  --options ApplianceModeSupport=enable,DnsSupport=enable \
  --tag-specifications \
 'ResourceType=transit-gateway-attachment,Tags=[{Key=Name,Value=prod-vpc-attachment},{Key=Env,Value=production}]'

# VPN Attachment (Customer Gateway 事前作成済み前提)
aws ec2 create-vpn-connection \
  --type ipsec.1 \
  --customer-gateway-id cgw-0123456789abcdef0 \
  --transit-gateway-id tgw-0123456789abcdef0

# Peering Attachment — 東京 TGW からバージニア TGW へ接続
aws ec2 create-transit-gateway-peering-attachment \
  --transit-gateway-id tgw-0ap123456789 \
  --peer-transit-gateway-id tgw-0us123456789 \
  --peer-region us-east-1 \
  --tag-specifications \
 'ResourceType=transit-gateway-attachment,Tags=[{Key=Name,Value=ap-us-peering}]'

3-3. Route Table 設計 — 分離型と共有型の選択

TGW の Route Table は「どの Attachment からの通信が、どの Attachment へ到達できるか」を制御します。環境ごとに Route Table を分離すると、Production と Staging の経路を完全に隔離できます。

設計パターン比較:
共有型: 全 Attachment が同一 Route Table を参照。設定シンプルだが環境間の通信が完全遮断できない。
分離型 (推奨): Production / Staging / Shared の独立 Route Table。Black Hole Route で誤接続を防止。

# Transit Gateway 本体
resource "aws_ec2_transit_gateway" "main" {
  description= "Central Hub TGW"
  amazon_side_asn  = 64512
  auto_accept_shared_attachments  = "disable"
  default_route_table_association = "disable"
  default_route_table_propagation = "disable"

  tags = { Name = "main-tgw", Env = "production" }
}

# Route Table: Production 専用
resource "aws_ec2_transit_gateway_route_table" "production" {
  transit_gateway_id = aws_ec2_transit_gateway.main.id
  tags = { Name = "tgw-rt-production" }
}

# Route Table: Staging 専用
resource "aws_ec2_transit_gateway_route_table" "staging" {
  transit_gateway_id = aws_ec2_transit_gateway.main.id
  tags = { Name = "tgw-rt-staging" }
}

# Route Table: Shared (監視・ログ基盤など全環境共通)
resource "aws_ec2_transit_gateway_route_table" "shared" {
  transit_gateway_id = aws_ec2_transit_gateway.main.id
  tags = { Name = "tgw-rt-shared" }
}

# VPC Attachment — Production VPC
resource "aws_ec2_transit_gateway_vpc_attachment" "prod_app" {
  subnet_ids= var.prod_private_subnet_ids
  transit_gateway_id = aws_ec2_transit_gateway.main.id
  vpc_id = var.prod_vpc_id

  transit_gateway_default_route_table_association = false
  transit_gateway_default_route_table_propagation = false

  tags = { Name = "prod-app-attachment", Env = "production" }
}

# Association: Production Attachment → Production Route Table
resource "aws_ec2_transit_gateway_route_table_association" "prod_app" {
  transit_gateway_attachment_id  = aws_ec2_transit_gateway_vpc_attachment.prod_app.id
  transit_gateway_route_table_id = aws_ec2_transit_gateway_route_table.production.id
}

# Propagation: Production Attachment が Production Route Table へ経路を自動広告
resource "aws_ec2_transit_gateway_route_table_propagation" "prod_to_production_rt" {
  transit_gateway_attachment_id  = aws_ec2_transit_gateway_vpc_attachment.prod_app.id
  transit_gateway_route_table_id = aws_ec2_transit_gateway_route_table.production.id
}

# Propagation: Production Attachment が Shared Route Table にも経路を広告 (共有サービスからの到達性確保)
resource "aws_ec2_transit_gateway_route_table_propagation" "prod_to_shared_rt" {
  transit_gateway_attachment_id  = aws_ec2_transit_gateway_vpc_attachment.prod_app.id
  transit_gateway_route_table_id = aws_ec2_transit_gateway_route_table.shared.id
}

# Black Hole Route: Production Route Table から Staging CIDR へのトラフィックをドロップ
resource "aws_ec2_transit_gateway_route" "prod_block_staging" {
  destination_cidr_block= "10.20.0.0/16"
  transit_gateway_route_table_id = aws_ec2_transit_gateway_route_table.production.id
  blackhole = true
}

Association と Propagation の役割:
Association: Attachment が「どの Route Table を参照するか」を定義。1 Attachment につき 1 Route Table のみ関連付け可能。
Propagation: Attachment が「自身の CIDR を Route Table に広告するか」を制御。複数 Route Table への伝搬が可能。
Black Hole Route: 到達させたくない CIDR を明示的にドロップ。Production から Staging への誤接続を防止する。

3-4. Multi-Account 共有 — AWS RAM による TGW 一元管理

マルチアカウント構成では AWS Resource Access Manager (RAM) を使って管理アカウントの TGW を各ワークロードアカウントへ共有します。Attachment の作成権限を委譲しつつ、Route Table の制御は管理アカウントが一元保持する設計が本番向けです。

# 管理アカウント側: RAM Resource Share の作成
resource "aws_ram_resource_share" "tgw_share" {
  name = "tgw-cross-account-share"
  allow_external_principals = false

  tags = { Name = "tgw-ram-share" }
}

# TGW を Resource Share に登録
resource "aws_ram_resource_association" "tgw" {
  resource_arn = aws_ec2_transit_gateway.main.arn
  resource_share_arn = aws_ram_resource_share.tgw_share.arn
}

# Organizations の OU 単位で共有 (OU ARN を指定)
resource "aws_ram_principal_association" "workload_ou" {
  principal = "arn:aws:organizations::123456789012:ou/o-exampleorgid/ou-exmpl-workloadouid"
  resource_share_arn = aws_ram_resource_share.tgw_share.arn
}
# ワークロードアカウント側: 共有された TGW への Attachment 作成
aws ec2 create-transit-gateway-vpc-attachment \
  --transit-gateway-id tgw-0123456789abcdef0 \
  --vpc-id vpc-0workload123 \
  --subnet-ids subnet-0wk1 subnet-0wk2 \
  --tag-specifications \
 'ResourceType=transit-gateway-attachment,Tags=[{Key=Name,Value=workload-a-attachment}]'

# 管理アカウント側: Attachment を承認 (auto_accept_shared_attachments=disable の場合)
aws ec2 accept-transit-gateway-vpc-attachment \
  --transit-gateway-attachment-id tgw-attach-0abc123456789

Organizations × RAM 共有の詳細な設計パターンは マルチアカウント運用 Vol1 で解説しています。Control Tower Landing Zone と組み合わせた TGW 共有のベストプラクティスを確認してください。

3-5. Inter-Region Peering — グローバル展開時の TGW 接続

DR 構成やグローバルサービスのために複数リージョンへ展開する場合、Transit Gateway Peering Attachment でリージョン間の TGW を接続します。Peering Attachment は AWS グローバルバックボーン経由で暗号化通信を確立します。

# ap-northeast-1 (東京) の TGW から us-east-1 (バージニア) の TGW へ Peering Attachment を作成
resource "aws_ec2_transit_gateway_peering_attachment" "ap_to_us" {
  provider = aws.ap_northeast_1

  transit_gateway_id= aws_ec2_transit_gateway.ap.id
  peer_transit_gateway_id = var.us_east_1_tgw_id
  peer_region = "us-east-1"
  peer_account_id= var.us_east_1_account_id

  tags = { Name = "ap-ne1-to-us-east-1-peering" }
}

# us-east-1 側で Peering Attachment を承認
resource "aws_ec2_transit_gateway_peering_attachment_accepter" "us_accept" {
  provider = aws.us_east_1

  transit_gateway_attachment_id = aws_ec2_transit_gateway_peering_attachment.ap_to_us.id
  tags = { Name = "us-east-1-accept-ap-ne1-peering" }
}

# 東京 Route Table にバージニアへの静的ルートを追加
# Peering Attachment は自動伝搬 (Propagation) に対応していないため静的ルートが必須
resource "aws_ec2_transit_gateway_route" "ap_to_us_cidr" {
  destination_cidr_block= "10.100.0.0/16"
  transit_gateway_route_table_id = aws_ec2_transit_gateway_route_table.production.id
  transit_gateway_attachment_id  = aws_ec2_transit_gateway_peering_attachment.ap_to_us.id
}

Inter-Region Peering の設計上の注意:
– Peering Attachment は Propagation (自動経路広告) に対応していない。送信側・受信側それぞれの Route Table に静的ルートを手動で登録する必要がある。
– リージョン間データ転送料金が発生する ($0.02/GB 程度)。転送量が多い場合は Cloud WAN の導入も検討する。
– Multi-Region Active-Active / Active-Passive のフェイルオーバー設計については 復旧運用編 Vol4 を参照してください。

3-6. TGW のサービス上限と EKS VPC 接続パターン

Transit Gateway には以下のサービス上限 (デフォルト値) があります。マルチアカウント・マルチリージョン展開では事前に上限引き上げ申請を検討してください。

上限項目デフォルト上限引き上げ可否
TGW あたり Attachment 数5,000
TGW あたり Route Table 数20
Route Table あたりルート数10,000
TGW あたり Peering Attachment 数50
VPC Attachment あたり Subnet 数1 per AZ (最大 20 AZ)固定

EKS VPC 接続パターン: EKS を複数アカウントへ展開する場合、各アカウントの EKS VPC を TGW に Attachment し、Shared Route Table 経由で共有サービス (Container Registry / Artifact Repository など) にアクセスさせます。EKS ノードグループ用サブネットは Private Subnet に配置し、TGW を経由して NAT Gateway や共有 VPC のリソースへ到達させる設計が標準です。EKS 本番設計の詳細は EKS本番運用 Vol1 / Vol2 / Vol3 を参照してください。

# TGW の現在の上限値を確認
aws service-quotas list-service-quotas \
  --service-code vpc \
  --query 'Quotas[?contains(QuotaName, `transit gateway`)].[QuotaName,Value]' \
  --output table

# Attachment 一覧と状態確認
aws ec2 describe-transit-gateway-attachments \
  --filters Name=transit-gateway-id,Values=tgw-0123456789abcdef0 \
  --query 'TransitGatewayAttachments[*].[TransitGatewayAttachmentId,ResourceType,State,Tags[?Key==`Name`].Value|[0]]' \
  --output table
Transit Gateway 3鉄則

  1. Attachment 設計: 接続先リソース (VPC / VPN / Direct Connect / Peering) ごとに Attachment を明示的に作成し、auto_accept_shared_attachments = "disable" で意図しない接続を防止する。
  2. Route Table 分離: Production / Staging / Shared の Route Table を独立させ、Black Hole Route で環境間の誤接続を遮断する。default_route_table_association = "disable" を必ず設定し、デフォルト Route Table への自動関連付けを無効化する。
  3. Multi-Account 共有: RAM Resource Share を Organizations の OU 単位で適用し、Attachment 承認は管理アカウント側で明示的に行う。Route Table の制御権は管理アカウントが保持し、ワークロードアカウントには Attachment 作成権限のみ委譲する。

4. VPC Lattice 実践 (山場2) — Service Network / Service / Auth Policy / Target Group

VPC Lattice 3層構造図 — Service Network/Service/Target Group
図4: VPC Lattice 3層構造図 — Service Network・Service・Target Group
VPC Lattice Auth Policy 評価シーケンス
図5: VPC Lattice Auth Policy 評価シーケンス — IAM Principal認可フロー

4-1. VPC Lattice とは — Service Mesh代替としてのアプリケーション層接続基盤

AWS VPC Lattice (2024年 GA) は、EKS Pod / Lambda / EC2 / ALB を問わず、サービス間通信を統一的に制御するアプリケーション層ネットワークサービスです。従来のサービスメッシュ (Istio / App Mesh) が各ノードにサイドカープロキシを注入するのに対し、VPC Lattice は AWS マネージドのデータプレーンとして動作し、インフラ管理コストを大幅に削減します。

比較軸Istio / App MeshVPC Lattice
データプレーンサイドカープロキシ (Envoy)AWS マネージド (エージェント不要)
認証・認可mTLS / JWT (自己管理)IAM Policy (AWS 統合)
可視性Prometheus / JaegerCloudWatch / Access Logs
マルチアカウント複雑 (Istio Federation)RAM + Service Network 共有
EKS 連携Pod レベル (サイドカー)Service (ALB / IP TargetGroup)
スケールPod 数に比例してプロキシ増加AWS 側でオートスケール
対応プロトコルHTTP/gRPC/TCPHTTP 1.1 / HTTP 2 / gRPC

選定指針: マルチアカウント / マルチ VPC 間のサービス接続で IAM 統合認証が必要な場合は VPC Lattice を優先します。同一クラスタ内の Pod-to-Pod 通信の細粒度制御が必要な場合は Istio との組み合わせも選択肢になります。

4-2. 3層構造の詳解 — Service Network / Service / Target Group

VPC Lattice は 3 つの論理コンポーネントで構成されます。

┌─────────────────────────────────────────────────────────────────┐
│  Service Network (組織単位 / 複数 VPC / 複数アカウントに共有) │
│  ┌──────────────────────────────────────────────────────────┐│
│  │  Service (アプリ単位 / 独自 DNS: <name>.vpc-lattice.io)││
│  │  ┌─────────────────────┐  ┌─────────────────────────┐││
│  │  │ Listener (HTTP:80)  │  │ Listener (HTTPS:443) │││
│  │  └──────────┬──────────┘  └──────────┬──────────────┘││
│  │ │ │ ││
│  │  ┌──────────▼──────────────────────────────────────┐ ││
│  │  │  Target Group│ ││
│  │  │  (Lambda / ALB / EC2 Instance / IP / K8s Svc)│ ││
│  │  └─────────────────────────────────────────────────┘ ││
│  └──────────────────────────────────────────────────────────┘│
└─────────────────────────────────────────────────────────────────┘
コンポーネント役割粒度
Service Network複数サービスをグループ化する組織単位。VPC をアソシエート組織 / BU 単位
Service単一アプリを表す。DNS 名・Listener・ルーティングルールを持つアプリ単位
Target Groupバックエンドの実体。Lambda / ALB / EC2 / IP / K8s Serviceデプロイ単位

Service Network を VPC にアソシエートすると、その VPC 内のリソースは Service Network 経由で全サービスにアクセス可能になります。

4-3. Auth Policy 設計 — IAM Principal × Condition による細粒度認可

VPC Lattice の Auth Policy は IAM Resource Policy として記述します。Service Network レベルと Service レベルの 2 段階で評価されます。

{
  "Version": "2012-10-17",
  "Statement": [
 {
"Effect": "Allow",
"Principal": {
  "AWS": "arn:aws:iam::123456789012:role/EksWorkerRole"
},
"Action": "vpc-lattice-svcs:Invoke",
"Resource": "*",
"Condition": {
  "StringEquals": {
 "vpc-lattice-svcs:SourceVpc": "vpc-0a1b2c3d4e5f6g7h8"
  }
}
 }
  ]
}

評価フロー: リクエストは Service Network の Auth Policy → Service の Auth Policy の順に評価されます。両方で Allow されたリクエストのみが Target Group に転送されます。vpc-lattice-svcs:SourceVpc Condition を省略すると、IAM Role を持つすべての VPC からアクセスを許可してしまう点に注意してください(§6-6 で詳述)。

# Service の Auth Policy 設定 (AWS CLI)
aws vpc-lattice put-auth-policy \
  --resource-identifier svc-0a1b2c3d4e5f6g7h8 \
  --policy file://auth-policy.json

4-4. EKS 連携 — Pod から VPC Lattice Service への通信 + Terraform 完全例

EKS Pod が VPC Lattice Service を呼び出すには、Pod の IAM Role (IRSA) に vpc-lattice-svcs:Invoke 権限を付与します。Pod から Service の DNS 名に HTTP リクエストを送るだけで、Lattice が認証・ルーティング・ロードバランシングを行います。

# Service Network の作成
resource "aws_vpclattice_service_network" "main" {
  name= "prod-service-network"
  auth_type = "AWS_IAM"

  tags = {
 Env = "production"
  }
}

# VPC を Service Network にアソシエート
resource "aws_vpclattice_service_network_vpc_association" "main" {
  service_network_identifier = aws_vpclattice_service_network.main.id
  vpc_identifier = var.vpc_id

  security_group_ids = [aws_security_group.lattice_client.id]
}

# Service の作成
resource "aws_vpclattice_service" "api" {
  name= "order-api"
  auth_type = "AWS_IAM"
}

# Service を Service Network にアソシエート
resource "aws_vpclattice_service_network_service_association" "api" {
  service_identifier= aws_vpclattice_service.api.id
  service_network_identifier = aws_vpclattice_service_network.main.id
}

# Target Group (IP ターゲット: EKS Pod)
resource "aws_vpclattice_target_group" "pods" {
  name = "order-api-pods"
  type = "IP"

  config {
 vpc_identifier = var.vpc_id
 port  = 8080
 protocol = "HTTP"
 ip_address_type = "IPV4"

 health_check {
enabled = true
path = "/health"
protocol= "HTTP"
healthy_threshold_count= 2
unhealthy_threshold_count = 2
 }
  }
}

# Listener と Default Rule
resource "aws_vpclattice_listener" "https" {
  name= "https-listener"
  service_identifier = aws_vpclattice_service.api.id
  protocol  = "HTTPS"
  port= 443

  default_action {
 forward {
target_groups {
  target_group_identifier = aws_vpclattice_target_group.pods.id
  weight= 100
}
 }
  }
}

4-5. EKS シリーズとの連携 — Gateway API Controller × IRSA 統合

VPC Lattice は EKS の AWS Gateway API Controller と連携し、Gateway / HTTPRoute カスタムリソースで Lattice Service を Kubernetes ネイティブに管理できます。

EKS Pod の IRSA IAM Policy (Lattice Invoke 権限):

{
  "Version": "2012-10-17",
  "Statement": [
 {
"Effect": "Allow",
"Action": "vpc-lattice-svcs:Invoke",
"Resource": "arn:aws:vpc-lattice:ap-northeast-1:123456789012:service/svc-0abc123456789/*"
 }
  ]
}

HTTPRoute で Lattice Service を宣言:

apiVersion: gateway.networking.k8s.io/v1beta1
kind: HTTPRoute
metadata:
  name: order-api-route
  namespace: production
spec:
  parentRefs:
 - name: prod-gateway
sectionName: https
  rules:
 - matches:
  - path:
type: PathPrefix
value: /orders
backendRefs:
  - name: order-service
 port: 8080

GitOps で HTTPRoute マニフェストを管理することで、ArgoCD が Lattice Service へのルーティング変更を自動同期します。カナリアリリース時は weight フィールドで旧バージョン/新バージョンへのトラフィック比率を段階的に移行できます。

  • EKS本番運用 Vol1 — IRSA 設計 / VPC CNI: Pod が Lattice Service を呼び出す際の IAM Role 設定
  • EKS本番運用 Vol2 — Container Insights / ADOT: Lattice 経由トレースの収集・可視化
  • EKS本番運用 Vol3 — GitOps × ArgoCD: HTTPRoute を Git 管理し Lattice Service を宣言的デプロイ

4-6. Observability Vol1 との連携 — Lattice Access Logs の可視化

VPC Lattice の Access Logs を CloudWatch Logs に転送することで、East-West サービス間通信のリクエスト数・レイテンシ・エラー率を可視化できます。Access Logs には リクエスト元 Principal (IRSA Role ARN) · 宛先 Service · HTTP ステータス · レイテンシ が含まれるため、権限エラー (403) と接続エラーの区別が容易です。

# Service Network への Access Logs サブスクリプション作成
aws vpc-lattice create-access-log-subscription \
  --resource-identifier arn:aws:vpc-lattice:ap-northeast-1:123456789012:servicenetwork/sn-0a1b2c3d \
  --destination-arn arn:aws:logs:ap-northeast-1:123456789012:log-group:/aws/vpc-lattice/access-logs

# 500 系エラーを CloudWatch Logs Insights でフィルタ
# (Observability Vol1 で構築した Insights ダッシュボードに統合)
aws logs start-query \
  --log-group-name /aws/vpc-lattice/access-logs \
  --start-time $(date -v-1H +%s) \
  --end-time $(date +%s) \
  --query-string '
 fields @timestamp, requestMethod, targetGroupArn, responseCode, requestToTargetDuration
 | filter responseCode >= 500
 | sort @timestamp desc
 | limit 50
  '

CloudWatch Application Signals / X-Ray との統合 (Lattice 経由リクエストの分散トレーシング) については Observability実践 Vol1 で詳解しています。

VPC Lattice 3鉄則

  1. Service Network を VPC にアソシエートせよ: Service Network と VPC の紐付けが漏れると、その VPC 内のクライアントはサービスにアクセスできない。マルチ VPC / マルチアカウント構成では、すべての Consumer VPC で Association を確認すること。
  2. Auth Policy に SourceVpc Condition を必ず設定せよ: IAM Principal のみの設定では想定外の VPC からアクセス可能になる。vpc-lattice-svcs:SourceVpc Condition で送信元 VPC を限定し、ゼロトラスト設計を実現する。
  3. Target Group のヘルスチェックを有効化せよ: ヘルスチェック未設定では、Pod 障害時にも Lattice がリクエストを転送し続ける。healthy_threshold_count / unhealthy_threshold_count の両方を必ず設定する。

5. PrivateLink 実装 — Endpoint Service / Interface Endpoint / Cross-Account / SaaS接続

PrivateLink Endpoint Service/Consumer図
図6: PrivateLink Endpoint Service/Consumer図 — Provider VPC・NLB・Interface Endpoint

5-1. PrivateLink とは — Interface Endpoint / VPC Lattice / TGW の使い分け

AWS PrivateLink は、VPC 間またはアカウント間でサービスをプライベート接続する仕組みです。パブリックインターネットを経由せず、AWS バックボーン上でトラフィックを完結させます。§3 の Transit Gateway が L3 ルーティングを担うのに対し、PrivateLink はサービス単位のアプリケーション層接続を実現します。

比較軸PrivateLink (Interface Endpoint)VPC LatticeTransit Gateway
接続粒度サービス単位 (NLB ベース)サービス単位 (ALB/Lambda/IP)VPC 単位 (L3)
マルチアカウント✅ Cross-Account 接続✅ RAM 共有不要✅ RAM 共有
Private DNS✅ 自動統合✅ 独自 DNS❌ 手動設定
認可制御Endpoint Policy (IAM)Auth Policy (IAM Principal)Route Table のみ
主なユースケースSaaS 接続 / Cross-Account APIEast-West マイクロサービス全社 VPC 統合 / オンプレ

使い分け原則:
SaaS 接続 (Snowflake/Datadog/AWS Marketplace): PrivateLink 一択 — 相手方が Endpoint Service を公開済み
自社アカウント間マイクロサービス (新規設計): VPC Lattice — Auth Policy × Target Group で柔軟に制御
VPC 間全通信 / オンプレ統合: Transit Gateway — L3 ルーティングを一括管理


5-2. Endpoint Service (Provider 側) 設計 — NLB 必須 / Acceptance Required / Allowed Principals

Provider 側は NLB (Network Load Balancer) をフロントに置き、aws_vpc_endpoint_service で公開します。

# Provider Account — NLB (Internal 配置)
resource "aws_lb" "private_nlb" {
  name= "my-service-nlb"
  load_balancer_type = "network"
  internal  = true
  subnets= var.private_subnet_ids

  enable_cross_zone_load_balancing = true
  tags = { Name = "my-service-nlb" }
}

resource "aws_lb_target_group" "app" {
  name  = "my-service-tg"
  port  = 443
  protocol = "TCP"
  vpc_id= var.vpc_id
  target_type = "ip"
}

resource "aws_lb_listener" "app" {
  load_balancer_arn = aws_lb.private_nlb.arn
  port  = 443
  protocol = "TCP"
  default_action {
 type = "forward"
 target_group_arn = aws_lb_target_group.app.arn
  }
}

# Endpoint Service 公開
resource "aws_vpc_endpoint_service" "my_service" {
  acceptance_required  = true  # Consumer 接続を手動承認 — 意図しない接続を防止
  network_load_balancer_arns = [aws_lb.private_nlb.arn]
  tags = { Name = "my-endpoint-service" }
}

# Consumer Account を事前許可
resource "aws_vpc_endpoint_service_allowed_principal" "consumer" {
  vpc_endpoint_service_id = aws_vpc_endpoint_service.my_service.id
  principal_arn  = "arn:aws:iam::${var.consumer_account_id}:root"
}
# Provider 側: Endpoint Service 名を確認 (Consumer に共有する)
aws ec2 describe-vpc-endpoint-services \
  --filters "Name=service-type,Values=Interface" \
  --query "ServiceDetails[*].ServiceName"

設計ポイント:
acceptance_required = true: Consumer の接続要求を手動承認 → 不正接続を防止
allowed_principals: Consumer Account ARN を事前登録 — アカウントレベルの制御
– NLB は Internal で配置 — インターネット向け公開は不要
– Consumer と同じ AZ にサブネットを配置 — AZ 間データ転送料を削減


5-3. Interface Endpoint (Consumer 側) Terraform 完全例 — aws_vpc_endpoint / Security Group

Consumer 側は aws_vpc_endpoint で Provider の Endpoint Service に接続します。ENI が Consumer VPC 内に作成され、Private DNS で解決されます。

# Consumer Account — Endpoint 用 Security Group
resource "aws_security_group" "endpoint_sg" {
  name  = "endpoint-sg"
  description = "Allow HTTPS to PrivateLink endpoint"
  vpc_id= var.consumer_vpc_id

  ingress {
 from_port= 443
 to_port  = 443
 protocol = "tcp"
 cidr_blocks = [var.consumer_vpc_cidr]
  }
  egress {
 from_port= 0
 to_port  = 0
 protocol = "-1"
 cidr_blocks = ["0.0.0.0/0"]
  }
  tags = { Name = "endpoint-sg" }
}

# Interface Endpoint 作成
resource "aws_vpc_endpoint" "my_service" {
  vpc_id  = var.consumer_vpc_id
  service_name  = var.endpoint_service_name  # Provider から共有された Endpoint Service 名
  vpc_endpoint_type= "Interface"
  private_dns_enabled = true  # VPC の enableDnsHostnames/enableDnsSupport が true であること

  subnet_ids= var.private_subnet_ids  # 各 AZ の Private Subnet
  security_group_ids = [aws_security_group.endpoint_sg.id]

  # Endpoint Policy: 組織内 Principal のみ許可 (最小権限原則)
  policy = jsonencode({
 Version = "2012-10-17"
 Statement = [{
Effect = "Allow"
Principal = "*"
Action = "*"
Resource  = "*"
Condition = {
  StringEquals = { "aws:PrincipalOrgID" = var.org_id }
}
 }]
  })

  tags = { Name = "my-service-endpoint" }
}
# Consumer 側: Endpoint の DNS 名を確認
aws ec2 describe-vpc-endpoints \
  --filters "Name=vpc-endpoint-type,Values=Interface" \
  --query "VpcEndpoints[*].DnsEntries"

private_dns_enabled = true の場合、Provider が提供するホスト名が Consumer VPC 内で自動解決されます。前提として Consumer VPC の enableDnsHostnames / enableDnsSupporttrue に設定してください。


5-4. Cross-Account PrivateLink — Provider Account / Consumer Account 2 ファイル構成

本番環境では Provider と Consumer が異なる AWS アカウントに存在します。Terraform で 2 アカウント分を分離管理し、RAM は不使用です。

# ===== provider_account/main.tf =====
provider "aws" {
  alias= "provider"
  region  = "ap-northeast-1"
  profile = var.provider_profile
}
# NLB + aws_vpc_endpoint_service は §5-2 の通り (provider alias で実行)

# ===== consumer_account/main.tf =====
provider "aws" {
  alias= "consumer"
  region  = "ap-northeast-1"
  profile = var.consumer_profile
}

data "aws_vpc_endpoint_service" "my_service" {
  provider  = aws.consumer
  service_name = var.endpoint_service_name
}

resource "aws_vpc_endpoint" "cross_account" {
  provider= aws.consumer
  vpc_id  = var.consumer_vpc_id
  service_name  = data.aws_vpc_endpoint_service.my_service.service_name
  vpc_endpoint_type= "Interface"
  private_dns_enabled = true
  subnet_ids = var.consumer_subnet_ids
  security_group_ids  = [aws_security_group.endpoint_sg.id]
}

Cross-Account 承認フロー:
1. Consumer が aws_vpc_endpoint を作成 → Pending acceptance 状態
2. Provider 側で承認:

# Provider 側: 接続要求を承認
aws ec2 accept-vpc-endpoint-connections \
  --service-id vpce-svc-xxxxxxxxxxxxxxxx \
  --vpc-endpoint-ids vpce-xxxxxxxxxxxxxxxx
  1. 承認後 → Consumer の Endpoint が Available 状態に遷移

allowed_principals でアカウントレベル制御するため、Transit Gateway で使う RAM 共有は不要です。


5-5. SaaS 接続パターン — AWS Marketplace SaaS / 自社サービス公開

AWS Marketplace SaaS (例: Snowflake):

# SaaS Endpoint Service 名を確認 (Marketplace ページに記載)
aws ec2 describe-vpc-endpoint-services \
  --service-names com.amazonaws.vpce.ap-northeast-1.vpce-svc-xxxxxxxxxxxxxxxx \
  --query "ServiceDetails[0].[ServiceName,AvailabilityZones]"
resource "aws_vpc_endpoint" "snowflake" {
  vpc_id  = var.vpc_id
  service_name  = "com.amazonaws.vpce.ap-northeast-1.vpce-svc-xxxxxxxxxxxxxxxx"
  vpc_endpoint_type= "Interface"
  subnet_ids = var.private_subnet_ids
  security_group_ids  = [aws_security_group.endpoint_sg.id]
  private_dns_enabled = false  # Snowflake は独自 DNS 設定が必要

  tags = { Name = "snowflake-privatelink" }
}

SaaS PrivateLink 注意点:
private_dns_enabled: SaaS 側の指示に従う (Snowflake は false → カスタム DNS 設定が必要)
– AZ 対応: SaaS が公開している AZ と Consumer サブネット AZ を一致させる — AZ 不一致は接続失敗の主因
– スループット: NLB ベースのため理論上無制限だが、SaaS 側の上限を事前確認

自社サービスを SaaS として公開: aws_vpc_endpoint_service を作成し AWS Marketplace に登録すると Consumer が検索・接続できます。大規模マルチテナント SaaS では Endpoint Service 1 件 × 複数 Consumer の構成が標準です。


5-6. IAM Vol1-4 クロスリンク — VPC Endpoint Policy 設計

Interface Endpoint には Endpoint Policy を設定し、経由できる IAM Principal / Action を制限します。

# S3 Interface Endpoint に Endpoint Policy を設定 (最小権限原則)
resource "aws_vpc_endpoint" "s3_interface" {
  vpc_id  = var.vpc_id
  service_name  = "com.amazonaws.${var.region}.s3"
  vpc_endpoint_type= "Interface"
  private_dns_enabled = true
  subnet_ids = var.private_subnet_ids

  policy = jsonencode({
 Version = "2012-10-17"
 Statement = [{
Effect = "Allow"
Principal = { AWS = "arn:aws:iam::${var.account_id}:role/app-role" }
Action = ["s3:GetObject", "s3:PutObject"]
Resource  = "arn:aws:s3:::${var.bucket_name}/*"
 }]
  })
}

VPC Endpoint Policy の詳細設計 (最小権限 / Cross-Account ロール / STS 連携) は以下を参照してください。


5-7. AI Vol1 + Vol2 クロスリンク — Bedrock VPC Endpoint / Knowledge Bases プライベート接続

Amazon Bedrock は VPC Endpoint (Interface) 経由でプライベート呼び出しが可能です。

# Bedrock Runtime Interface Endpoint
resource "aws_vpc_endpoint" "bedrock_runtime" {
  vpc_id  = var.vpc_id
  service_name  = "com.amazonaws.${var.region}.bedrock-runtime"
  vpc_endpoint_type= "Interface"
  private_dns_enabled = true
  subnet_ids = var.private_subnet_ids
  security_group_ids  = [aws_security_group.endpoint_sg.id]

  policy = jsonencode({
 Version = "2012-10-17"
 Statement = [{
Effect = "Allow"
Principal = { AWS = "arn:aws:iam::${var.account_id}:role/bedrock-app-role" }
Action = ["bedrock:InvokeModel", "bedrock:InvokeModelWithResponseStream"]
Resource  = "arn:aws:bedrock:${var.region}::foundation-model/*"
 }]
  })
  tags = { Name = "bedrock-runtime-endpoint" }
}

# Knowledge Bases 用 Endpoint (bedrock-agent-runtime)
resource "aws_vpc_endpoint" "bedrock_agent" {
  vpc_id  = var.vpc_id
  service_name  = "com.amazonaws.${var.region}.bedrock-agent-runtime"
  vpc_endpoint_type= "Interface"
  private_dns_enabled = true
  subnet_ids = var.private_subnet_ids
  security_group_ids  = [aws_security_group.endpoint_sg.id]
  tags = { Name = "bedrock-agent-runtime-endpoint" }
}

Bedrock Agents / Knowledge Bases の本番 VPC 設計については以下を参照してください。

PrivateLink 設計チェックリスト

  • ✅ NLB は Internal 配置 (Provider 側インターネット向け公開不要)
  • acceptance_required = true で Consumer 接続要求を手動承認
  • allowed_principals に Consumer Account ARN を事前登録
  • ✅ Consumer VPC の enableDnsHostnames / enableDnsSupport を true に設定
  • private_dns_enabled は SaaS 側指定に従う (Snowflake は false → カスタム DNS 設定)
  • ✅ Endpoint Security Group: 必要なポートのみ Inbound 許可 (最小権限)
  • ✅ Endpoint Policy: IAM Principal / Action / Resource を制限 (IAM Vol1 最小権限原則)
  • ✅ AZ を Provider / Consumer で揃える (AZ 間データ転送料削減)
  • ✅ Cross-Account: accept-vpc-endpoint-connections で手動承認後 Available 確認
  • ✅ Bedrock: bedrock-runtime + bedrock-agent-runtime の 2 Endpoint を配置

6. 詰まりポイント7選 図解

6-1. CIDR overlap — Multi-Account VPC 統合時のアドレス重複

原因: 複数 Account の VPC をネットワーク統合する際、各 Account で VPC CIDR を独立して設計したために重複が発生する。開発 Account と本番 Account でともに 10.0.0.0/16 を使用している典型パターン。

症状: Transit Gateway に両 VPC をアタッチすると RouteConflict エラーが発生し、一方の Attachment のルートが伝搬されない。VPC Peering の場合は重複 CIDR を理由に Peering 作成が失敗する。

診断コマンド:

# TGW Route Table に重複ルートがないか確認
aws ec2 search-transit-gateway-routes \
  --transit-gateway-route-table-id tgw-rtb-xxxxxxxxx \
  --filters "Name=state,Values=active,blackhole" \
  --query "Routes[*].{Prefix:DestinationCidrBlock,State:State,Type:Type}"

解決策: AWS IPAM (VPC IP Address Manager) を使用して全 Account の CIDR 割り当てを一元管理する。新規 VPC 作成時は IPAM Pool から自動発行し、重複を構造的に防止する。既存 VPC の変更が困難な場合は Secondary CIDR を追加してマイグレーションする。

# IPAM で CIDR 自動割り当て (重複防止)
resource "aws_vpc_ipam" "main" {
  operating_regions {
 region_name = "ap-northeast-1"
  }
}

resource "aws_vpc_ipam_pool" "private" {
  address_family = "ipv4"
  ipam_scope_id  = aws_vpc_ipam.main.private_default_scope_id
  locale= "ap-northeast-1"
}

resource "aws_vpc_ipam_pool_cidr" "private" {
  ipam_pool_id = aws_vpc_ipam_pool.private.id
  cidr= "10.0.0.0/8"
}

resource "aws_vpc" "main" {
  ipv4_ipam_pool_id= aws_vpc_ipam_pool.private.id
  ipv4_netmask_length = 16
}
CIDR overlap 対処チェックリスト

  • ✅ AWS IPAM で全 Account 分 CIDR 払い出しを一元管理する
  • ✅ 新規 VPC は必ず IPAM Pool から割り当て (手動指定禁止)
  • ✅ TGW 統合前に search-transit-gateway-routes で重複確認
  • ✅ 既存重複は Secondary CIDR 追加 → ENI 移行 → Primary CIDR 削除の順で解消
  • ✅ 管理台帳を IPAM Dashboard と同期し、属人的な Excel 管理を排除する

6-2. 経路ループ — TGW Route Table 設計ミスによるパケット循環

原因: Transit Gateway で Spoke VPC どうしが同一 Route Table を共有しており、すべての VPC に 0.0.0.0/0 をデフォルトルートとして Inspection VPC に転送しつつ、Inspection VPC も同じ Route Table に参加している。Inspection VPC からの戻りパケットが再び Inspection VPC へ転送されるループが発生する。

症状: Inspection VPC を経由する通信が TTL 超過で切断される。VPC Flow Logs に大量の REJECT が出力される。traceroute が同一 AZ 内で繰り返し応答を示す。

診断コマンド:

# TGW Route Table のルート一覧確認
aws ec2 search-transit-gateway-routes \
  --transit-gateway-route-table-id tgw-rtb-xxxxxxxxx \
  --filters "Name=state,Values=active,blackhole" \
  --query "Routes[*].{CIDR:DestinationCidrBlock,State:State}"

# Attachment と Route Table の対応確認
aws ec2 describe-transit-gateway-attachments \
  --filters "Name=transit-gateway-id,Values=tgw-xxxxxxxxx" \
  --query "TransitGatewayAttachments[*].{VpcId:ResourceId,RTB:Association.TransitGatewayRouteTableId}"

解決策: Hub-Spoke 設計では Route Table を役割で分離する。Spoke VPC は Spoke-RTB、Inspection VPC は Inspection-RTB に分類し、0.0.0.0/0 を Inspection VPC への経路にするのは Spoke-RTB のみとする。Inspection-RTB には具体的な Spoke CIDR のみを記述して 0.0.0.0/0 を置かない。

# Spoke Route Table — Inspection VPC へのデフォルトルート
resource "aws_ec2_transit_gateway_route" "spoke_default" {
  destination_cidr_block= "0.0.0.0/0"
  transit_gateway_attachment_id  = aws_ec2_transit_gateway_vpc_attachment.inspection.id
  transit_gateway_route_table_id = aws_ec2_transit_gateway_route_table.spoke.id
}

# Inspection Route Table — Spoke CIDR を直接指定 (0.0.0.0/0 は置かない)
resource "aws_ec2_transit_gateway_route" "inspection_to_spoke" {
  for_each  = var.spoke_cidrs
  destination_cidr_block= each.value
  transit_gateway_attachment_id  = aws_ec2_transit_gateway_vpc_attachment.spoke[each.key].id
  transit_gateway_route_table_id = aws_ec2_transit_gateway_route_table.inspection.id
}
経路ループ 対処チェックリスト

  • ✅ TGW Route Table を役割別 (Spoke / Inspection / Shared) に分離する
  • ✅ Inspection-RTB には 0.0.0.0/0 を置かず、Spoke CIDR のみ記述する
  • ✅ 設計変更後に Blackhole Route がないか search-transit-gateway-routes で確認
  • ✅ VPC Flow Logs を有効化し、TTL expired が出力されていないかモニタリングする
  • ✅ Route Table 変更は Terraform plan → apply の二段階で適用し差分を可視化する

6-3. NAT Gateway 過剰課金 — AZ をまたぐデータ転送料の爆発

原因: 3 AZ 構成で NAT Gateway を 1 台のみ配置し、他 AZ のリソースがその NAT Gateway を経由してインターネットへ通信する。NAT Gateway 利用料 ($0.062/時間) に加え、AZ 間データ転送料 ($0.01/GB) が加算される。EKS ノードが大量のコンテナイメージを pull する構成では月数万円単位の差が生じる。コスト削減の全体戦略はコスト最適化入門Vol1を参照。

症状: AWS Cost Explorer で NatGateway データ転送料 が想定の 3 倍以上になっている。EC2 の Inter-AZ data transfer コストが急増している。

診断コマンド:

# NAT Gateway の配置 AZ 確認
aws ec2 describe-nat-gateways \
  --filter "Name=state,Values=available" \
  --query "NatGateways[*].{ID:NatGatewayId,Subnet:SubnetId,State:State}"

# Route Table で NAT Gateway へのルートを確認
aws ec2 describe-route-tables \
  --query "RouteTables[*].Routes[?NatGatewayId!=null].{NAT:NatGatewayId,CIDR:DestinationCidrBlock}"

解決策: NAT Gateway を AZ ごとに 1 台配置し、各 AZ のプライベートサブネットがそれぞれの AZ の NAT Gateway を使用する Route Table を設定する。加えて、S3・DynamoDB へのトラフィックは VPC Gateway Endpoint に切り替えてデータ転送料をゼロにする。

# AZ ごとに NAT Gateway を配置
resource "aws_nat_gateway" "az" {
  for_each= var.public_subnet_ids
  allocation_id = aws_eip.nat[each.key].id
  subnet_id  = each.value
  tags = { Name = "nat-${each.key}" }
}

# AZ ごとのプライベートサブネット Route Table
resource "aws_route" "private_nat" {
  for_each= var.private_route_table_ids
  route_table_id= each.value
  destination_cidr_block = "0.0.0.0/0"
  nat_gateway_id= aws_nat_gateway.az[each.key].id
}

# S3 Gateway Endpoint でデータ転送料ゼロ
resource "aws_vpc_endpoint" "s3_gateway" {
  vpc_id= var.vpc_id
  service_name= "com.amazonaws.ap-northeast-1.s3"
  vpc_endpoint_type = "Gateway"
  route_table_ids= values(var.private_route_table_ids)
}
NAT Gateway 過剰課金 対処チェックリスト

  • ✅ NAT Gateway は AZ ごと 1 台配置し、AZ 間データ転送料を排除する
  • ✅ S3 / DynamoDB は Gateway 型 VPC Endpoint に切り替えてデータ転送料ゼロにする
  • ✅ ECR / CloudWatch Logs は Interface Endpoint 化して NAT 経由を排除する
  • ✅ AWS Cost Explorer の NatGateway フィルタでコストを日次モニタリングする
  • ✅ EKS ノードの imagePullPolicy: IfNotPresent でイメージ pull 回数を削減する

6-4. VPC Endpoint 選定ミス — Gateway vs Interface の使い分け失敗

原因: S3 に Interface 型 Endpoint を作成してしまい、1 ENI あたり月 $7 のコストが発生している。逆に、ECR (Elastic Container Registry) に Gateway 型を誤選択しようとして「Gateway 型は S3/DynamoDB のみ」というエラーに直面するケース。

症状: Terraform apply で InvalidServiceName エラー、または S3 トラフィックが Interface Endpoint の ENI を経由して予想外の ENI 料金が発生する。

診断コマンド:

# Gateway 型に対応しているサービス一覧を確認
aws ec2 describe-vpc-endpoint-services \
  --query "ServiceDetails[?ServiceType[?ServiceType=='Gateway']].ServiceName"

# 現在の Endpoint 一覧と種別確認
aws ec2 describe-vpc-endpoints \
  --query "VpcEndpoints[*].{Name:ServiceName,Type:VpcEndpointType,State:State}"

解決策: S3・DynamoDB は必ず Gateway 型を選択する。Gateway 型は ENI 料金なし・データ転送料なし・Route Table への自動ルート追加が特長。その他のサービス (ECR / CloudWatch Logs / Secrets Manager 等) は Interface 型を選択する。

# 正解: S3 → Gateway 型 (ENI 料金なし)
resource "aws_vpc_endpoint" "s3" {
  vpc_id= var.vpc_id
  service_name= "com.amazonaws.ap-northeast-1.s3"
  vpc_endpoint_type = "Gateway"
  route_table_ids= var.private_route_table_ids
}

# 正解: ECR → Interface 型 (Gateway 型は非対応)
resource "aws_vpc_endpoint" "ecr_api" {
  vpc_id  = var.vpc_id
  service_name  = "com.amazonaws.ap-northeast-1.ecr.api"
  vpc_endpoint_type= "Interface"
  subnet_ids = var.private_subnet_ids
  private_dns_enabled = true
}
VPC Endpoint 選定ミス 対処チェックリスト

  • ✅ S3 / DynamoDB は必ず Gateway 型 — ENI 料金なし・ルート自動追加が利点
  • ✅ ECR / CloudWatch Logs / Secrets Manager は Interface 型 — ENI 料金 ($7/月/AZ) を事前試算
  • describe-vpc-endpoint-services で Gateway 対応サービスを事前確認する
  • ✅ Interface Endpoint は必要な AZ にのみ配置し、不要 AZ への展開は避ける
  • ✅ Terraform の vpc_endpoint_type は必ず明示的に指定し、デフォルト任せにしない

6-5. TGW Attachment 上限 — Route 数超過によるルート設定失敗

原因: Transit Gateway Route Table の最大ルート数はデフォルト 10,000 件。Multi-Account 環境で VPC 数・オンプレミス拠点数が増加するにつれて上限に近づく。また Attachment 数の上限は 1 TGW あたり 5,000 (デフォルト)。大規模環境では上限引き上げ申請を怠ったまま拡張して障害になる。

症状: 新しい VPC を TGW にアタッチしようとすると TransitGatewayAttachmentLimitExceeded エラー。Route Table に追加ルートを設定しようとすると RouteTableLimitExceeded エラー。

診断コマンド:

# TGW の Attachment 数確認
aws ec2 describe-transit-gateways \
  --query "TransitGateways[*].{ID:TransitGatewayId,State:State}"

# Route Table のルート数確認
aws ec2 search-transit-gateway-routes \
  --transit-gateway-route-table-id tgw-rtb-xxxxxxxxx \
  --filters "Name=state,Values=active" \
  --query "length(Routes)"

# Service Quotas で現在の上限確認
aws service-quotas get-service-quota \
  --service-code vpc \
  --quota-code L-A2478E04

解決策: 上限に近づいたら AWS Service Quotas から上限引き上げリクエストを提出する。また、アグリゲートルート (サマリー CIDR) を活用してルート数を削減する。超大規模環境では AWS Cloud WAN への移行を検討する。

# Service Quotas でリクエスト提出
aws service-quotas request-service-quota-increase \
  --service-code vpc \
  --quota-code L-A2478E04 \
  --desired-value 10000

# 引き上げリクエストの確認
aws service-quotas list-requested-changes-by-service \
  --service-code vpc \
  --query "RequestedQuotas[*].{QuotaName:QuotaName,Desired:DesiredValue,Status:Status}"
TGW Attachment 上限 対処チェックリスト

  • ✅ Attachment 数 / Route 数を月次で AWS Service Quotas Dashboard からモニタリングする
  • ✅ 上限の 80% に達したら Service Quotas 引き上げリクエストを発行する仕組みを構築
  • ✅ IPAM と連携してサマリー CIDR を活用し、個別ルート数を削減する
  • ✅ 超大規模 (Attachment 数千単位) は AWS Cloud WAN の採用を検討する
  • ✅ Route Table の伝搬設定は必要な組み合わせのみに絞り、不要な伝搬を排除する

6-6. VPC Lattice Auth Policy エラー — IAM Principal 条件未設定による全許可

原因: VPC Lattice サービスに Auth Policy を設定したが、Principal"*" を指定した状態でデプロイしてしまい、全リクエストが許可される状態になっている。または Auth Policy 自体を未設定のまま auth_type = "NONE" で本番デプロイするミス。

症状: 意図しない呼び出し元からのリクエストが通過する。CloudWatch Logs の VPC Lattice Access Log に見覚えのないソース IP からの呼び出しが記録されている。

診断コマンド:

# Lattice Service の Auth 設定確認
aws vpc-lattice get-service \
  --service-identifier svc-xxxxxxxxxxxxxxxxx \
  --query "{AuthType:AuthType,Arn:Arn}"

# Auth Policy の内容確認
aws vpc-lattice get-auth-policy \
  --resource-identifier svc-xxxxxxxxxxxxxxxxx \
  --query "Policy"

解決策: Auth Type を AWS_IAM に設定し、Principal に呼び出し元ロールの ARN を明示する。組織全体への制限には aws:PrincipalOrgID Condition を活用する。

resource "aws_vpclattice_auth_policy" "service" {
  resource_identifier = aws_vpclattice_service.api.id

  policy = jsonencode({
 Version = "2012-10-17"
 Statement = [{
Effect = "Allow"
Principal = {
  AWS = "arn:aws:iam::${var.consumer_account_id}:role/api-consumer-role"
}
Action= "vpc-lattice-svcs:Invoke"
Resource = aws_vpclattice_service.api.arn
Condition = {
  StringEquals = {
 "aws:PrincipalOrgID" = var.org_id
  }
}
 }]
  })
}
VPC Lattice Auth Policy 対処チェックリスト

  • auth_type = "AWS_IAM" を必須とし、"NONE" は開発環境限定にする
  • Principal"*" は使用禁止 — 呼び出し元ロール ARN を明示する
  • aws:PrincipalOrgID Condition で AWS Organizations 外からのアクセスを禁止する
  • ✅ Lattice Access Log を CloudWatch Logs に転送し、意図しない呼び出しをアラートする
  • ✅ Terraform plan 時に Auth Policy JSON を静的解析 (tfsec / Checkov) にかける

6-7. DNS resolver 詰まり — Route 53 Resolver Endpoint 設計不備

原因: オンプレミスから AWS リソースへの名前解決、または AWS からオンプレミス DNS への名前解決が期待どおりに機能しない。主な原因は (a) Inbound/Outbound Resolver Endpoint の Security Group 設定ミス (Port 53 UDP/TCP を許可していない)、(b) Forwarding Rule の転送先 IP アドレスの誤り、(c) VPC の enableDnsHostnames / enableDnsSupport が無効、の 3 パターン。

症状: オンプレミスホストから RDS エンドポイントへの名前解決が失敗する。AWS Lambda からオンプレミス DNS へのクエリが SERVFAIL を返す。

診断コマンド:

# Resolver Endpoint の状態確認
aws route53resolver list-resolver-endpoints \
  --query "ResolverEndpoints[*].{Name:Name,Direction:Direction,Status:Status}"

# Forwarding Rule の確認
aws route53resolver list-resolver-rules \
  --query "ResolverRules[*].{Name:Name,DomainName:DomainName,TargetIPs:TargetIps}"

# VPC の DNS 設定確認
aws ec2 describe-vpc-attribute \
  --vpc-id vpc-xxxxxxxxx \
  --attribute enableDnsHostnames

解決策: Inbound Resolver Endpoint の Security Group に Port 53 UDP/TCP の Inbound を許可する。VPC の DNS サポートを有効化する。Forwarding Rule の転送先はオンプレミス DNS の IP を直接指定し、二重リレーを避ける。

# Inbound Resolver (オンプレミス → AWS 名前解決用)
resource "aws_route53_resolver_endpoint" "inbound" {
  direction = "INBOUND"
  security_group_ids = [aws_security_group.resolver_inbound.id]

  dynamic "ip_address" {
 for_each = var.private_subnet_ids
 content {
subnet_id = ip_address.value
 }
  }
}

# Security Group: Port 53 UDP/TCP を許可
resource "aws_security_group_rule" "resolver_dns_udp" {
  security_group_id = aws_security_group.resolver_inbound.id
  type  = "ingress"
  protocol = "udp"
  from_port= 53
  to_port  = 53
  cidr_blocks = [var.onpremise_cidr]
}

resource "aws_security_group_rule" "resolver_dns_tcp" {
  security_group_id = aws_security_group.resolver_inbound.id
  type  = "ingress"
  protocol = "tcp"
  from_port= 53
  to_port  = 53
  cidr_blocks = [var.onpremise_cidr]
}

# Outbound Forwarding Rule (AWS → オンプレミス DNS)
resource "aws_route53_resolver_rule" "forward_onprem" {
  domain_name = "corp.example.com"
  name  = "forward-to-onpremise"
  rule_type= "FORWARD"
  resolver_endpoint_id = aws_route53_resolver_endpoint.outbound.id

  target_ip {
 ip= var.onpremise_dns_primary
 port = 53
  }

  target_ip {
 ip= var.onpremise_dns_secondary
 port = 53
  }
}
DNS resolver 詰まり 対処チェックリスト

  • ✅ VPC の enableDnsHostnames / enableDnsSupporttrue に設定する
  • ✅ Resolver Endpoint の Security Group に Port 53 UDP/TCP 双方向の許可ルールを設定
  • ✅ Outbound Forwarding Rule はオンプレミス DNS IP を直接指定し、二重リレーを避ける
  • dig @{resolver-endpoint-ip} {hostname} +short でオンプレミスから疎通確認する
  • ✅ Resolver Query Logs を有効化し、SERVFAIL パターンを CloudWatch Logs で検出する

7. アンチパターン→正解パターン変換演習 (Terraform + AWS CLI)

演習1: 壊れた VPC Terraform (CIDR 重複) → RFC1918 適切割当

アンチパターン — 開発/本番で同一 CIDR を使用:

# ❌ アンチパターン: 本番・開発で同じ CIDR
resource "aws_vpc" "prod" {
  cidr_block = "10.0.0.0/16"
}

resource "aws_vpc" "dev" {
  cidr_block = "10.0.0.0/16"  # 重複! TGW 統合不可
}

正解パターン — Account 単位で CIDR ブロックを分割:

# ✅ 正解パターン: Account 単位でアドレス帯を分割
resource "aws_vpc" "prod" {
  cidr_block = "10.0.0.0/16"# 本番: 10.0.x.x
}

resource "aws_vpc" "staging" {
  cidr_block = "10.2.0.0/16"# ステージング: 10.2.x.x
}

resource "aws_vpc" "dev" {
  cidr_block = "10.10.0.0/16"  # 開発: 10.10.x.x
}

解説: CIDR 重複は TGW / VPC Peering の統合時に発覚する。AWS IPAM を使用すれば cidr_block を明示指定せず ipv4_ipam_pool_id + ipv4_netmask_length で自動発行できるため、重複を構造的に排除できる。


演習2: 経路ループする TGW Route Table → Route Table 分離設計

アンチパターン — 全 VPC が同一 Route Table を共有:

# ❌ アンチパターン: 全 Attachment が同一 RTB を共有
resource "aws_ec2_transit_gateway_route_table_association" "all" {
  for_each  = var.all_vpc_attachment_ids
  transit_gateway_attachment_id  = each.value
  transit_gateway_route_table_id = aws_ec2_transit_gateway_route_table.shared.id
}

# Inspection VPC も同じ RTB に参加 → ループ発生
resource "aws_ec2_transit_gateway_route" "default" {
  destination_cidr_block= "0.0.0.0/0"
  transit_gateway_attachment_id  = aws_ec2_transit_gateway_vpc_attachment.inspection.id
  transit_gateway_route_table_id = aws_ec2_transit_gateway_route_table.shared.id
}

正解パターン — RTB を役割で分離:

# ✅ 正解パターン: Spoke RTB / Inspection RTB を分離

# Spoke RTB: Inspection VPC へのデフォルトルートのみ
resource "aws_ec2_transit_gateway_route" "spoke_to_inspection" {
  destination_cidr_block= "0.0.0.0/0"
  transit_gateway_attachment_id  = aws_ec2_transit_gateway_vpc_attachment.inspection.id
  transit_gateway_route_table_id = aws_ec2_transit_gateway_route_table.spoke.id
}

# Inspection RTB: Spoke CIDR を列挙 (0.0.0.0/0 は不要)
resource "aws_ec2_transit_gateway_route" "inspection_to_prod" {
  destination_cidr_block= "10.0.0.0/16"
  transit_gateway_attachment_id  = aws_ec2_transit_gateway_vpc_attachment.prod.id
  transit_gateway_route_table_id = aws_ec2_transit_gateway_route_table.inspection.id
}

解説: Inspection-RTB に 0.0.0.0/0 を置くと、インターネット向け以外の戻りパケットがループする。Route Table を Spoke / Inspection に分離し、Inspection-RTB には具体的な Spoke CIDR のみを記述することで経路ループを構造的に防止できる。


演習3: NAT Gateway 全トラフィック → VPC Endpoint (S3/DynamoDB) 切替

アンチパターン — S3/DynamoDB も NAT 経由:

# ❌ アンチパターン: VPC Endpoint なし → 全トラフィックが NAT を経由
resource "aws_route" "private_nat" {
  route_table_id= aws_route_table.private.id
  destination_cidr_block = "0.0.0.0/0"
  nat_gateway_id= aws_nat_gateway.main.id
  # S3 / DynamoDB も NAT を経由 → AZ 跨ぎコストが発生
}

正解パターン — Gateway Endpoint でデータ転送料ゼロ:

# ✅ 正解パターン: S3 / DynamoDB は Gateway Endpoint 経由
resource "aws_vpc_endpoint" "s3" {
  vpc_id= aws_vpc.main.id
  service_name= "com.amazonaws.ap-northeast-1.s3"
  vpc_endpoint_type = "Gateway"
  route_table_ids= [aws_route_table.private.id]
}

resource "aws_vpc_endpoint" "dynamodb" {
  vpc_id= aws_vpc.main.id
  service_name= "com.amazonaws.ap-northeast-1.dynamodb"
  vpc_endpoint_type = "Gateway"
  route_table_ids= [aws_route_table.private.id]
}

# NAT Gateway は S3/DynamoDB 以外のインターネット向けトラフィック専用
resource "aws_route" "private_nat" {
  route_table_id= aws_route_table.private.id
  destination_cidr_block = "0.0.0.0/0"
  nat_gateway_id= aws_nat_gateway.main.id
}

確認コマンド:

# Gateway Endpoint のルートが Route Table に追加されているか確認
aws ec2 describe-route-tables \
  --route-table-ids rtb-xxxxxxxxx \
  --query "RouteTables[*].Routes[?GatewayId!=null && contains(GatewayId,'vpce')]"

解説: Gateway 型 Endpoint は ENI 料金がかからず、データ転送料も無料。Route Table に Prefix List エントリが自動追加され、S3 / DynamoDB への通信は NAT を経由しなくなる。EKS Pod から S3 へのデータ転送が多い環境では月数十万円の削減効果が得られる。


演習4: PrivateLink Acceptance 未設定 → allowed_principals で Consumer 登録

アンチパターン — allowed_principals 未設定で Consumer がリクエストを送れない:

# ❌ アンチパターン: allowed_principals が空
resource "aws_vpc_endpoint_service" "api" {
  acceptance_required  = true
  network_load_balancer_arns = [aws_lb.api_nlb.arn]
  # allowed_principals が空 → Consumer Account がリクエストすら送れない
}

正解パターン — Consumer Account ARN を事前登録:

# ✅ 正解パターン: Consumer Account ARN を allowed_principals に事前登録
resource "aws_vpc_endpoint_service" "api" {
  acceptance_required  = true
  network_load_balancer_arns = [aws_lb.api_nlb.arn]

  allowed_principals = [
 "arn:aws:iam::${var.consumer_account_id}:root",
 "arn:aws:iam::${var.consumer_account_id}:role/api-consumer-role",
  ]

  tags = { Name = "internal-api-endpoint-service" }
}

Consumer 側の接続リクエスト:

# Consumer Account 側: Endpoint 接続リクエスト
aws ec2 create-vpc-endpoint \
  --vpc-id vpc-xxxxxxxxx \
  --service-name com.amazonaws.vpce.ap-northeast-1.vpce-svc-xxxxxxxxx \
  --vpc-endpoint-type Interface \
  --subnet-ids subnet-xxxxxxxxx \
  --security-group-ids sg-xxxxxxxxx

# Provider Account 側: 接続リクエストを承認
aws ec2 accept-vpc-endpoint-connections \
  --service-id vpce-svc-xxxxxxxxx \
  --vpc-endpoint-ids vpce-xxxxxxxxx

解説: allowed_principals に Consumer の Account ARN またはロール ARN を登録しないと、Consumer は Endpoint サービスを検索できてもリクエスト自体が拒否される。acceptance_required = true の場合は Provider 側での手動承認が必要なため、申請フローを自動化する運用が推奨される。


演習5: VPC Lattice Auth Policy なし → IAM Principal 条件付き Allow 設定

アンチパターン — Auth Type が NONE のまま本番デプロイ:

# ❌ アンチパターン: 認証なし (全リクエスト許可)
resource "aws_vpclattice_service" "api" {
  name= "internal-api"
  auth_type = "NONE"
}

正解パターン — AWS_IAM 認証 + Principal 明示:

# ✅ 正解パターン: AWS_IAM 認証 + 呼び出し元ロール ARN を明示
resource "aws_vpclattice_service" "api" {
  name= "internal-api"
  auth_type = "AWS_IAM"
}

resource "aws_vpclattice_auth_policy" "api" {
  resource_identifier = aws_vpclattice_service.api.id

  policy = jsonencode({
 Version = "2012-10-17"
 Statement = [{
Sid= "AllowConsumerRole"
Effect= "Allow"
Principal = {
  AWS = "arn:aws:iam::${var.consumer_account_id}:role/api-consumer-role"
}
Action= "vpc-lattice-svcs:Invoke"
Resource = "*"
Condition = {
  StringEquals = {
 "aws:PrincipalOrgID" = var.org_id
  }
}
 }]
  })
}

検証コマンド:

# Auth Policy が正しく設定されているか確認
aws vpc-lattice get-auth-policy \
  --resource-identifier svc-xxxxxxxxxxxxxxxxx \
  --query "Policy" | jq .

# Lattice サービスの AuthType を確認
aws vpc-lattice get-service \
  --service-identifier svc-xxxxxxxxxxxxxxxxx \
  --query "{AuthType:AuthType}"

解説: auth_type = "NONE" は VPC Lattice サービスの初期デフォルトだが、本番環境では必ず AWS_IAM に変更する。Condition の aws:PrincipalOrgID を指定することで、組織外アカウントからの呼び出しを IAM レベルで拒否できる。


8. まとめ + Vol2予告 + 落とし穴10選 + 全8軸クロスリンク + 第9軸完結宣言

8-1. 本記事で学んだこと

本記事では AWS ネットワーク設計の5大テーマを体系的に解説しました。

セクション学習内容
§2 VPC 基本設計CIDR 設計 / NAT Gateway / Internet Gateway / Subnet 3 層構成
§3 Transit GatewayHub-Spoke 設計 / Route Table 分離 / Inter-Region Peering
§4 VPC Latticeサービス間通信の抽象化 / Auth Policy / Target Group
§5 PrivateLinkNLB + Endpoint Service / SaaS 接続 / Cross-Account 設計
§6 詰まりポイント7選CIDR 重複 / 経路ループ / NAT 過剰課金 / Endpoint 選定ミス / TGW 上限 / Lattice Auth / DNS Resolver
§7 変換演習5問実際の Terraform コードを使ったアンチパターン → 正解パターン学習

AWS ネットワーク設計は「一度設計したら変更困難」な要素が多い反面、正しく設計すれば長期にわたって安定稼働する基盤になります。本記事で学んだ CIDR 設計の基礎から TGW の Route Table 分離、VPC Lattice の Auth Policy 設計、PrivateLink の Cross-Account 接続、そして詰まりポイント7選と変換演習5問は、現場で繰り返し直面する実践的な課題です。

次のステップとして、§2-§5 の内容を自社の AWS 環境に当てはめて Terraform コードを書いてみてください。§6 の詰まりポイントは実際に遭遇する前にチェックリストとして手元に置いておくと障害対応の時間を大幅に削減できます。

本記事で登場した主要 Terraform リソースは以下のとおりです。環境構築の際はこれらを起点に実装を進めてください。

Terraform リソース役割
aws_vpc / aws_subnetVPC と 3 層 Subnet の基本構成
aws_nat_gateway / aws_eipAZ ごとの NAT Gateway
aws_ec2_transit_gatewayTGW と Hub-Spoke ネットワーク
aws_ec2_transit_gateway_route_tableRoute Table 役割別分離
aws_vpclattice_serviceVPC Lattice サービス定義
aws_vpclattice_auth_policyLattice IAM 認証ポリシー
aws_vpc_endpoint_servicePrivateLink Endpoint サービス
aws_vpc_endpointInterface / Gateway Endpoint
aws_vpc_ipamCIDR 一元管理 (IPAM)
aws_route53_resolver_endpointInbound/Outbound Resolver

8-2. 落とし穴10選

#落とし穴対処法
1CIDR 設計を後から変更できないAWS IPAM で全 Account を一元管理
2TGW Route Table の全共有でループ発生Spoke / Inspection に RTB を分離
3NAT Gateway の AZ 跨ぎコスト爆発AZ ごとに 1 台配置 + Gateway Endpoint
4S3 に Interface Endpoint を使うS3 / DynamoDB は必ず Gateway 型
5TGW Attachment 上限を監視しないService Quotas Dashboard で月次確認
6Lattice Auth Policy で Principal に * を設定ロール ARN と PrincipalOrgID を明示
7Resolver Endpoint の Port 53 設定漏れSecurity Group に UDP/TCP 53 を許可
8PrivateLink の allowed_principals 未設定Consumer ARN を事前登録必須
9Inter-Region TGW Peering の料金を見落とすリージョン間データ転送料を事前試算
10VPC Flow Logs 未設定で障害調査が困難全 VPC で Flow Logs を有効化しておく

8-3. Vol2 予告

Network/VPC設計入門 Vol2 では以下のテーマを扱う予定です。

AWS Network Firewall

AWS Network Firewall は VPC 内にデプロイするマネージド型 L7 ファイアウォールです。Stateful ルールでドメイン名フィルタリングや TLS インスペクションが可能で、Inspection VPC パターンと組み合わせることで全 Spoke VPC のトラフィックを一元的に検査できます。Terraform による Firewall Policy / Rule Group の管理と、TGW Hub-Spoke 設計との統合パターンを解説します。

AWS Direct Connect

オンプレミスと AWS を専用線で接続する Direct Connect は、VPN と異なりインターネットを経由しないため安定した帯域が確保できます。LAG (Link Aggregation Group) による冗長化、BGP AS PATH と MED を活用したトラフィック制御、および Direct Connect Gateway を使った複数リージョン接続の設計を Terraform で実装する手順を解説します。

AWS Cloud WAN

Cloud WAN は Transit Gateway を複数リージョンにわたって統合するグローバルネットワークサービスです。Core Network Policy を JSON で定義し、Segment (Spoke / Shared / Inspection) によるトラフィック分離を宣言的に管理できます。大規模 Multi-Account・Multi-Region 環境での TGW との比較と、Cloud WAN への段階的移行戦略を解説します。


8-4. 全8軸クロスリンク — AWS本番運用ナビゲーション

本記事が属する「Network/VPC設計入門」シリーズは、AWS 本番運用の第9軸に位置します。以下の全8軸シリーズと組み合わせることで、AWS を使った本番システムの設計・運用・改善に必要な知識体系が完成します。各シリーズは独立して読めますが、IAM → EKS → ネットワークの順で進むと体系的に習得できます。


8-5. 第9軸完結宣言

🎉 Network/VPC設計入門 Vol1 完結 — AWS本番運用 第9軸 確立

本記事をもって AWS 本番運用9軸 (IAM / EKS / AWSバックアップ / AIシリーズ / セキュリティ / コスト / マルチアカウント / Observability / Network) が揃いました。

各軸シリーズを縦断することで、AWS を使った本番システムを設計・運用・改善するための体系的な知識が習得できます。

次回 Network/VPC設計入門 Vol2 では Network Firewall × Direct Connect × Cloud WAN を扱います。公開をお楽しみに。