VPC 深掘り — Subnet 設計 · Peering · Transit Gateway · PrivateLink
第8章で押さえた VPC の基礎を production 規模へ引き上げます。3-tier / 4-tier の Subnet 設計と CIDR 計画、NAT · Egress-only IGW · VPC Endpoint でインターネットの出入りを Terraform コードとコスト計算まで扱い、VPC Peering と Transit Gateway で VPC をつなぐ方法、PrivateLink、IPv6 dual-stack、マルチ VPC のメンタルモデルまでを整理します。
5部 運用 · セキュリティ · コストの最初の章です。4部まで、私たちは単一 VPC の中で ECS Fargate 上に1つのサービスを載せて運用してきました。第8章 EC2 と VPC の基礎 で VPC · Subnet · ルーティングテーブル · インターネットゲートウェイ · セキュリティグループの基本を押さえたなら、この章はその上で production 規模のネットワークをどう設計するか を扱います。
運用に入ると単一の public Subnet 1つでは足りません。DB はインターネットから届いてはならず、複数の環境と複数の VPC が生まれ、プライベートな区間からも AWS サービスに届く必要があります。この章で押さえる Subnet 階層 · VPC 間の接続 · プライベートな出入りモデルは、第29章 セキュリティガバナンス のマルチアカウントや、6部 フルスタックアプリを AWS にデプロイする のインフラ骨格へそのままつながります。この章のコードは 第25章 Terraform 入門 の HCL を前提とします。
第8章から5部へ #
第8章 で扱ったのは次のとおりです。
- VPC はアカウント内に作るプライベートネットワークであり、CIDR ブロック(例:
10.0.0.0/16)で IP 範囲を定めます。 - Subnet は VPC の中を AZ 単位で区切った区画です。
- インターネットゲートウェイ(IGW)にルーティングがつながった Subnet が public、そうでない Subnet が private です。
- セキュリティグループはインスタンス単位のファイアウォール、ネットワーク ACL は Subnet 単位のファイアウォールです。
この章は、その基本要素を 複数階層 · 複数 AZ · 複数 VPC へ拡張します。
Subnet 設計 — 3-tier と 4-tier #
production VPC の基本骨格は 3-tier です。役割の異なる Subnet を階層に分けます。
| 階層 | インターネット | 入るもの | デフォルトルーティング |
|---|---|---|---|
| public | インバウンド / アウトバウンド | ALB、NAT Gateway、Bastion | 0.0.0.0/0 → IGW |
| private (app) | アウトバウンドのみ | ECS / EC2 アプリ、Lambda(VPC) | 0.0.0.0/0 → NAT Gateway |
| isolated (data) | なし | RDS、ElastiCache | ローカル + VPC Endpoint のみ |
規制の強い環境では、ここに 管理専用階層(Bastion / 運用ツール)を加えて 4-tier に分けることもあります。要点は DB が住む isolated 階層はインターネットへ出る経路そのものがない ということです。アプリは private から NAT を通って出ていき、DB はどこへも出ていきません。階層の間のアクセスはセキュリティグループで一方向ずつだけ開けます — ALB → app(8000)、app → DB(5432) のように 送信元のセキュリティグループを参照 し、IP ではなく役割で制御します。
resource "aws_security_group_rule" "app_from_alb" {
type = "ingress"
security_group_id = aws_security_group.app.id
source_security_group_id = aws_security_group.alb.id # ALB から来たものだけ
from_port = 8000
to_port = 8000
protocol = "tcp"
}
resource "aws_security_group_rule" "db_from_app" {
type = "ingress"
security_group_id = aws_security_group.db.id
source_security_group_id = aws_security_group.app.id # app から来たものだけ
from_port = 5432
to_port = 5432
protocol = "tcp"
}CIDR 計画 #
CIDR は一度決めると変えるのが面倒です。最初に余裕をもって、規則的に切ります。2 AZ × 3階層の例は次のとおりです。
VPC 10.0.0.0/16 (65,536 IP)
AZ a
public 10.0.0.0/20 (4,096)
private-app 10.0.16.0/20 (4,096)
isolated-db 10.0.32.0/20 (4,096)
AZ c
public 10.0.128.0/20 (4,096)
private-app 10.0.144.0/20 (4,096)
isolated-db 10.0.160.0/20 (4,096)- VPC は
/16で大きく取ります。あとから階層や AZ を追加する余地を残します。 - 各 Subnet は
/20(4,096 IP)程度あれば ECS Task が増えても IP が枯渇しません。Fargate Task 1つが ENI 1つ = IP 1つ を使うので、コンテナ密度が高いと IP 枯渇が実際の障害につながります。AWS が予約する Subnet あたり5個(ネットワーク/ブロードキャスト/ゲートウェイなど)も考慮します。 - AZ 同士で上位ビットを離して(
10.0.0.xvs10.0.128.x)、あとから読みやすくします。 - 他の VPC と Peering する可能性があるなら CIDR が重ならないように します。重なった2つの VPC は Peering できません。組織レベルでは VPC ごとに
10.0.0.0/16、10.1.0.0/16のようにあらかじめ番号を割り当てておきます。
ルーティングテーブル #
階層ごとにルーティングテーブルを別々に置きます。ルーティングの違いがすなわち階層の定義です。
# public: インターネットへ双方向
resource "aws_route" "public_internet" {
route_table_id = aws_route_table.public.id
destination_cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.main.id
}
# private-app: AZ 別の NAT でアウトバウンドのみ
resource "aws_route" "private_nat" {
for_each = aws_nat_gateway.az # AZ ごとに1つ
route_table_id = aws_route_table.private[each.key].id
destination_cidr_block = "0.0.0.0/0"
nat_gateway_id = each.value.id
}
# isolated-db: デフォルト経路なし — local のみ。インターネット遮断
# (aws_route_table.isolated に 0.0.0.0/0 経路を置かない)isolated-db のルーティングテーブルに 0.0.0.0/0 経路を 置かないこと が、すなわち DB をインターネットから切り離す方法です。
インターネット出入り — IGW · NAT · Egress-only IGW #
private 階層のアプリは外へ出る必要が多くあります(パッケージのインストール、外部 API 呼び出し、ECR pull)。ところがインバウンドは塞ぐ必要があります。この非対称を解くのが NAT です。
| 装置 | 方向 | 対象 | 備考 |
|---|---|---|---|
| Internet Gateway (IGW) | 双方向 | public Subnet | VPC あたり1個、無料 |
| NAT Gateway | アウトバウンドのみ (IPv4) | private Subnet | 時間あたり + 処理 GB あたり課金。AZ ごとに1個推奨 |
| Egress-only IGW | アウトバウンドのみ (IPv6) | private Subnet | 無料。IPv6 専用 |
resource "aws_eip" "nat" {
for_each = toset(["a", "c"])
domain = "vpc"
}
resource "aws_nat_gateway" "az" {
for_each = aws_eip.nat
allocation_id = each.value.id
subnet_id = aws_subnet.public[each.key].id # NAT は public に住む
}NAT Gateway のコスト注意 #
NAT Gateway は運用の請求書で意外に大きな項目です。時間あたり料金(ソウル基準で約 $0.059/h、NAT 1つあたり月に約 $43)に加えて 処理したデータ GB あたり(約 $0.059/GB)課金されます。そのため private アプリが S3 / ECR / DynamoDB のようにトラフィックの多い AWS サービスを NAT 経由で出入りすると、コストが速く積み上がります。
たとえば AZ 2個に NAT を置き、月に 500GB を処理すると、おおよそ次のようになります。
時間あたり $0.059 × 24h × 30日 × 2個 ≈ $85
処理量 $0.059 × 500GB ≈ $30
─────────
約 $115 / 月減らす方法は次のとおりです。
- AZ ごとに NAT Gateway を1つ — AZ 間のデータ転送コストを避けます。単一 NAT にまとめると安く見えますが、AZ 障害時に全 AZ のアウトバウンドが断たれ、他の AZ のアプリが NAT のある AZ へ越境して AZ 間転送料がつきます。
- VPC Endpoint で NAT を迂回 — S3 と DynamoDB は Gateway Endpoint が無料です。ECR · CloudWatch · Secrets Manager のトラフィックも Interface Endpoint へ送れば NAT の処理コストから外れます。コンテナ環境では ECR pull はトラフィックが大きいので効果が大きいです。
- 学習 / dev 環境は NAT の代わりにアプリを public Subnet に置く、または必要なときだけ NAT を起動するのも方法です。
このコストの観点は、第27章 コスト最適化 や 第30章 災害復旧・バックアップ のマルチ AZ 設計と併せて見ます。
VPC Endpoint と PrivateLink #
private / isolated 階層からも AWS サービス(S3、Secrets Manager、ECR など)には届く必要があります。このときインターネットへ出てから戻る代わりに、VPC Endpoint でプライベートな経路を作ります。
| 種類 | 対象 | 動作 | コスト |
|---|---|---|---|
| Gateway Endpoint | S3、DynamoDB | ルーティングテーブルに経路を追加 | 無料 |
| Interface Endpoint (PrivateLink) | ほとんどの AWS サービス、自作のサービス | Subnet に ENI を生成、プライベート DNS | 時間あたり + GB あたり |
# S3 — ルーティングテーブルに付く無料の Gateway Endpoint
resource "aws_vpc_endpoint" "s3" {
vpc_id = aws_vpc.main.id
service_name = "com.amazonaws.ap-northeast-2.s3"
vpc_endpoint_type = "Gateway"
route_table_ids = [for rt in aws_route_table.private : rt.id]
}
# ECR — private subnet に ENI ができる Interface Endpoint
resource "aws_vpc_endpoint" "ecr_dkr" {
vpc_id = aws_vpc.main.id
service_name = "com.amazonaws.ap-northeast-2.ecr.dkr"
vpc_endpoint_type = "Interface"
subnet_ids = [for s in aws_subnet.private : s.id]
security_group_ids = [aws_security_group.endpoints.id]
private_dns_enabled = true # 既存の ECR ドメインがプライベート IP に解決される
}private_dns_enabled = true なら *.ecr.ap-northeast-2.amazonaws.com のような既存のドメインが自動で Endpoint のプライベート IP に解決され、アプリのコードを変える必要がありません。
PrivateLink は、ある VPC のサービスを別の VPC やアカウントに インターネットなしで 公開する技術でもあります。あるチームが作った内部 API の前に NLB を置いて aws_vpc_endpoint_service で公開すると、消費者側の VPC は Interface Endpoint でそのサービスに接続します。2つの VPC の CIDR が重なってもプライベートに接続できるのが、Peering に対する PrivateLink の強みです。第20章 Secrets Manager / Parameter Store のシークレットを isolated アプリが読むときも、Interface Endpoint がすっきりした経路です。
VPC 同士をつなぐ — Peering と Transit Gateway #
VPC が2つ以上になると(環境分離、アカウント分離、買収したシステムなど)、プライベートにつなぐ2つの方法があります。
VPC Peering #
2つの VPC を 1:1 で直接接続します。接続を作ったあと、両側のルーティングテーブルに相手の CIDR 経路を直接入れて初めて 通じます。
resource "aws_vpc_peering_connection" "a_to_b" {
vpc_id = aws_vpc.a.id
peer_vpc_id = aws_vpc.b.id
auto_accept = true # 同じアカウント/リージョンのとき
}
resource "aws_route" "a_to_b" {
route_table_id = aws_route_table.a.id
destination_cidr_block = aws_vpc.b.cidr_block # 10.1.0.0/16
vpc_peering_connection_id = aws_vpc_peering_connection.a_to_b.id
}
resource "aws_route" "b_to_a" {
route_table_id = aws_route_table.b.id
destination_cidr_block = aws_vpc.a.cidr_block # 10.0.0.0/16
vpc_peering_connection_id = aws_vpc_peering_connection.a_to_b.id
}- 設定が単純で、同じ / 異なるアカウント · リージョンのいずれも可能です。
- 非推移的(non-transitive) です。A–B、B–C を Peering しても A–C は自動では通りません。VPC が N 個あれば接続が N(N-1)/2 個に増え、5個になるだけで10個の Peering とその2倍の経路を管理することになります。
- 両側の CIDR が重なってはいけません。
Transit Gateway #
複数の VPC を1つのハブに付ける方式です。
resource "aws_ec2_transit_gateway" "hub" {
description = "org hub"
default_route_table_association = "enable"
default_route_table_propagation = "enable"
}
resource "aws_ec2_transit_gateway_vpc_attachment" "a" {
transit_gateway_id = aws_ec2_transit_gateway.hub.id
vpc_id = aws_vpc.a.id
subnet_ids = [for s in aws_subnet.a_private : s.id]
}
# VPC b, c... も同じように1回ずつだけ付ける- 各 VPC を Transit Gateway に1回ずつ付ければよいです(スター構造)。VPC が増えても接続数が線形にしか増えません。
- TGW ルーティングテーブル でどの VPC 同士が通信するかを制御します。たとえば dev と prod を互いに見えないように分離しつつ、両方とも共有サービス VPC には届くようにできます。オンプレミス(VPN / Direct Connect)も同じハブに付きます。
- 時間あたり(アタッチあたり)+ 処理 GB あたり課金があるため、VPC が 2 ~ 3個と少ないときは Peering の方が安いです。
判断基準: VPC が 2 ~ 3個なら Peering、それ以上に増える兆しが見えれば Transit Gateway です。マルチアカウントガバナンス(第29章)へ向かう組織は、最初から Transit Gateway を敷いておく方が後で楽です。6部キャップストーンは単一 VPC なのでどちらも使いませんが、2つ目の環境用アカウントが生まれた瞬間にこの判断が登場します。
IPv6 導入の判断ガイド #
IPv6 はすべてのプロジェクトに必要なわけではありません。次のときに検討します。
- アウトバウンドトラフィックが多く NAT Gateway のコスト が負担なとき。IPv6 のアウトバウンドは無料の Egress-only IGW で処理されるので、NAT コストを減らせます。
- パブリック IPv4 アドレスが 有料(2024年2月から、使用中のパブリック IPv4 1つあたり時間あたり課金)なので、大量のパブリック IP が必要なワークロードでコスト削減の余地があるとき。
- 外部から IPv6 で入ってくるクライアントを直接受ける必要があるとき。
ほとんどは dual-stack(IPv4 + IPv6 同時)で導入します。
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
assign_generated_ipv6_cidr_block = true # AWS が /56 IPv6 を割り当て
}
resource "aws_egress_only_internet_gateway" "main" {
vpc_id = aws_vpc.main.id # IPv6 アウトバウンド (無料)
}
resource "aws_route" "private_ipv6" {
route_table_id = aws_route_table.private["a"].id
destination_ipv6_cidr_block = "::/0"
egress_only_gateway_id = aws_egress_only_internet_gateway.main.id
}単純なバックエンド1台なら IPv4 だけで十分なので、IPv6 はコストや要件が明確なときに入れます。
マルチ VPC のメンタルモデル #
VPC をいつ分けるかの基準です。
- 単一 VPC + 階層 Subnet — 1つのサービス、1つのチーム、1つの環境ならこの骨格で十分です。4部まで私たちが使ってきたモデルです。
- 環境別 VPC — dev / staging / prod を VPC で分けると、事故の爆発半径が環境の中に閉じ込められます。ただし本当の隔離は アカウント分離(第29章)の方が強いです。
- アカウント + VPC の組み合わせ — 規模が大きくなれば、アカウントを環境 / チーム別に分け、各アカウントに VPC を置き、Transit Gateway で必要な分だけつなぎます。これが production 組織の一般的な姿です。
VPC を分ける一次的な動機は、たいてい セキュリティ境界と爆発半径 です。その深い話は、次の 第29章 セキュリティガバナンス の Organizations · SCP へつながります。
練習問題 #
- ご自身のサービスの VPC を
10.0.0.0/16とし、2 AZ × 3-tier(public / private-app / isolated-db)で Subnet 6個の CIDR を自分で割り当ててみてください。各 Subnet のルーティングテーブルが IGW / NAT / ローカルのうち何をデフォルト経路に持つかを1行ずつ書き、isolated 階層になぜ0.0.0.0/0経路を置かないかを説明してみてください。第32章 フルスタックアプリを AWS にデプロイする の Terraform VPC モジュールを書くとき、この表がそのまま入力になります。 - private アプリが ① S3、② ECR(イメージ pull)、③ 外部決済 API の3か所へアウトバウンドするとき、それぞれを NAT Gateway · Gateway Endpoint · Interface Endpoint のどれで送ればコストが最小になるかと、その理由を §「NAT Gateway のコスト注意」の概算を根拠に書いてみてください。
- VPC が現在2個で、1年以内に 5 ~ 6個に増える計画があるなら、Peering と Transit Gateway のどちらで始めるかを決め、接続数(N(N-1)/2 vs 線形)とルーティング管理の負担を根拠に1段落で書いてみてください。
一行まとめ: production VPC は public / private-app / isolated-db の 3-tier で設計し、DB 階層はルーティングテーブルに
0.0.0.0/0経路そのものを置かないことでインターネットから切り離す。階層間のアクセスはセキュリティグループ参照で役割ベースに開ける。private アプリのアウトバウンドは AZ 別の NAT Gateway へ送りつつ、S3 · DynamoDB は無料の Gateway Endpoint、ECR などは Interface Endpoint で迂回して時間あたり + GB あたりの NAT コストを減らす。VPC が 2 ~ 3個なら Peering(非推移的、両側の経路を手動)、さらに増えたら Transit Gateway(スターハブ、TGW ルーティングで制御)でつなぐ。PrivateLink は CIDR が重なってもプライベートにサービスを公開し、IPv6 は NAT/パブリック IPv4 のコストが負担なときに dual-stack + Egress-only IGW で導入する。VPC を分ける一次的な動機はセキュリティ境界と爆発半径。
次の章 #
次の 第29章 セキュリティガバナンス では、単一アカウントからマルチアカウントへ移る時点を扱います。AWS Organizations · SCP · Control Tower でアカウントをまとめてポリシーを一括適用するモデル、そして GuardDuty · Security Hub · Config でアカウント全体を監視するガバナンスを整理します。この章のマルチ VPC がマルチアカウントへ拡張される自然な次のステップです。