← Tổng quan series

Xây VPC AWS bằng Terraform module

Từ VPC viết tay đến module có input/output, hiểu subnet public/private, NAT Gateway và cách tái sử dụng cấu hình mạng.

VPC gồm những mảnh nào?

Một mạng ứng dụng tối thiểu thường có VPC, subnet public để đặt load balancer, subnet private cho application/database, Internet Gateway cho public route và NAT Gateway để private subnet đi ra ngoài khi cần. NAT Gateway tính phí theo giờ và dữ liệu, nên đừng tạo trong account lab rồi quên xóa.

Thay vì copy một khối dài cho dev/staging/prod, ta đóng gói thành module.

Cấu trúc module

modules/vpc/
├── main.tf
├── variables.tf
└── outputs.tf

modules/vpc/variables.tf:

variable "name" { type = string }
variable "cidr" { type = string }
variable "azs"  { type = list(string) }

Root module gọi module:

module "network" {
  source = "./modules/vpc"

  name = "demo"
  cidr = "10.30.0.0/16"
  azs  = ["ap-southeast-1a", "ap-southeast-1b"]
}

Trong module, dùng for_each để tạo subnet theo AZ thay vì copy-paste:

resource "aws_vpc" "this" {
  cidr_block           = var.cidr
  enable_dns_hostnames = true
  tags = { Name = "${var.name}-vpc" }
}

resource "aws_subnet" "private" {
  for_each = toset(var.azs)

  vpc_id            = aws_vpc.this.id
  availability_zone = each.value
  cidr_block        = cidrsubnet(var.cidr, 8, index(var.azs, each.value) + 10)
  tags              = { Name = "${var.name}-private-${each.value}" }
}

Module nên trả output rõ ràng, không bắt root module “đi xuyên” vào resource nội bộ:

output "vpc_id" {
  value = aws_vpc.this.id
}

output "private_subnet_ids" {
  value = [for subnet in aws_subnet.private : subnet.id]
}

Module local hay Registry?

Module local phù hợp khi team đang thiết kế convention riêng. Module trên Terraform Registry giúp đi nhanh nhưng phải đọc version, input, output và changelog; đừng copy module vào production chỉ vì README có một đoạn code đẹp.

module "vpc" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "~> 6.0"
  # inputs...
}

Bài tập

Thêm biến enable_nat_gateway, output route table và viết một test plan cho hai AZ. Kiểm tra rằng resource ứng dụng chỉ nhận subnet ID từ output, không phụ thuộc vào tên resource nội bộ.

Các quyết định mạng cần ghi lại

Module VPC không nên chỉ trả “một đống subnet ID”. Hãy ghi rõ:

  • subnet nào dành cho load balancer, app và database;
  • route nào được phép ra Internet;
  • NAT Gateway đặt một hay mỗi AZ;
  • security group nào là ingress từ ALB, security group nào là egress tới database;
  • CIDR có chừa khoảng trống cho mở rộng và kết nối on-premise không.

Một thiết kế rẻ cho dev có thể dùng một NAT Gateway; production cần cân nhắc HA theo AZ và chi phí dữ liệu. Đây là trade-off phải xuất hiện trong README/module documentation, không nên ẩn trong một biến boolean khó hiểu.

Bài này là bản chuyển thể thực hành từ AWS VPC with a Terraform Module.