02. VPC とネットワーク
AWS のほぼ全リソースは VPC(仮想ネットワーク) の上で動きます。最初の VPC を作り、パブリック/プライベートのサブネットに分け、インターネットと出入りできるようにルーティングする。これが AWS インフラの「土台 50%」です。
この章の目次
登場人物の早見表
| 用語 | 役割 | たとえると |
|---|---|---|
| VPC | AWS 上のあなた専用の仮想ネットワーク | マンション全体の専有エリア |
| Subnet | VPC を切り分けた区画。1 つの AZ に紐づく | マンションの各フロア |
| Internet Gateway (IGW) | VPC をインターネットにつなぐ玄関 | 建物のメイン出入口 |
| Route Table | 「このサブネットの通信は、ここに送る」のルール表 | 各部屋の郵便配送ルール |
| NAT Gateway | プライベートサブネットの「外向きだけ」通信を可能にする | 外には出られるが、外からは入れない通用口 |
| Elastic IP (EIP) | 固定のグローバル IP アドレス | NAT Gateway 等に貼り付ける番地 |
| Security Group (SG) | リソース単位のファイアウォール | 各部屋のドアの鍵 |
標準トポロジ図
2 つの AZ にまたがる、Web 3 層構成の標準図です。
インターネット
│
┌───────┴───────┐
│ Internet GW │
└───────┬───────┘
│
┌─────────────────┴─────────────────┐
│ VPC (10.0.0.0/16) │
│ │
│ ┌─ AZ-a ─────────┐ ┌─ AZ-c ────┐ │
│ │ Public Subnet │ │ Public │ │ ← IGW にルート
│ │ 10.0.1.0/24 │ │ 10.0.2.0/24│ │
│ │ ・ALB / NAT GW│ │ │ │
│ └────────┬───────┘ └─────┬─────┘ │
│ │ NAT GW │ │
│ ┌────────┴───────┐ ┌──────┴────┐ │
│ │ Private Subnet │ │ Private │ │ ← NAT GW にルート
│ │ 10.0.11.0/24 │ │ 10.0.12.0/24│ │
│ │ ・EC2 / ECS │ │ │ │
│ └────────────────┘ └───────────┘ │
│ │
│ ┌────────────────┐ ┌───────────┐ │
│ │ DB Subnet │ │ DB Subnet │ │ ← ルートなし(外部不通)
│ │ 10.0.21.0/24 │ │ 10.0.22.0/24│ │
│ └────────────────┘ └───────────┘ │
└────────────────────────────────────┘
ポイントは、サブネットの種類はネットワーク的に決まる こと。「パブリックサブネット」とは、ルートテーブルが 0.0.0.0/0 → IGW を持っているサブネットのことを指します。AWS のサブネット属性ではなく ルーティング で決まる。
aws_vpc
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16" # /16 で 65,536 IP 確保
enable_dns_support = true
enable_dns_hostnames = true # EC2 にパブリック DNS が付与される
tags = {
Name = "main"
}
}
覚えておきたい属性
| 属性 | 用途 |
|---|---|
id | 他リソースから VPC を参照する |
arn | IAM ポリシー条件に書く |
default_security_group_id | デフォルト SG。使わない のが原則 |
main_route_table_id | VPC のメインルートテーブル |
aws_subnet
data "aws_availability_zones" "available" {
state = "available"
}
# パブリックサブネット × 2 (各 AZ)
resource "aws_subnet" "public" {
for_each = {
"a" = { cidr = "10.0.1.0/24", az = data.aws_availability_zones.available.names[0] }
"c" = { cidr = "10.0.2.0/24", az = data.aws_availability_zones.available.names[1] }
}
vpc_id = aws_vpc.main.id
cidr_block = each.value.cidr
availability_zone = each.value.az
map_public_ip_on_launch = true # ここに置く EC2 は public IP 自動付与
tags = {
Name = "public-${each.key}"
Tier = "public"
}
}
# プライベートサブネット × 2
resource "aws_subnet" "private" {
for_each = {
"a" = { cidr = "10.0.11.0/24", az = data.aws_availability_zones.available.names[0] }
"c" = { cidr = "10.0.12.0/24", az = data.aws_availability_zones.available.names[1] }
}
vpc_id = aws_vpc.main.id
cidr_block = each.value.cidr
availability_zone = each.value.az
tags = {
Name = "private-${each.key}"
Tier = "private"
}
}
aws_internet_gateway / aws_route_table
resource "aws_internet_gateway" "main" {
vpc_id = aws_vpc.main.id
tags = { Name = "main" }
}
resource "aws_route_table" "public" {
vpc_id = aws_vpc.main.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.main.id
}
tags = { Name = "public" }
}
# 各 public サブネットに、上のルートテーブルを紐付ける
resource "aws_route_table_association" "public" {
for_each = aws_subnet.public
subnet_id = each.value.id
route_table_id = aws_route_table.public.id
}
aws_nat_gateway / aws_eip
プライベートサブネットの中の EC2 が 外向き通信(パッケージ更新、外部 API 呼び出し)をしたい時に必要。NAT Gateway は パブリックサブネットに置く。
resource "aws_eip" "nat" {
domain = "vpc"
tags = { Name = "nat" }
}
resource "aws_nat_gateway" "main" {
allocation_id = aws_eip.nat.id
subnet_id = aws_subnet.public["a"].id # public 側に置く
tags = { Name = "main" }
depends_on = [aws_internet_gateway.main] # IGW があってからじゃないと NAT は動かない
}
resource "aws_route_table" "private" {
vpc_id = aws_vpc.main.id
route {
cidr_block = "0.0.0.0/0"
nat_gateway_id = aws_nat_gateway.main.id
}
tags = { Name = "private" }
}
resource "aws_route_table_association" "private" {
for_each = aws_subnet.private
subnet_id = each.value.id
route_table_id = aws_route_table.private.id
}
コスト注意
NAT Gateway は 1 時間あたり $0.045 + データ処理量。1 か月で $35 + α。学習や開発環境で常時上げっぱなしにするとボディブローのように効きます。
terraform destroy を忘れずに。
aws_security_group の現代的書き方
以前は ingress { ... }、egress { ... } を inline で書く形が主流でしたが、2023 年以降は分離型のリソース が公式推奨です。差分が小さくなり、ルール単位で管理できます。
resource "aws_security_group" "web" {
name_prefix = "web-"
description = "Web tier (ALB)"
vpc_id = aws_vpc.main.id
lifecycle {
create_before_destroy = true # 入れ替え時に通信断を起こさない
}
tags = { Name = "web" }
}
# 個別ルールは別リソースで(推奨)
resource "aws_vpc_security_group_ingress_rule" "web_http" {
security_group_id = aws_security_group.web.id
description = "HTTP from anywhere"
from_port = 80
to_port = 80
ip_protocol = "tcp"
cidr_ipv4 = "0.0.0.0/0"
}
resource "aws_vpc_security_group_ingress_rule" "web_https" {
security_group_id = aws_security_group.web.id
from_port = 443
to_port = 443
ip_protocol = "tcp"
cidr_ipv4 = "0.0.0.0/0"
}
resource "aws_vpc_security_group_egress_rule" "web_all" {
security_group_id = aws_security_group.web.id
ip_protocol = "-1" # all
cidr_ipv4 = "0.0.0.0/0"
}
# アプリ層: ALB だけ受ける
resource "aws_security_group" "app" {
name_prefix = "app-"
description = "App tier"
vpc_id = aws_vpc.main.id
lifecycle { create_before_destroy = true }
}
resource "aws_vpc_security_group_ingress_rule" "app_from_web" {
security_group_id = aws_security_group.app.id
referenced_security_group_id = aws_security_group.web.id # ← SG 同士で参照
from_port = 8080
to_port = 8080
ip_protocol = "tcp"
}
完成形
上で書いた要素を 1 ファイルに集約した main.tf 例(このサイト自身の Terraform リポジトリにも収録予定)。terraform apply で 2-AZ の Web 3 層 VPC が作れます。