Variable, Output, Locals và Kiểm Tra Giá Trị Sớm
Cho tới giờ mọi giá trị trong cấu hình đều cứng: region gõ thẳng, tên bucket gắn chết tiền tố. Cách đó không tái dùng được — muốn dựng cùng hạ tầng cho dev và prod thì phải chép cả file rồi sửa tay, sai sót khó tránh. Part III làm cấu hình linh hoạt. Bài này là ba mảnh nền: variable đưa giá trị vào, output lấy kết quả ra, locals đặt tên cho biểu thức. Phần cuối bài là cách bắt giá trị sai trước khi nó kịp tạo ra hạ tầng hỏng.
Mục tiêu
Tham số hóa được một cấu hình bằng variable/output/locals, và dựng hàng rào kiểm tra bằng validation, precondition, postcondition để lỗi lộ ra ở bước plan thay vì giữa lúc apply.
variable: đầu vào của cấu hình
Một variable khai báo một giá trị truyền từ ngoài vào:
variable "environment" {
type = string
description = "Môi trường: dev | staging | prod"
default = "dev"
}
type ràng buộc kiểu (string, number, bool, hoặc các kiểu gộp như list(string), map(...), object({...})). description để người khác hiểu biến dùng làm gì. default cho giá trị mặc định — không có default thì biến thành bắt buộc, ai chạy phải cung cấp.
Có nhiều cách truyền giá trị, theo thứ tự ưu tiên tăng dần: file terraform.tfvars (tự nạp), biến môi trường TF_VAR_environment=staging, và cờ dòng lệnh -var environment=staging (cao nhất). Trong thực tế, mỗi môi trường một file .tfvars riêng là cách gọn để cùng một code chạy cho nhiều môi trường (Part V).
output: đầu ra của cấu hình
output công bố một giá trị sau khi apply — để bạn xem, hoặc để cấu hình khác đọc lại (qua remote state, bài 16):
output "bucket_name" {
value = aws_s3_bucket.app.id
}
Output nhận description, và sensitive = true để che giá trị (bài 8). Ngoài việc in ra cuối apply, output là giao diện công khai của một module — Part IV sẽ thấy module trả kết quả cho bên ngoài qua chính output.
locals: đặt tên cho biểu thức
locals gán tên cho một biểu thức để dùng lại nhiều nơi mà không lặp:
locals {
name_prefix = "${var.project}-${var.environment}"
is_production = var.environment == "prod"
common_tags = {
Project = var.project
Environment = var.environment
ManagedBy = "terraform"
}
}
Tham chiếu bằng local.name_prefix. Khác biệt với variable: variable nhận giá trị từ ngoài, local tính giá trị từ bên trong cấu hình (thường từ các variable khác). Dùng local khi một giá trị dẫn xuất lặp lại ở nhiều chỗ (như bộ tag chung), hoặc khi một biểu thức phức tạp cần một cái tên có nghĩa.
Tài liệu cũng cảnh báo đừng lạm dụng: local "có thể khiến cấu hình khó đọc hơn vì che mất nơi giá trị thực sự đến từ đâu". Khi đọc local.x bạn phải nhảy đi tìm định nghĩa. Dùng có chừng mực — chỉ khi lợi ích (tránh lặp, đặt tên rõ) thực sự bù lại.
Ráp ba thứ lại, một bucket tham số hóa trông như sau:
resource "aws_s3_bucket" "app" {
bucket_prefix = "${local.name_prefix}-"
force_destroy = var.force_destroy
tags = local.common_tags
}
Cùng file này, đổi -var environment=staging là ra một bucket khác cho môi trường khác, không sửa một dòng code nào.
validation: chặn input sai ngay ở plan
Một biến string chấp nhận mọi chuỗi, kể cả "pruduction" gõ sai hay "PROD" sai hoa thường. Nếu không kiểm, giá trị sai sẽ đi sâu vào cấu hình rồi gây lỗi khó hiểu ở chỗ khác. Block validation (có từ Terraform 0.13) kiểm ngay khi nạp biến:
variable "environment" {
type = string
default = "dev"
validation {
condition = contains(["dev", "staging", "prod"], var.environment)
error_message = "environment phải là một trong: dev, staging, prod."
}
}
Truyền một giá trị ngoài danh sách:
$ terraform plan -var environment=production
Error: Invalid value for variable
on main.tf line 26:
26: variable "environment" {
environment phải là một trong: dev, staging, prod.
This was checked by the validation rule at main.tf:32,3-13.
Terraform dừng ngay ở bước plan, in đúng thông báo bạn viết, chưa gọi một API nào. error_message rõ ràng là điểm tạo nên khác biệt: người dùng biết ngay sai ở đâu và sửa thế nào, thay vì đoán mò từ một lỗi AWS mơ hồ ở tận đâu.
precondition và postcondition: kiểm giả định quanh resource
Validation kiểm giá trị một biến. Nhưng nhiều giả định nằm ở mức quan hệ giữa các thứ — "ở prod thì không được bật force_destroy", "AMI phải đúng kiến trúc x86_64". Đó là việc của precondition và postcondition (có từ Terraform 1.2), đặt trong block lifecycle của resource:
resource "aws_s3_bucket" "app" {
bucket_prefix = "${local.name_prefix}-"
force_destroy = var.force_destroy
lifecycle {
precondition {
condition = !local.is_production || !var.force_destroy
error_message = "Ở prod không được bật force_destroy trên bucket."
}
}
}
Điều kiện đọc là "hoặc không phải prod, hoặc không bật force_destroy" — nghĩa là cấm đúng tổ hợp prod-cộng-force_destroy. Thử với prod:
$ terraform plan -var environment=prod
Error: Resource precondition failed
on main.tf line 64, in resource "aws_s3_bucket" "app":
64: condition = !local.is_production || !var.force_destroy
Ở prod không được bật force_destroy trên bucket.
Khác biệt timing giữa hai loại: precondition chạy sau khi lập plan nhưng trước khi tạo resource, kiểm các giả định đầu vào (vì thế nó không tham chiếu được self — resource chưa tồn tại). postcondition chạy sau khi apply hoặc đọc data source, kiểm rằng kết quả đúng như mong đợi (và dùng được self để soi thuộc tính resource vừa tạo). Một postcondition điển hình: sau khi tạo instance, đảm bảo nó đúng nằm trong VPC mong muốn.
biến nạp vào plan xong apply apply xong
─────────── ───────── ───── ──────────
validation ──► precondition ──► [tạo/sửa resource] ──► postcondition
(giá trị biến) (giả định đầu vào) (kết quả đúng?)
check (cảnh báo, không chặn)
check: kiểm ngoài vòng đời, chỉ cảnh báo
Còn một loại nữa, check block (Terraform 1.5), kiểm "hạ tầng ngoài vòng đời resource thông thường" và chỉ cảnh báo chứ không chặn apply. Nó hợp để giám sát những điều kiện không nên làm hỏng deploy nhưng vẫn muốn biết (ví dụ endpoint trả về 200). Ta để dành phần này cho bài 17, khi nói về các tính năng lifecycle nâng cao.
🧹 Dọn dẹp
$ terraform destroy -auto-approve
Destroy complete! Resources: 1 destroyed.
Tổng kết
Variable đưa giá trị từ ngoài vào (type/description/default, truyền qua tfvars/TF_VAR_/-var), output công bố kết quả, locals đặt tên cho biểu thức dẫn xuất nhưng đừng lạm dụng. Ba lớp kiểm tra bắt lỗi mỗi lớp một thời điểm: validation chặn giá trị biến sai khi nạp, precondition kiểm giả định đầu vào trước khi tạo resource, postcondition kiểm kết quả sau khi tạo, và check cảnh báo ngoài vòng đời. Điểm chung: lỗi lộ ra ở plan với thông báo bạn tự viết, thay vì nổ giữa apply bằng một lỗi AWS khó đọc.
Bài tới khai thác sức mạnh thật sự của HCL: data source để đọc thông tin có sẵn trên AWS (AMI mới nhất, AZ khả dụng), các hàm và biểu thức for, và dynamic block để sinh cấu hình lặp lại mà không chép tay.