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
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
sourcecủa module, bạn phải chạyterraform initlạ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
versioncụ 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