Lỗi Gặp Phải
Error: Reference to undeclared resource. A dynamic block's "content" must refer to the block label or the "iterator" argument.
Lỗi này xuất hiện khi bạn chạy terraform plan hoặc terraform apply. Terraform chỉ ra một dòng cụ thể bên trong phần content của khối dynamic và từ chối phân giải một tên — dù tên đó trông hoàn toàn hợp lệ với bạn.
Nguyên Nhân
Mỗi khối dynamic tự động tạo ra một đối tượng iterator có tên theo nhãn của khối. Nếu bạn tham chiếu bất kỳ tên nào khác bên trong content, Terraform sẽ coi đó là một resource chưa được khai báo — nó sẽ không tìm kiếm trong locals, variables hay bất kỳ nơi nào khác.
Đây là tình huống thường gặp nhất:
dynamic "ingress" {
for_each = var.ingress_rules
content {
from_port = rule.value.from_port # ERROR: "rule" is not defined
to_port = rule.value.to_port
protocol = rule.value.protocol
cidr_blocks = rule.value.cidr_blocks
}
}
Nhãn của khối là ingress, vì vậy iterator cũng phải là ingress, không phải rule. Việc dùng rule.value.* gây ra lỗi vì Terraform tìm kiếm một resource tên rule nhưng không tìm thấy gì.
Hai nguyên nhân phổ biến khác: sao chép một khối dynamic giữa các resource mà không cập nhật lại tham chiếu iterator, hoặc đổi tên nhãn khối trong khi vẫn giữ nguyên tên iterator cũ rải rác trong content.
Cách Sửa 1 — Dùng Đúng Tên Iterator Được Tạo Tự Động
Cách đơn giản nhất: đổi tên các tham chiếu iterator bên trong content để khớp với nhãn của khối.
# Nhãn khối là "ingress" → iterator tự động là "ingress"
dynamic "ingress" {
for_each = var.ingress_rules
content {
from_port = ingress.value.from_port # Đúng
to_port = ingress.value.to_port
protocol = ingress.value.protocol
cidr_blocks = ingress.value.cidr_blocks
}
}
ingress.value trả về phần tử hiện tại; ingress.key trả về khóa của map (hoặc chỉ số của list).
Cách Sửa 2 — Khai Báo Iterator Tường Minh
Muốn dùng tên ngắn hơn như rule? Hãy thêm đối số iterator trực tiếp bên trong khối dynamic. Cách này sẽ ghi đè tên mặc định.
dynamic "ingress" {
for_each = var.ingress_rules
iterator = rule # Đặt tên tường minh cho iterator là "rule"
content {
from_port = rule.value.from_port # Hợp lệ
to_port = rule.value.to_port
protocol = rule.value.protocol
cidr_blocks = rule.value.cidr_blocks
}
}
Đối số iterator chỉ là một alias cục bộ — nó thay đổi cách bạn tham chiếu phần tử hiện tại bên trong content, nhưng không ảnh hưởng gì đến cấu hình Terraform được tạo ra.
Ví Dụ Hoàn Chỉnh
Một AWS security group đầy đủ với hai ingress rule động cho cổng 80 và 443:
variable "ingress_rules" {
type = list(object({
from_port = number
to_port = number
protocol = string
cidr_blocks = list(string)
}))
default = [
{ from_port = 80, to_port = 80, protocol = "tcp", cidr_blocks = ["0.0.0.0/0"] },
{ from_port = 443, to_port = 443, protocol = "tcp", cidr_blocks = ["0.0.0.0/0"] },
]
}
resource "aws_security_group" "web" {
name = "web-sg"
# Cách 1: dùng nhãn khối làm iterator
dynamic "ingress" {
for_each = var.ingress_rules
content {
from_port = ingress.value.from_port
to_port = ingress.value.to_port
protocol = ingress.value.protocol
cidr_blocks = ingress.value.cidr_blocks
}
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
Khối Dynamic Lồng Nhau
Mỗi khối dynamic lồng nhau có phạm vi iterator riêng, mặc định theo nhãn của khối. Điều này hoạt động tốt — cho đến khi hai nhãn trùng tên, hoặc bạn vô tình tham chiếu iterator ngoài từ bên trong content của khối trong. Việc đặt tên iterator tường minh loại bỏ hoàn toàn sự mơ hồ này:
resource "aws_wafv2_rule_group" "example" {
name = "example"
scope = "REGIONAL"
capacity = 100
dynamic "rule" {
for_each = var.rules
iterator = waf_rule # iterator ngoài
content {
name = waf_rule.value.name
priority = waf_rule.value.priority
dynamic "statement" {
for_each = waf_rule.value.statements
iterator = stmt # iterator trong
content {
# Dùng stmt.value ở đây, không phải rule.value
byte_match_statement {
search_string = stmt.value.search_string
}
}
}
}
}
}
Nếu không có iterator = stmt, khối trong sẽ mặc định lấy tên statement. Viết rule.value bên trong nó và bạn sẽ vô tình lấy iterator ngoài, tạo ra dữ liệu sai. Dùng một tên thứ ba hoàn toàn khác và bạn lại quay về lỗi tham chiếu chưa khai báo ban đầu.
Xác Nhận Đã Sửa
- Chạy
terraform validatetrước — không cần kết nối backend:
terraform validate
# Kết quả mong đợi: Success! The configuration is valid.
- Chạy
terraform planvà kiểm tra các khối đã được mở rộng trong output:
terraform plan
# Kiểm tra các khối được mở rộng trong kết quả plan, ví dụ:
# + ingress {
# + from_port = 80
# + to_port = 80
# ...
Cả hai bước đều thành công nghĩa là iterator đã được phân giải đúng. Lưu ý rằng validate chỉ kiểm tra cú pháp và tham chiếu — bạn cần plan để xác nhận các giá trị thực tế sau khi mở rộng trông có đúng không.
Phòng Ngừa
- Bắt đầu với nhãn khối. Khi viết một khối
dynamicmới, hãy dùng tên iterator được tạo tự động trước. Chỉ thêmiteratortường minh khi nhãn không rõ ràng hoặc xung đột với khối lồng nhau. - Đổi tên nhãn và iterator cùng lúc. Khi thay đổi nhãn của một khối, hãy thực hiện tìm-và-thay-thế có phạm vi trên tên iterator cũ bên trong
contentcủa khối đó trước khi commit. - Thêm
terraform validatevào CI. Lệnh này chạy trong chưa đầy một giây với hầu hết các module, không cần backend, và phát hiện các lỗi tham chiếu như thế này trước khi pull request được merge. - Luôn đặt tên tường minh cho iterator trong. Các khối
dynamiclồng nhau với tên iterator mặc định là cạm bẫy về khả năng đọc hiểu. Một đối sốiteratorgiúp phạm vi trở nên rõ ràng ngay khi nhìn vào.

