この記事について
| 記事 | タイトル | 状態 |
|---|---|---|
| 第0回 | 全体ガイド | ✅ 完了 |
| 第1回 | 構成図編 | ✅ 完了 |
| 第2回 | VPC & Subnet | 📍今回 |
| 第3回 | NAT Gateway & Route Table | ⬜ 未読 |
| 第4回 | Security Group | ⬜ 未読 |
| 第5回 | ECR & Docker Image | ⬜ 未読 |
| 第6回 | Public ALB | ⬜ 未読 |
| 第7回 | ECS Front & IAM | ⬜ 未読 |
| 第8回 | ECS API & Internal ALB | ⬜ 未読 |
| 第9回 | RDS MySQL | ⬜ 未読 |
| 第10回 | Secrets Manager & 完成 | ⬜ 未読 |
進捗: 18% (2/11記事) | Phase: 1-1 | 目標: VPC、Subnet × 6、Internet Gateway作成
📁 完全なコードはGitHubで公開:👉 GitHub: fargate-iac-02
1. はじめに
この記事は、**第1回(構成図編)**で作成した構成図を元に、実際にTerraformでAWSリソースを構築していくシリーズの第2回です。
1-1. 前回までの振り返り
第0回:
- シリーズ全体のナビゲーション
第1回:
- 4種類(5図)の構成図を作成
- 全体の設計方針を決定
今回(第2回):
- Phase 1-1を実装
- VPC、Subnet、Internet Gatewayを作成
1-2. この記事で学べること
- VPCの基本と設計思想
- Subnetの役割(Public/Private/DB)
- CIDR設計の考え方
- Multi-AZ構成の利点
- Terraformの基本操作(init/plan/apply)
1-3. 対象読者
- 第1回を読んだ方
- AWSアカウントを持っている方
- Terraformをインストール済みの方
1-4. 想定環境
- Terraform: v1.9.x
- AWS CLI: v2.x
- OS: macOS / Linux / Windows(WSL2)
2. Phase 1-1で作成するもの
2-1. リソース一覧
| リソース | 数量 | 配置場所 | 用途 |
|---|---|---|---|
| VPC | 1 | - | ネットワークの基盤 |
| Public Subnet | 2 | AZ-1a, AZ-1c | NAT Gateway、Public ALB用 |
| Private Subnet | 2 | AZ-1a, AZ-1c | ECS Task用 |
| DB Subnet | 2 | AZ-1a, AZ-1c | RDS用 |
| Internet Gateway | 1 | VPC境界 | VPCの出入口 |
合計: 9リソース
2-2. 構成図での位置づけ
Phase 1-1で作成するのは、配置構成図の以下の部分です:
VPC: 10.0.0.0/16
├─ Internet Gateway
│
├─ Public Subnet A: 10.0.1.0/24 (AZ-1a)
├─ Public Subnet C: 10.0.2.0/24 (AZ-1c)
│
├─ Private Subnet A: 10.0.10.0/24 (AZ-1a)
├─ Private Subnet C: 10.0.11.0/24 (AZ-1c)
│
├─ DB Subnet A: 10.0.20.0/24 (AZ-1a)
└─ DB Subnet C: 10.0.21.0/24 (AZ-1c)
次回(第3回)で追加:
- NAT Gateway × 2
- Route Table × 2
3. VPCとSubnetの設計思想
3-1. VPCとは
VPC (Virtual Private Cloud) は、AWS上に作成する仮想ネットワークです。
料理で例えると:
- VPC = レストランの建物全体
- Subnet = 厨房、ホール、倉庫などの各部屋
- Internet Gateway = 正面玄関
3-2. CIDR設計の考え方
CIDR (Classless Inter-Domain Routing) は、IPアドレスの範囲を表す記法です。
今回の設計:
| 用途 | CIDR | 利用可能IP数 | 理由 |
|---|---|---|---|
| VPC | 10.0.0.0/16 | 65,536 | 将来の拡張を考慮 |
| Public Subnet | 10.0.1.0/24 | 256 | NAT Gateway、ALB用 |
| Private Subnet | 10.0.10.0/24 | 256 | ECS Task用 |
| DB Subnet | 10.0.20.0/24 | 256 | RDS用 |
ポイント:
- VPCは広めに確保(/16)
- Subnetは目的別に分離(Public/Private/DB)
- 将来の追加Subnetを考慮して番号を飛ばす(1, 10, 20)
3-3. Multi-AZ構成
AZ (Availability Zone) は、物理的に独立したデータセンターです。
今回の設計:
- AZ-1a: ap-northeast-1a(東京リージョンのAZ-A)
- AZ-1c: ap-northeast-1c(東京リージョンのAZ-C)
メリット:
- ✅ 片方のAZが障害でも、もう片方で稼働継続
- ✅ AWSが推奨する高可用性構成
- ✅ RDS Multi-AZの前提条件
料理で例えると:
- AZ-1a = 本店
- AZ-1c = 支店
- 片方が停電でも、もう片方で営業継続
4. ディレクトリ構成
4-1. プロジェクト全体の構成
my-ecs-project/
├── docker-compose/ # 元のdocker-compose構成
└── terraform/
└── phase1-network/ 👉 今回作成
├── main.tf
├── variables.tf
├── terraform.tfvars
├── vpc.tf
├── subnet.tf
├── igw.tf
└── outputs.tf
4-2. Phase 1-1のファイル構成
| ファイル名 | 役割 | 行数 |
|---|---|---|
| main.tf | Provider設定、Terraform設定 | 20 |
| variables.tf | 変数定義 | 40 |
| terraform.tfvars | 変数の実際の値 | 10 |
| vpc.tf | VPC定義 | 15 |
| subnet.tf | Subnet × 6定義 | 80 |
| igw.tf | Internet Gateway定義 | 10 |
| outputs.tf | 他Phaseで使う値を出力 | 30 |
合計: 7ファイル、約205行
5. ファイル別コード解説
5-1. main.tf - Provider設定
ファイルの役割:
- Terraformのバージョン指定
- AWSプロバイダーの設定
- デフォルトタグの設定
コード:
# terraform/phase1-network/main.tf
terraform {
required_version = ">= 1.9.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = var.region
default_tags {
tags = {
Project = var.project_name
ManagedBy = "Terraform"
Phase = "1"
}
}
}
ポイント:
- required_version: Terraform 1.9.0以上を要求
- required_providers: AWS Provider 5.x系を使用
-
default_tags: すべてのリソースに自動的にタグを付与
- Project: プロジェクト名
- ManagedBy: Terraformで管理していることを明示
- Phase: Phase番号(後で削除時に便利)
料理で例えると:
- main.tf = レシピの冒頭(使う調理器具の指定)
- Terraform version = 調理器具のバージョン
- default_tags = すべての料理に店名のラベルを貼る
5-2. variables.tf - 変数定義
ファイルの役割:
- 変数の定義(型、説明、デフォルト値)
コード:
# terraform/phase1-network/variables.tf
variable "project_name" {
description = "プロジェクト名(リソース名のプレフィックス)"
type = string
}
variable "region" {
description = "AWSリージョン"
type = string
default = "ap-northeast-1"
}
variable "azs" {
description = "利用するAvailability Zone"
type = list(string)
default = ["ap-northeast-1a", "ap-northeast-1c"]
}
variable "vpc_cidr" {
description = "VPCのCIDRブロック"
type = string
}
variable "public_subnet_cidrs" {
description = "Public SubnetのCIDRブロック"
type = list(string)
}
variable "private_subnet_cidrs" {
description = "Private SubnetのCIDRブロック"
type = list(string)
}
variable "db_subnet_cidrs" {
description = "DB SubnetのCIDRブロック"
type = list(string)
}
ポイント:
- description: 変数の説明(日本語OK)
- type: 変数の型(string、list、map等)
- default: デフォルト値(オプション)
変数の種類:
- 共通変数: project_name、region、azs
- Phase 1固有変数: vpc_cidr、subnet_cidrs
5-3. terraform.tfvars - 変数の値
ファイルの役割:
- 変数の実際の値を設定
コード:
# terraform/phase1-network/terraform.tfvars
project_name = "myproject"
region = "ap-northeast-1"
azs = ["ap-northeast-1a", "ap-northeast-1c"]
vpc_cidr = "10.0.0.0/16"
public_subnet_cidrs = ["10.0.1.0/24", "10.0.2.0/24"]
private_subnet_cidrs = ["10.0.10.0/24", "10.0.11.0/24"]
db_subnet_cidrs = ["10.0.20.0/24", "10.0.21.0/24"]
ポイント:
- project_name: 好きな名前に変更可能
- CIDR設計: 10.0.0.0/16をベースに設計
- 配列の順序: [AZ-1a, AZ-1c]の順で統一
カスタマイズ例:
# 大阪リージョンを使う場合
region = "ap-northeast-3"
azs = ["ap-northeast-3a", "ap-northeast-3c"]
# プロジェクト名を変更
project_name = "my-awesome-app"
5-4. vpc.tf - VPC
ファイルの役割:
- VPCを作成
コード:
# terraform/phase1-network/vpc.tf
resource "aws_vpc" "main" {
cidr_block = var.vpc_cidr
enable_dns_hostnames = true
enable_dns_support = true
tags = {
Name = "${var.project_name}-vpc"
}
}
ポイント:
- cidr_block: VPCのIPアドレス範囲
- enable_dns_hostnames: DNS名を有効化(ECS Fargateで必要)
- enable_dns_support: DNS解決を有効化
- tags: リソースに名前を付ける
DNSを有効化する理由:
- ECS TaskがECRからイメージをpullする際に必要
- RDSのエンドポイント名を解決する際に必要
5-5. subnet.tf - Subnet × 6
ファイルの役割:
- Public Subnet × 2、Private Subnet × 2、DB Subnet × 2を作成
コード:
# terraform/phase1-network/subnet.tf
# ==========================================
# Public Subnet × 2
# ==========================================
resource "aws_subnet" "public" {
count = 2
vpc_id = aws_vpc.main.id
cidr_block = var.public_subnet_cidrs[count.index]
availability_zone = var.azs[count.index]
map_public_ip_on_launch = true
tags = {
Name = "${var.project_name}-public-subnet-${count.index + 1}"
}
}
# ==========================================
# Private Subnet × 2
# ==========================================
resource "aws_subnet" "private" {
count = 2
vpc_id = aws_vpc.main.id
cidr_block = var.private_subnet_cidrs[count.index]
availability_zone = var.azs[count.index]
tags = {
Name = "${var.project_name}-private-subnet-${count.index + 1}"
}
}
# ==========================================
# DB Subnet × 2
# ==========================================
resource "aws_subnet" "db" {
count = 2
vpc_id = aws_vpc.main.id
cidr_block = var.db_subnet_cidrs[count.index]
availability_zone = var.azs[count.index]
tags = {
Name = "${var.project_name}-db-subnet-${count.index + 1}"
}
}
ポイント:
- count = 2: 同じリソースを2つ作成
- count.index: 0, 1の順で繰り返す
- map_public_ip_on_launch: Public Subnetのみtrue
Subnet別の特徴:
| Subnet種別 | map_public_ip_on_launch | 用途 |
|---|---|---|
| Public | true | 起動時にパブリックIPを自動付与 |
| Private | false | パブリックIPは付与しない |
| DB | false | パブリックIPは付与しない |
料理で例えると:
- Public Subnet = ホール(お客さんが見える場所)
- Private Subnet = 厨房(外から見えない場所)
- DB Subnet = 倉庫(さらに奥の場所)
5-6. igw.tf - Internet Gateway
ファイルの役割:
- Internet Gatewayを作成
- VPCに接続
コード:
# terraform/phase1-network/igw.tf
resource "aws_internet_gateway" "main" {
vpc_id = aws_vpc.main.id
tags = {
Name = "${var.project_name}-igw"
}
}
ポイント:
- vpc_id: VPCに紐付ける
- シンプルな定義: Internet Gatewayは設定項目が少ない
Internet Gatewayの役割:
- VPCとインターネットの出入口
- Public SubnetからインターネットへのアクセスはIGW経由
料理で例えると:
- Internet Gateway = レストランの正面玄関
- お客さん(インターネット)はここから出入り
5-7. outputs.tf - 他Phaseで使う値
ファイルの役割:
- Phase 2以降で使う値を出力
コード:
# terraform/phase1-network/outputs.tf
output "vpc_id" {
description = "VPC ID"
value = aws_vpc.main.id
}
output "public_subnet_ids" {
description = "Public Subnet IDs"
value = aws_subnet.public[*].id
}
output "private_subnet_ids" {
description = "Private Subnet IDs"
value = aws_subnet.private[*].id
}
output "db_subnet_ids" {
description = "DB Subnet IDs"
value = aws_subnet.db[*].id
}
output "azs" {
description = "Availability Zones"
value = var.azs
}
output "igw_id" {
description = "Internet Gateway ID"
value = aws_internet_gateway.main.id
}
ポイント:
- output: 他のPhaseから参照できるようにする
- [*]: countで作成したリソースをすべて出力
- description: 出力の説明
次のPhaseでの使い方:
# phase2-security/data.tf
data "terraform_remote_state" "network" {
backend = "local"
config = {
path = "../phase1-network/terraform.tfstate"
}
}
# VPC IDを参照
vpc_id = data.terraform_remote_state.network.outputs.vpc_id
6. 実行手順
6-1. ディレクトリ作成
# プロジェクトディレクトリ作成
mkdir -p my-ecs-project/terraform/phase1-network
cd my-ecs-project/terraform/phase1-network
6-2. ファイル作成
上記のコードを7つのファイルに保存:
- main.tf
- variables.tf
- terraform.tfvars
- vpc.tf
- subnet.tf
- igw.tf
- outputs.tf
6-3. terraform init
Terraformの初期化:
terraform init
実行結果:
Initializing the backend...
Initializing provider plugins...
- Finding hashicorp/aws versions matching "~> 5.0"...
- Installing hashicorp/aws v5.x.x...
Terraform has been successfully initialized!
やっていること:
- AWS Providerのダウンロード
- .terraformディレクトリの作成
6-4. terraform plan
実行計画の確認:
terraform plan
実行結果:
Terraform will perform the following actions:
# aws_vpc.main will be created
+ resource "aws_vpc" "main" {
+ cidr_block = "10.0.0.0/16"
+ enable_dns_hostnames = true
+ enable_dns_support = true
...
}
# aws_subnet.public[0] will be created
...
Plan: 9 to add, 0 to change, 0 to destroy.
ポイント:
- Plan: 9 to add: 9個のリソースが作成される
- エラーがないことを確認
6-5. terraform apply
実際にリソースを作成:
terraform apply
確認メッセージ:
Do you want to perform these actions?
Terraform will perform the actions described above.
Only 'yes' will be accepted to approve.
Enter a value: yes 👈 yesと入力
実行結果:
aws_vpc.main: Creating...
aws_vpc.main: Creation complete after 3s [id=vpc-xxxx]
aws_internet_gateway.main: Creating...
aws_subnet.public[0]: Creating...
aws_subnet.public[1]: Creating...
aws_subnet.private[0]: Creating...
aws_subnet.private[1]: Creating...
aws_subnet.db[0]: Creating...
aws_subnet.db[1]: Creating...
...
Apply complete! Resources: 9 added, 0 changed, 0 destroyed.
Outputs:
vpc_id = "vpc-0123456789abcdef0"
public_subnet_ids = [
"subnet-0a1b2c3d4e5f6g7h8",
"subnet-1a2b3c4d5e6f7g8h9",
]
...
所要時間: 約1-2分
7. 動作確認
7-1. AWSコンソールで確認
VPC:
- AWSコンソール → VPC → Your VPCs
- 「myproject-vpc」が作成されていることを確認
- CIDR: 10.0.0.0/16
Subnet:
- VPC → Subnets
- 6つのSubnetが作成されていることを確認
- myproject-public-subnet-1, 2
- myproject-private-subnet-1, 2
- myproject-db-subnet-1, 2
Internet Gateway:
- VPC → Internet Gateways
- 「myproject-igw」が作成されていることを確認
- State: Attached
7-2. Terraform出力で確認
terraform output
結果:
azs = [
"ap-northeast-1a",
"ap-northeast-1c",
]
db_subnet_ids = [
"subnet-xxx1",
"subnet-xxx2",
]
igw_id = "igw-xxx"
private_subnet_ids = [
"subnet-yyy1",
"subnet-yyy2",
]
public_subnet_ids = [
"subnet-zzz1",
"subnet-zzz2",
]
vpc_id = "vpc-xxx"
8. トラブルシューティング
エラー1: AWS認証情報が設定されていない
エラーメッセージ:
Error: No valid credential sources found
原因:
- AWS CLIの認証情報が未設定
解決策:
aws configure
入力内容:
- AWS Access Key ID
- AWS Secret Access Key
- Default region name: ap-northeast-1
- Default output format: json
エラー2: CIDR範囲の重複
エラーメッセージ:
Error: creating EC2 Subnet: InvalidSubnet.Conflict
原因:
- Subnet CIDRが重複している
解決策:
terraform.tfvarsを確認:
# ❌ 悪い例(CIDR重複)
public_subnet_cidrs = ["10.0.1.0/24", "10.0.1.0/24"]
# ✅ 良い例(CIDR重複なし)
public_subnet_cidrs = ["10.0.1.0/24", "10.0.2.0/24"]
エラー3: Terraformのバージョンが古い
エラーメッセージ:
Error: Unsupported Terraform Core version
解決策:
# Terraformのバージョン確認
terraform --version
# v1.9.0未満の場合はアップデート
# macOS (Homebrew)
brew upgrade terraform
# Linux
wget https://releases.hashicorp.com/terraform/1.9.x/...
9. 🎯 Phase 1-1完了!
できたこと
✅ VPCの作成
✅ Subnet × 6の作成
✅ Internet Gatewayの作成
✅ Terraformの基本操作の習得
次回予告
第3回: Phase 1-2 - NAT Gateway & Route Table
次回は、Phase 1の後半として以下を作成します:
- NAT Gateway × 2(Private SubnetからのInternet接続)
- Route Table × 2(通信経路の設定)
- Elastic IP × 2(NAT Gateway用の固定IP)
Phase 1-2が完了すると、ネットワーク基盤が完成します!
10. 参考リンク
(続く)