0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Terraform : 第1回 VPCを構築する - 全体像とネットワーク基盤

0
Posted at

はじめに

Terraformで再利用可能なVPCモジュールを構築する手順を、2回に分けて解説します。

  • 第1回(本記事): 全体像とネットワーク基盤(VPC・サブネット・ルーティング・NAT Gateway)
  • 第2回: セキュリティグループ・監査編(デフォルトSGの制限、VPC Flow Logs)

第1回では、まずこれから作るネットワークの全体像と、サブネットに何を配置するかの前提を共有した上で、実際に通信が成立する骨格部分をTerraformコードで組み立てます。

配置の前提

サブネットの設計は「何を置くか」によって決まります。本シリーズでは、以下の構成を前提とします。

サブネット 配置するもの 理由
パブリックサブネット ALB、NAT Gateway インターネットからの入口・出口として直接通信が必要
プライベートサブネット バックエンドAPI(ECS)、フロントエンド Next.js(ECS)、RDS インターネットから直接アクセスさせず、ALB経由でのみ到達させたいリソース

フロントエンド(Next.js)とバックエンドAPIはどちらもコンテナとしてプライベートサブネットに置き、ALBがパスなどに応じて振り分ける構成を想定しています。RDSも直接インターネットからは到達不可な状態で、プライベートサブネットのみに配置します。

全体像

配置の前提を踏まえた全体像は以下の通りです。

                              Internet
                                 │
                        [Internet Gateway]
                                 │
┌────────────────────────────── VPC ───────────────────────────────┐
│                                                                    │
│   AZ-a                              AZ-c                          │
│  ┌───────────────────────┐        ┌───────────────────────┐      │
│  │ Public Subnet          │        │ Public Subnet          │     │
│  │  [ALB]  [NAT Gateway]  │        │  [ALB]                 │     │
│  └───────────┬────────────┘        └───────────┬────────────┘     │
│              │ 0.0.0.0/0 → NAT GW                │                 │
│  ┌───────────┴────────────┐        ┌────────────┴──────────┐      │
│  │ Private Subnet          │        │ Private Subnet          │    │
│  │ [ECS: バックエンドAPI]  │        │ [ECS: バックエンドAPI]  │    │
│  │ [ECS: Next.js]          │        │ [ECS: Next.js]          │    │
│  │ [RDS]                   │        │ [RDS: スタンバイ]       │    │
│  └─────────────────────────┘        └───────────────────────┘     │
│                                                                     │
└─────────────────────────────────────────────────────────────────┘

ポイントは3つです。

  1. サブネットは「パブリック」と「プライベート」の2種類に分ける。ALBやNAT Gatewayのような「外部との窓口」だけをパブリックサブネットに置き、バックエンドAPI・Next.js・RDSといったアプリケーション本体はプライベートサブネットに置きます。
  2. サブネットは複数のAZ(アベイラビリティゾーン)に分散配置する。1つのAZだけに集めると、そのAZに障害が起きたときにサービス全体が止まってしまうためです。
  3. 「行き」の通信経路をルートテーブルで制御する。パブリックサブネットはインターネットゲートウェイ(IGW)経由で直接インターネットへ、プライベートサブネットはNATゲートウェイを経由して間接的にインターネットへ出ます(バックエンドAPIやNext.jsコンテナが外部APIを呼んだり、パッケージを取得したりするために必要)。

なお、本記事ではVPCモジュール(ネットワークの骨格)のみを対象とし、ALB・ECS・RDS自体の構築は扱いません。「これらが将来乗ることを見越したサブネット設計」という位置づけで進めます。

対象リソースは以下の10種類です。

# リソースタイプ ローカル名 役割
1 aws_vpc this VPC本体
2 aws_internet_gateway this インターネットゲートウェイ
3 aws_subnet public(count) パブリックサブネット
4 aws_route_table public パブリック用ルートテーブル
5 aws_route_table_association public(count) パブリックサブネットの紐付け
6 aws_subnet private(count) プライベートサブネット
7 aws_eip nat NAT Gateway用EIP
8 aws_nat_gateway this NATゲートウェイ
9 aws_route_table private プライベート用ルートテーブル
10 aws_route_table_association private(count) プライベートサブネットの紐付け

セキュリティグループ(デフォルトSG)とVPC Flow Logsは第2回で扱います。

ステップ1: VPCとインターネットゲートウェイを作る

すべての土台となるVPC本体を作成します。DNS解決とDNSホスト名の付与を有効化しておかないと、後々ECSやRDSなどでプライベートDNSを使う際につまずくため、最初から有効にしておきます。

resource "aws_vpc" "this" {
  cidr_block           = var.vpc_cidr
  enable_dns_support   = true
  enable_dns_hostnames = true

  tags = {
    Name = "${var.project}-${var.env}-vpc"
  }
}

VPCを外部(インターネット)とつなぐための出入り口として、インターネットゲートウェイ(IGW)を1つ作成し、VPCにアタッチします。IGWはVPCに1つあれば十分で、後述するパブリックサブネットのルートテーブルから参照されます。

resource "aws_internet_gateway" "this" {
  vpc_id = aws_vpc.this.id

  tags = {
    Name = "${var.project}-${var.env}-igw"
  }
}

ステップ2: サブネットをAZに分散して作る

パブリック/プライベートの2種類のサブネットを、複数のAZに分散して作成します。CIDRとAZのリストをcountで回すことで、AZ数を可変にできます。

resource "aws_subnet" "public" {
  count = length(var.public_subnet_cidrs)

  vpc_id                  = aws_vpc.this.id
  cidr_block              = var.public_subnet_cidrs[count.index]
  availability_zone       = var.availability_zones[count.index]
  map_public_ip_on_launch = true

  tags = {
    Name = "${var.project}-${var.env}-public-${count.index + 1}"
    Tier = "public"
  }
}

resource "aws_subnet" "private" {
  count = length(var.private_subnet_cidrs)

  vpc_id            = aws_vpc.this.id
  cidr_block        = var.private_subnet_cidrs[count.index]
  availability_zone = var.availability_zones[count.index]

  tags = {
    Name = "${var.project}-${var.env}-private-${count.index + 1}"
    Tier = "private"
  }
}

パブリックサブネットにはmap_public_ip_on_launch = trueを指定しています。これにより、このサブネットに起動したリソース(ALBなど)へ自動でパブリックIPが割り当てられます。プライベートサブネットにはこの指定をしないため、パブリックIPは付与されません。

対応する変数定義は以下の通りです。public_subnet_cidrsprivate_subnet_cidrsavailability_zonesは要素数を揃えて渡す前提のリストです。例えば2AZ構成なら、それぞれ2要素のリストを渡します。

variable "project" { type = string }
variable "env" { type = string }
variable "vpc_cidr" { type = string }
variable "public_subnet_cidrs" { type = list(string) }
variable "private_subnet_cidrs" { type = list(string) }
variable "availability_zones" { type = list(string) }

ステップ3: パブリックサブネットの経路をIGWに向ける

サブネットを作っただけでは、まだどこにも通信できません。ルートテーブルで「どこ宛の通信をどこへ送るか」を定義する必要があります。パブリックサブネット用のルートテーブルは、0.0.0.0/0(すべての宛先)をIGWへ向けます。これにより、パブリックサブネットに置かれたALBはインターネットと直接通信できるようになります。

resource "aws_route_table" "public" {
  vpc_id = aws_vpc.this.id

  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = aws_internet_gateway.this.id
  }

  tags = {
    Name = "${var.project}-${var.env}-rtb-public"
  }
}

resource "aws_route_table_association" "public" {
  count = length(aws_subnet.public)

  subnet_id      = aws_subnet.public[count.index].id
  route_table_id = aws_route_table.public.id
}

aws_route_table_associationは、作成したルートテーブルとサブネットを紐付けるためのリソースです。これを行って初めて、そのサブネットにそのルートテーブルの経路が適用されます。

ステップ4: プライベートサブネットの経路をNAT Gateway経由でインターネットに向ける

プライベートサブネットはIGWに直接つなぎません。代わりに、パブリックサブネット上にNATゲートウェイを置き、そこを経由して間接的にインターネットへアクセスできるようにします。これにより、外部からバックエンドAPI・Next.js・RDSへ直接アクセスすることはできない一方、これらのリソース側からの通信(パッケージのダウンロードや外部API呼び出しなど)は可能になります。

resource "aws_eip" "nat" {
  domain = "vpc"

  tags = {
    Name = "${var.project}-${var.env}-eip-nat"
  }
}

resource "aws_nat_gateway" "this" {
  allocation_id = aws_eip.nat.id
  subnet_id     = aws_subnet.public[0].id

  tags = {
    Name = "${var.project}-${var.env}-natgw"
  }

  depends_on = [aws_internet_gateway.this]
}

aws_nat_gateway.thissubnet_idaws_subnet.public[0].id固定になっている点に注目してください。これは、AZ冗長化をせず1台のNATゲートウェイのみを作成する構成です。EIPとNATゲートウェイの時間課金を抑えられる一方、そのAZに障害が起きると全プライベートサブネットの外部通信が止まるというトレードオフがあります。可用性を優先する場合は、この部分をcount = length(var.availability_zones)に変更し、EIPとNATゲートウェイをAZ数分作成した上で、後述のプライベート用ルートテーブルもAZごとに分離する構成に拡張します。

プライベートサブネット用のルートテーブルは、0.0.0.0/0をこのNATゲートウェイへ向けます。

resource "aws_route_table" "private" {
  vpc_id = aws_vpc.this.id

  route {
    cidr_block     = "0.0.0.0/0"
    nat_gateway_id = aws_nat_gateway.this.id
  }

  tags = {
    Name = "${var.project}-${var.env}-rtb-private"
  }
}

resource "aws_route_table_association" "private" {
  count = length(aws_subnet.private)

  subnet_id      = aws_subnet.private[count.index].id
  route_table_id = aws_route_table.private.id
}

ここまでで、全体像の図で示した「通信が成立するネットワークの骨格」が完成しました。ALB・ECS(バックエンドAPI/Next.js)・RDSといった実際のワークロードは、このVPCモジュールが公開するサブネットIDを受け取って、それぞれ別モジュールとして構築していく形になります。

まとめ

  1. VPCとIGWを作る(土台)
  2. パブリック/プライベートサブネットをAZに分散して作る(区画分け・配置の前提に基づく設計)
  3. パブリックサブネットの経路をIGWに向ける(ALBの入出口)
  4. プライベートサブネットの経路をNAT Gateway経由でインターネットに向ける(バックエンドAPI・Next.js・RDSの出口)

次回では、VPC作成時に自動生成される全許可のデフォルトセキュリティグループを制限する設計と、VPC内の通信を可視化するVPC Flow Logsの設定、モジュールのoutputsによる他モジュールとの連携設計を解説します。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?