search

15-Terraform Modules – Tái sử dụng code hạ tầng

calendar_today Đăng ngày: 03/05/2026

Nếu bạn đã dùng Terraform một thời gian và bắt đầu thấy mình copy-paste cùng một đống .tf sang nhiều project khác nhau – đây là lúc cần học Modules. Bài này mình sẽ giải thích terraform modules là gì, cách viết, và cách dùng lại trong thực tế, bao gồm cả module từ Terraform Registry.

Xem thêm:

06-Terraform Variables – Biến, tfvars và Output

Terraform Remote Backend – Lưu State trên AWS S3

18-Terraform Cloud – Cộng tác và quản lý hạ tầng ở tầm team

1. Terraform Module là gì?

Đơn giản mà nói: module là một thư mục chứa các file .tf mà bạn có thể gọi từ nơi khác, giống như một function trong lập trình.

Mọi thư mục Terraform đều là một module. Khi bạn chạy terraform apply trong một thư mục, bạn đang dùng root module. Còn nếu bạn gọi thư mục khác từ bên trong root module đó, thư mục được gọi là child module.

Tại sao cần module?

  • Tránh lặp code khi deploy nhiều môi trường (dev, staging, prod)
  • Tổ chức hạ tầng theo từng khối chức năng (network, database, compute…)
  • Dễ bảo trì: sửa một chỗ, áp dụng cho toàn bộ nơi gọi module đó

2. Cấu trúc cơ bản của một module

Một module chuẩn thường có 3 file chính:

modules/
└── ec2-instance/
    ├── main.tf        # tài nguyên chính
    ├── variables.tf   # input variables
    └── outputs.tf     # output values

variables.tf – Đầu vào của module

variable "instance_type" {
  description = "EC2 instance type"
  type        = string
  default     = "t3.micro"
}

variable "ami_id" {
  description = "AMI ID to use"
  type        = string
}

variable "instance_name" {
  description = "Name tag for the instance"
  type        = string
}

main.tf – Tài nguyên bên trong module

resource "aws_instance" "this" {
  ami           = var.ami_id
  instance_type = var.instance_type

  tags = {
    Name = var.instance_name
  }
}

outputs.tf – Trả về thông tin ra ngoài

output "instance_id" {
  description = "ID of the EC2 instance"
  value       = aws_instance.this.id
}

output "public_ip" {
  description = "Public IP of the instance"
  value       = aws_instance.this.public_ip
}

3. Gọi module từ root module

Giả sử cấu trúc project của bạn như sau:

project/
├── main.tf
├── variables.tf
├── outputs.tf
└── modules/
    └── ec2-instance/
        ├── main.tf
        ├── variables.tf
        └── outputs.tf

Trong main.tf của root module, bạn gọi module bằng block module:

module "web_server" {
  source        = "./modules/ec2-instance"
  ami_id        = "ami-0c55b159cbfafe1f0"
  instance_type = "t3.small"
  instance_name = "web-server-prod"
}

module "app_server" {
  source        = "./modules/ec2-instance"
  ami_id        = "ami-0c55b159cbfafe1f0"
  instance_type = "t3.medium"
  instance_name = "app-server-prod"
}

Với cách này, mình tạo 2 EC2 instance khác nhau chỉ bằng cách thay đổi tham số, không cần viết lại resource.

Sau đó chạy:

terraform init   # cần init lại khi thêm module mới
terraform plan
terraform apply

Lưu ý: Mỗi khi thêm hoặc thay đổi source của module, bạn phải chạy terraform init lại. Nếu bỏ qua bước này, Terraform sẽ báo lỗi.

4. Dùng output của module

Bạn có thể lấy giá trị output từ một module để dùng ở nơi khác:

output "web_server_ip" {
  value = module.web_server.public_ip
}

Cú pháp: module.<tên_module>.<output_name>

5. Module từ Terraform Registry

Ngoài việc viết module từ đầu, bạn có thể dùng module có sẵn từ registry.terraform.io. Đây là nơi cộng đồng publish các module đã được kiểm thử kỹ.

Ví dụ dùng module VPC của AWS từ registry:

module "vpc" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "5.1.2"

  name = "my-vpc"
  cidr = "10.0.0.0/16"

  azs             = ["us-east-1a", "us-east-1b"]
  private_subnets = ["10.0.1.0/24", "10.0.2.0/24"]
  public_subnets  = ["10.0.101.0/24", "10.0.102.0/24"]

  enable_nat_gateway = true
}

Chỉ vài chục dòng là bạn có một VPC đầy đủ với subnet, NAT Gateway, route table. Thay vì tự viết cả trăm dòng config.

Khuyến nghị: Luôn chỉ định version cụ thể khi dùng module từ registry. Tránh để mặc định vì module có thể được cập nhật theo cách không tương thích ngược (breaking changes).

6. So sánh: Có module vs Không có module

Tiêu chí Không dùng module Dùng module
Tái sử dụng code Copy-paste thủ công Gọi lại bằng module block
Quản lý nhiều môi trường Dễ sai lệch Nhất quán
Đọc hiểu cấu trúc Khó khi file dài Rõ ràng theo từng khối
Debug Khó trace Dễ isolate từng module
Thời gian viết lần đầu Nhanh hơn Lâu hơn chút

7. Một số quy ước khi viết module

Mình hay áp dụng những cái này trong thực tế:

1. Đặt tên biến nhất quán

Dùng snake_case cho tất cả biến và output. Không mix camelCase vào.

2. Luôn có description cho variable và output

variable "instance_type" {
  description = "EC2 instance type, e.g. t3.micro, t3.small"
  type        = string
}

Người khác (hoặc mình sau 3 tháng) sẽ cảm ơn bạn.

3. Đặt default hợp lý

Những giá trị nào thường dùng thì set default, những gì bắt buộc người gọi phải truyền thì để không có default.

4. Tổ chức thư mục theo chức năng

modules/
├── networking/
├── compute/
├── database/
└── monitoring/

Thay vì đặt theo tên resource (ví dụ: aws_instance, aws_sg…), hãy đặt theo mục đích sử dụng.

Tóm lại

Trong bài này mình đã đi qua:

  • Module là gì và tại sao cần dùng
  • Cách tổ chức file bên trong một module
  • Cách gọi module từ root và truyền tham số
  • Lấy output từ module
  • Dùng module có sẵn từ Terraform Registry
  • Một số best practices thực tế

Bài viết thuộc series HashiCorp Certified: Terraform Associate trên ttnguyen.net. Cảm ơn bạn đã đọc