VPC + 퍼블릭 서브넷(IGW) + (선택) NAT + 프라이빗 서브넷까지 한 번에.
aws configure (또는 AWS_PROFILE, AWS_REGION)versions.tf)terraform {
required_version = "~> 1.9"
required_providers {
aws = { source = "hashicorp/aws", version = "~> 5.0" }
}
}
provider "aws" { region = var.region }
➡️Terraform의 버전 제약에서 ~>는 “pessimistic(호환 범위)” 연산자
~> 1.9 ⇒ 1.9.0 이상, 2.0.0 미만
~> 1.9.0 ⇒ 1.9.0 이상, 1.10.0 미만(= 1.9 패치만 허용)
variables.tf)variable "project" { type = string, default = "jj" }
variable "env" { type = string, default = "dev" }
variable "region" { type = string, default = "ap-northeast-2" } # 서울
variable "vpc_cidr" {
type = string
default = "10.0.0.0/16"
validation {
condition = can(cidrnetmask(var.vpc_cidr))
error_message = "유효한 CIDR을 입력하세요. 예: 10.0.0.0/16"
}
}
variable "az_count" {
type = number
default = 2 # 1~3 권장
validation {
condition = var.az_count >= 1 && var.az_count <= 3
error_message = "az_count는 1~3 범위여야 합니다."
}
}
# 서브넷 마스크: /16 VPC에서 /24 서브넷으로 쪼개려면 8비트
variable "public_subnet_bits" { type = number, default = 8 }
variable "private_subnet_bits" { type = number, default = 8 }
data&locals.tf)data "aws_availability_zones" "available" {
state = "available"
}
locals {
name_prefix = "${var.project}-${var.env}"
common_tags = { Project = var.project, Env = var.env }
# 사용할 AZ 목록 (앞에서부터 az_count개)
azs = slice(data.aws_availability_zones.available.names, 0, var.az_count)
# 서브넷 CIDR 자동 계산: public은 0.., private은 100..(충돌 방지)
public_subnets = {
for idx, az in local.azs :
az => cidrsubnet(var.vpc_cidr, var.public_subnet_bits, idx)
}
private_subnets = {
for idx, az in local.azs :
az => cidrsubnet(var.vpc_cidr, var.private_subnet_bits, idx + 100)
}
}
vpc_public.tf)resource "aws_vpc" "main" {
cidr_block = var.vpc_cidr
enable_dns_support = true
enable_dns_hostnames = true
tags = merge(local.common_tags, { Name = "${local.name_prefix}-vpc" })
}
resource "aws_internet_gateway" "igw" {
vpc_id = aws_vpc.main.id
tags = merge(local.common_tags, { Name = "${local.name_prefix}-igw" })
}
resource "aws_route_table" "public" {
vpc_id = aws_vpc.main.id
tags = merge(local.common_tags, { Name = "${local.name_prefix}-rt-public" })
}
resource "aws_route" "public_default" {
route_table_id = aws_route_table.public.id
destination_cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.igw.id
}
resource "aws_subnet" "public" {
for_each = local.public_subnets
vpc_id = aws_vpc.main.id
availability_zone = each.key
cidr_block = each.value
map_public_ip_on_launch = true # 퍼블릭 서브넷 필수 옵션
tags = merge(local.common_tags, {
Name = "${local.name_prefix}-pub-${replace(each.key, var.region, "")}"
})
}
resource "aws_route_table_association" "public" {
for_each = aws_subnet.public
subnet_id = each.value.id
route_table_id = aws_route_table.public.id
}
vpc_private.tf)NAT 게이트웨이는 유료입니다. 실습 후 꼭
destroy할것.
# NAT용 탄력적 IP
resource "aws_eip" "nat" {
domain = "vpc"
tags = merge(local.common_tags, { Name = "${local.name_prefix}-eip-nat" })
}
# 퍼블릭 서브넷 중 첫 번째에 NAT 게이트웨이 배치
resource "aws_nat_gateway" "nat" {
allocation_id = aws_eip.nat.id
subnet_id = values(aws_subnet.public)[0].id
tags = merge(local.common_tags, { Name = "${local.name_prefix}-nat" })
depends_on = [aws_internet_gateway.igw] # IGW 먼저
}
resource "aws_route_table" "private" {
vpc_id = aws_vpc.main.id
tags = merge(local.common_tags, { Name = "${local.name_prefix}-rt-private" })
}
resource "aws_route" "private_default" {
route_table_id = aws_route_table.private.id
destination_cidr_block = "0.0.0.0/0"
nat_gateway_id = aws_nat_gateway.nat.id
}
resource "aws_subnet" "private" {
for_each = local.private_subnets
vpc_id = aws_vpc.main.id
availability_zone = each.key
cidr_block = each.value
tags = merge(local.common_tags, {
Name = "${local.name_prefix}-pri-${replace(each.key, var.region, "")}"
})
}
resource "aws_route_table_association" "private" {
for_each = aws_subnet.private
subnet_id = each.value.id
route_table_id = aws_route_table.private.id
}
outputs.tf)output "vpc_id" { value = aws_vpc.main.id }
output "public_subnet_ids" { value = [for s in aws_subnet.public : s.id] }
output "private_subnet_ids" { value = try([for s in aws_subnet.private : s.id], []) }
output "igw_id" { value = aws_internet_gateway.igw.id }
output "natgw_id" { value = try(aws_nat_gateway.nat.id, null) }
terraform init
terraform validate
terraform plan -out=plan.bin
terraform apply plan.bin
# 출력 확인
terraform output
aws ec2 describe-vpcs --filters Name=cidr,Values=10.0.0.0/16
aws ec2 describe-route-tables --filters Name=vpc-id,Values=$(terraform output -raw vpc_id)
terraform destroy
map_public_ip_on_launch를 빼먹음 → EC2가 공인 IP 없음cidrsubnet()로 자동 계산 추천VPC → IGW → 퍼블릭 RT/서브넷 → (선택) NAT → 프라이빗 RT/서브넷 → apply → output 확인.