Skip to content

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.md

modules/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 convention

modules/vpc/outputs.tf — Output interface:

hcl
# TODO: Export vpc_id, subnet_ids, cidr_block
# Output này sẽ được modules khác sử dụng

Bà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_id

Câ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_each thay vì count cho 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.0 cho 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

Liên kết liên quan