Khi viết Terraform, bạn sẽ gặp tình huống cần xử lý chuỗi, lọc danh sách, hoặc tính toán giá trị động thay vì hardcode. Đó là lúc các hàm built-in của Terraform phát huy tác dụng. Bài này mình tổng hợp các hàm hay dùng nhất trong thực tế – có ví dụ cụ thể để bạn áp dụng ngay được.
Xem thêm:
07-Terraform Data Types – Các kiểu dữ liệu trong Terraform
10-Terraform Locals – Khai báo biến cục bộ
14-Terraform Workspace – Quản lý nhiều môi trường từ một codebase
1. Terraform Functions hoạt động như thế nào?
Terraform có một tập hàm built-in sẵn, bạn gọi trực tiếp trong expressions mà không cần import hay cài thêm gì. Cú pháp cơ bản:
function_name(argument1, argument2, ...)
Bạn có thể test nhanh bất kỳ hàm nào bằng lệnh:
terraform console
Sau đó gõ thẳng biểu thức vào:
> upper("ttnguyen")
"TTNGUYEN"
> length(["a", "b", "c"])
3
Rất tiện để kiểm tra kết quả trước khi đưa vào code thật.
2. Nhóm 1: String Functions
format – Ghép chuỗi có định dạng
Tương tự printf trong C hay sprintf trong các ngôn ngữ khác.
locals {
bucket_name = format("%s-%s-%s", var.project, var.environment, var.region)
# Kết quả: "ttnguyen-production-ap-southeast-1"
}
Dùng khi cần ghép nhiều phần với separator cố định, rõ hơn template string trong một số trường hợp.
formatlist – Áp dụng format cho cả list
locals {
hostnames = formatlist("server-%s.ttnguyen.net", ["01", "02", "03"])
# Kết quả: ["server-01.ttnguyen.net", "server-02.ttnguyen.net", "server-03.ttnguyen.net"]
}
join và split
locals {
tags_string = join(",", ["web", "production", "ap-southeast-1"])
# Kết quả: "web,production,ap-southeast-1"
parts = split(".", "ttnguyen.net")
# Kết quả: ["ttnguyen", "net"]
}
replace – Thay thế chuỗi
locals {
clean_name = replace(var.app_name, "_", "-")
# "my_app" → "my-app"
}
AWS S3 không cho dùng underscore trong tên bucket – replace giải quyết nhanh gọn.
trimspace, upper, lower
locals {
env = lower(trimspace(var.environment))
# " Production " → "production"
}
Dùng khi bạn không kiểm soát được input từ user hoặc CI/CD system.
substr
locals {
short_id = substr(var.commit_hash, 0, 8)
# "a1b2c3d4e5f6..." → "a1b2c3d4"
}
3. Nhóm 2: Collection Functions
length – Đếm phần tử
locals {
instance_count = length(var.availability_zones)
}
merge – Gộp nhiều map
Đây là hàm mình dùng rất nhiều khi làm việc với tags trên ttnguyen.net:
locals {
common_tags = {
Project = var.project
ManagedBy = "terraform"
}
resource_tags = merge(local.common_tags, {
Component = "database"
Tier = "backend"
})
}
Nếu có key trùng, map sau sẽ ghi đè map trước.
concat – Gộp nhiều list
locals {
all_cidrs = concat(var.public_cidrs, var.private_cidrs)
}
flatten – Làm phẳng list lồng nhau
locals {
nested = [["a", "b"], ["c", "d"]]
flat = flatten(local.nested)
# Kết quả: ["a", "b", "c", "d"]
}
Hay gặp khi dùng for expressions trả về list of lists.
distinct – Loại bỏ phần tử trùng
locals {
unique_zones = distinct(["ap-southeast-1a", "ap-southeast-1b", "ap-southeast-1a"])
# Kết quả: ["ap-southeast-1a", "ap-southeast-1b"]
}
toset, tolist, tomap – Chuyển đổi kiểu
locals {
zones_set = toset(var.availability_zones)
}
resource "aws_subnet" "main" {
for_each = local.zones_set
availability_zone = each.value
# ...
}
for_each yêu cầu set hoặc map, không nhận list trực tiếp – đây là lý do hay phải dùng toset.
lookup – Lấy giá trị từ map với default
locals {
instance_types = {
dev = "t3.micro"
staging = "t3.medium"
production = "t3.large"
}
instance_type = lookup(local.instance_types, var.environment, "t3.micro")
}
Argument thứ ba là giá trị mặc định nếu key không tồn tại. Dùng thay vì để Terraform báo lỗi khi key sai.
element – Lấy phần tử theo index (có vòng)
locals {
# Phân bổ 5 instance vào 3 AZ theo vòng
az_for_instance = [for i in range(5) : element(var.azs, i)]
# Kết quả: [az[0], az[1], az[2], az[0], az[1]]
}
4. Nhóm 3: Numeric Functions
max, min
locals {
replica_count = max(var.desired_replicas, 2) # Tối thiểu 2 replicas
disk_size = min(var.requested_disk, 1000) # Tối đa 1000 GB
}
ceil, floor
locals {
# Tính số AZ cần dùng, làm tròn lên
az_needed = ceil(var.instance_count / 2)
}
abs
locals {
diff = abs(var.target - var.current)
}
5. Nhóm 4: Filesystem & Encoding Functions
file – Đọc nội dung file
resource "aws_iam_policy" "custom" {
name = "ttnguyen-custom-policy"
policy = file("${path.module}/policies/custom.json")
}
filebase64 – Đọc file và encode sang base64
resource "aws_lambda_function" "handler" {
filename = "lambda.zip"
source_code_hash = filebase64sha256("lambda.zip")
# ...
}
jsonencode và jsondecode
locals {
policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect = "Allow"
Action = ["s3:GetObject"]
Resource = "*"
}]
})
}
Dùng jsonencode thay vì viết JSON string thủ công – tránh lỗi cú pháp và dễ đọc hơn nhiều.
locals {
config = jsondecode(file("config.json"))
bucket_name = local.config.storage.bucket
}
base64encode / base64decode
locals {
encoded_script = base64encode(file("userdata.sh"))
}
resource "aws_instance" "web" {
user_data = local.encoded_script
# ...
}
6. Nhóm 5: IP Network Functions
Hay dùng khi chia subnet động thay vì hardcode CIDR:
cidrsubnet – Tính CIDR con
locals {
vpc_cidr = "10.0.0.0/16"
public_subnets = [
cidrsubnet(local.vpc_cidr, 8, 0), # 10.0.0.0/24
cidrsubnet(local.vpc_cidr, 8, 1), # 10.0.1.0/24
cidrsubnet(local.vpc_cidr, 8, 2), # 10.0.2.0/24
]
private_subnets = [
cidrsubnet(local.vpc_cidr, 8, 10), # 10.0.10.0/24
cidrsubnet(local.vpc_cidr, 8, 11), # 10.0.11.0/24
cidrsubnet(local.vpc_cidr, 8, 12), # 10.0.12.0/24
]
}
Argument: cidrsubnet(prefix, newbits, netnum). Không cần tính tay nữa.
cidrhost – Lấy địa chỉ IP cụ thể trong subnet
locals {
gateway_ip = cidrhost("10.0.1.0/24", 1) # "10.0.1.1"
dns_ip = cidrhost("10.0.1.0/24", 2) # "10.0.1.2"
}
7. Nhóm 6: Type Conversion & Logical Functions
coalesce – Trả về giá trị không null/empty đầu tiên
locals {
instance_name = coalesce(var.custom_name, var.default_name, "ttnguyen-instance")
}
Giá trị nào không phải null và không phải chuỗi rỗng thì dùng cái đó.
can – Kiểm tra expression có hợp lệ không
locals {
is_valid_json = can(jsondecode(var.config_string))
}
try – Thử expression, fallback nếu lỗi
locals {
port = try(tonumber(var.port_string), 8080)
# Nếu convert thất bại thì dùng 8080
}
8. Kết hợp nhiều hàm trong thực tế
Trong thực tế, bạn sẽ hay kết hợp các hàm lại. Ví dụ tạo tên resource chuẩn hóa:
locals {
# Chuẩn hóa tên: lowercase, bỏ khoảng trắng, thay _ thành -
normalized_name = replace(lower(trimspace(var.app_name)), "_", "-")
# Tên đầy đủ với prefix + truncate để không quá 63 ký tự (giới hạn DNS)
full_name = substr(
format("%s-%s-%s", local.normalized_name, var.environment, var.region),
0,
63
)
}
Hay khi tạo subnet map cho for_each:
locals {
subnet_config = {
for idx, az in var.availability_zones :
az => {
cidr = cidrsubnet(var.vpc_cidr, 8, idx)
az = az
}
}
}
resource "aws_subnet" "main" {
for_each = local.subnet_config
availability_zone = each.value.az
cidr_block = each.value.cidr
# ...
}
Tóm tắt
| Nhóm | Hàm hay dùng |
|---|---|
| String | format, join, split, replace, lower, trimspace, substr |
| Collection | merge, concat, flatten, distinct, toset, lookup, element |
| Numeric | max, min, ceil, floor |
| Encoding | jsonencode, jsondecode, file, base64encode |
| Network | cidrsubnet, cidrhost |
| Logic | coalesce, try, can |
Bạn không cần nhớ hết tất cả. Cứ dùng terraform console để test nhanh khi cần, tra Terraform docs khi gặp use case mới.
Bài viết thuộc series HashiCorp Certified: Terraform Associate trên ttnguyen.net. Cảm ơn bạn đã đọc