Tại sao Terraform dừng quá trình Plan của bạn
Terraform hoạt động dựa trên một nguyên tắc nghiêm ngặt: nó phải dự đoán chính xác địa chỉ của mọi tài nguyên trước khi tác động đến hạ tầng của bạn. Nếu địa chỉ tài nguyên là aws_instance.web["web-01"], thì key "web-01" phải được xác định rõ ràng trong giai đoạn plan. Terraform không thể đợi đến giai đoạn apply mới quyết định tên của một tài nguyên là gì.
Lỗi này thường xảy ra khi bạn sử dụng một giá trị động—chẳng hạn như ID của AWS Instance được tạo tự động hoặc mật khẩu nhạy cảm—làm key trong vòng lặp for_each. Vì các giá trị này không tồn tại cho đến khi provider thực sự tạo tài nguyên, Terraform sẽ rơi vào một điểm bế tắc về logic. Nó dừng quá trình này để ngăn chặn file state của bạn trở nên không nhất quán.
Kịch bản: Các bản ghi DNS động
Giả sử bạn đang triển khai một cụm gồm ba instance EC2. Bạn muốn tự động tạo một bản ghi DNS Route53 cho mỗi instance bằng cách sử dụng ID của instance đó như một phần của tên miền. Bạn có thể thử đoạn code sau:
resource "aws_instance" "server" {
count = 3
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t3.micro"
}
resource "aws_route53_record" "dns" {
# Lệnh này sẽ THẤT BẠI vì các ID chỉ được tạo trong quá trình apply
for_each = { for s in aws_instance.server : s.id => s.private_ip }
zone_id = "Z0123456789ABCDEF"
name = "${each.key}.example.com"
type = "A"
ttl = 300
records = [each.value]
}
AWS chỉ cấp một ID (ví dụ: i-04f123456789ab) sau khi instance đã chạy. Vì Terraform không thể biết các ID này trong bước terraform plan, nó không thể xác định các key cho bản ghi DNS của bạn. Điều này dẫn đến lỗi Invalid for_each argument.
Cách khắc phục tạm thời: Sử dụng cờ Target
Bạn cần sửa lỗi ngay lập tức? Bạn có thể buộc Terraform tạo các tài nguyên phụ thuộc trước bằng cách sử dụng cờ -target:
terraform apply -target=aws_instance.server
Lệnh này tạo các instance và lưu ID của chúng vào file state của bạn. Lệnh terraform apply tiếp theo sau đó sẽ hoạt động vì các ID hiện đã là các giá trị "đã biết" (known). Hãy hạn chế sử dụng cách này. Đây chỉ là một giải pháp tình thế thủ công và thường làm hỏng các quy trình CI/CD tự động.
Cách khắc phục triệt để 1: Sử dụng Key tĩnh
Giải pháp đáng tin cậy nhất là tách biệt các key tài nguyên khỏi các thuộc tính đám mây động. Thay vì sử dụng ID được tạo tự động làm key, hãy sử dụng một chuỗi duy nhất mà bạn tự định nghĩa trong cấu hình.
Bằng cách sử dụng một map hoặc một danh sách các chuỗi, các key sẽ có sẵn ngay lập tức trong giai đoạn plan:
variable "node_names" {
type = list(string)
default = ["api-prod-01", "api-prod-02", "api-prod-03"]
}
resource "aws_instance" "server" {
for_each = toset(var.node_names)
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t3.micro"
tags = { Name = each.key }
}
resource "aws_route53_record" "dns" {
# KHẮC PHỤC: each.key giờ đây là một chuỗi tĩnh từ var.node_names
for_each = aws_instance.server
zone_id = "Z0123456789ABCDEF"
name = "${each.key}.example.com"
type = "A"
ttl = 300
records = [each.value.private_ip]
}
Giờ đây Terraform biết rằng nó cần tạo aws_route53_record.dns["api-prod-01"] ngay cả trước khi nó kết nối với AWS. Giá trị private_ip có thể tiếp tục ở trạng thái "chỉ biết sau khi apply" vì chỉ có các key là bắt buộc phải tĩnh.
Cách khắc phục triệt để 2: Xử lý các giá trị nhạy cảm
Đôi khi các key về mặt kỹ thuật là đã biết, nhưng Terraform ẩn chúng đi vì chúng được đánh dấu là sensitive. Terraform từ chối sử dụng các giá trị nhạy cảm làm key tài nguyên vì các key này xuất hiện dưới dạng văn bản thuần túy trong terminal và file state của bạn.
Nếu bạn chắc chắn rằng giá trị đó an toàn để hiển thị trong địa chỉ tài nguyên, hãy bao bọc nó trong hàm nonsensitive():
resource "vault_generic_secret" "example" {
# Sử dụng nonsensitive để cho phép các key này được dùng trong địa chỉ tài nguyên
for_each = { for k, v in var.secret_map : nonsensitive(k) => v }
path = "secret/${each.key}"
data_json = jsonencode(each.value)
}
Cách khắc phục triệt để 3: Tận dụng Data Source
Các Data source thường có thể lấp đầy khoảng trống nếu bạn cần tham chiếu đến hạ tầng hiện có. Khác với các tài nguyên được quản lý, các data source thường có thể được giải quyết trong giai đoạn "refresh" của quá trình plan nếu các đối số của chúng là tĩnh.
data "aws_subnets" "public" {
filter {
name = "tag:Network"
values = ["public"]
}
}
resource "aws_instance" "nodes" {
# Cách này hoạt động vì các subnet ID được lấy trong quá trình plan refresh
for_each = toset(data.aws_subnets.public.ids)
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t3.micro"
}
Xác minh kết quả
Chạy terraform plan để kiểm tra công việc của bạn. Một bản sửa lỗi thành công sẽ hiển thị danh sách rõ ràng các hành động đối với tài nguyên. Ví dụ, bạn sẽ thấy các địa chỉ cụ thể như aws_route53_record.dns["api-prod-01"] thay vì thông báo lỗi. Nếu lỗi vẫn còn, hãy kiểm tra kỹ xem bạn có đang vô tình sử dụng id, arn, hoặc primary_access_key làm key cho map hay không.

