★★ 中級

02. VPC とネットワーク

AWS のほぼ全リソースは VPC(仮想ネットワーク) の上で動きます。最初の VPC を作り、パブリック/プライベートのサブネットに分け、インターネットと出入りできるようにルーティングする。これが AWS インフラの「土台 50%」です。

登場人物の早見表

用語役割たとえると
VPCAWS 上のあなた専用の仮想ネットワークマンション全体の専有エリア
SubnetVPC を切り分けた区画。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 を参照する
arnIAM ポリシー条件に書く
default_security_group_idデフォルト SG。使わない のが原則
main_route_table_idVPC のメインルートテーブル

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 が作れます。