Giao diện
Thực hành: Module Patterns
🎯 Mục tiêu
🎯 Sau bài thực hành này, bạn sẽ:
- Thiết kế Terraform module với interface rõ ràng (inputs/outputs)
- Áp dụng composition pattern để kết hợp nhiều modules
- Hiểu versioning và cách publish module cho team sử dụng
Mô tả bài tập
Team bạn có 5 project đều cần tạo VPC + Subnets + Security Groups. Thay vì copy-paste, bạn sẽ tạo reusable modules để DRY codebase và chuẩn hóa infrastructure patterns.
Yêu cầu
Bài 1: Tạo VPC Module
Tạo module structure:
modules/
└── vpc/
├── main.tf
├── variables.tf
├── outputs.tf
└── README.mdmodules/vpc/variables.tf — Input interface:
hcl
variable "name" {
description = "VPC name prefix"
type = string
validation {
condition = length(var.name) > 0 && length(var.name) <= 32
error_message = "Name must be between 1 and 32 characters."
}
}
variable "cidr_block" {
description = "VPC CIDR block"
type = string
default = "10.0.0.0/16"
# TODO: Thêm validation cho CIDR format
}
variable "public_subnet_cidrs" {
description = "List of public subnet CIDR blocks"
type = list(string)
default = ["10.0.1.0/24", "10.0.2.0/24"]
}
variable "availability_zones" {
description = "List of AZs"
type = list(string)
}
variable "tags" {
description = "Additional tags"
type = map(string)
default = {}
}modules/vpc/main.tf — Resources:
hcl
# TODO: Tạo VPC, Subnets (dùng count hoặc for_each), IGW, Route Table
# Yêu cầu:
# - Tạo N public subnets dựa trên var.public_subnet_cidrs
# - Mỗi subnet ở AZ khác nhau
# - Tất cả resources được tag đúng conventionmodules/vpc/outputs.tf — Output interface:
hcl
# TODO: Export vpc_id, subnet_ids, cidr_block
# Output này sẽ được modules khác sử dụngBài 2: Module Composition
Sử dụng VPC module trong project:
hcl
# environments/dev/main.tf
module "vpc" {
source = "../../modules/vpc"
name = "dev-platform"
cidr_block = "10.0.0.0/16"
public_subnet_cidrs = ["10.0.1.0/24", "10.0.2.0/24"]
availability_zones = ["ap-southeast-1a", "ap-southeast-1b"]
tags = {
Environment = "dev"
Team = "platform"
}
}
# TODO: Tạo thêm module "security_group" sử dụng output từ module "vpc"
# Security Group cần vpc_id từ module.vpc.vpc_idCâu hỏi thiết kế:
- Module nên trả về security group ID hay toàn bộ security group object?
- Nếu 2 environments (dev, prod) dùng cùng module nhưng CIDR khác nhau, cần thay đổi gì?
Bài 3: Module Versioning
Cấu hình module source với version pinning:
hcl
# Từ Git repository
module "vpc" {
source = "git::https://github.com/my-org/terraform-modules.git//vpc?ref=v1.2.0"
# TODO: Thêm các variables
}
# Từ Terraform Registry
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "~> 5.0"
# TODO: Thêm các variables
}Bài tập: Giải thích sự khác biệt giữa:
version = "5.0.0"(exact)version = "~> 5.0"(pessimistic)version = ">= 5.0, < 6.0"(range)
Gợi ý
💡 Xem gợi ý
- Bài 1: Dùng
for_eachthay vìcountcho subnets — dễ quản lý khi thêm/xóa subnet - Bài 1: CIDR validation:
can(cidrhost(var.cidr_block, 0))kiểm tra format hợp lệ - Bài 2: Module composition = output của module A là input của module B. Tạo dependency chain rõ ràng
- Bài 3:
~> 5.0cho phép 5.0.x đến 5.x.y nhưng KHÔNG cho 6.0. An toàn cho minor/patch updates - Best practice: Module nên có README.md mô tả inputs, outputs, examples
Lời giải
✅ Xem lời giải
Bài 1: VPC Module hoàn chỉnh
modules/vpc/main.tf:
hcl
resource "aws_vpc" "this" {
cidr_block = var.cidr_block
enable_dns_hostnames = true
enable_dns_support = true
tags = merge(var.tags, {
Name = "${var.name}-vpc"
})
}
resource "aws_internet_gateway" "this" {
vpc_id = aws_vpc.this.id
tags = merge(var.tags, {
Name = "${var.name}-igw"
})
}
resource "aws_subnet" "public" {
for_each = { for idx, cidr in var.public_subnet_cidrs :
"public-${idx}" => {
cidr = cidr
az = var.availability_zones[idx % length(var.availability_zones)]
}
}
vpc_id = aws_vpc.this.id
cidr_block = each.value.cidr
availability_zone = each.value.az
map_public_ip_on_launch = true
tags = merge(var.tags, {
Name = "${var.name}-${each.key}"
})
}
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 = merge(var.tags, {
Name = "${var.name}-public-rt"
})
}
resource "aws_route_table_association" "public" {
for_each = aws_subnet.public
subnet_id = each.value.id
route_table_id = aws_route_table.public.id
}modules/vpc/variables.tf (CIDR validation):
hcl
variable "cidr_block" {
description = "VPC CIDR block"
type = string
default = "10.0.0.0/16"
validation {
condition = can(cidrhost(var.cidr_block, 0))
error_message = "Must be a valid CIDR block."
}
}modules/vpc/outputs.tf:
hcl
output "vpc_id" {
description = "The VPC ID"
value = aws_vpc.this.id
}
output "public_subnet_ids" {
description = "List of public subnet IDs"
value = [for s in aws_subnet.public : s.id]
}
output "cidr_block" {
description = "The VPC CIDR block"
value = aws_vpc.this.cidr_block
}Bài 3: Version constraints
"5.0.0"— Exact: chỉ dùng đúng 5.0.0"~> 5.0"— Pessimistic: >= 5.0, < 6.0 (cho phép 5.1, 5.2...)">= 5.0, < 6.0"— Range: tương tự ~> 5.0 nhưng explicit