Sửa lỗi Terraform 'Reference to undeclared resource' Khi Dùng Sai Tên Iterator trong Dynamic Block

intermediate🏗️ Terraform2026-07-07| Terraform 0.13+, HashiCorp HCL, mọi hệ điều hành (Linux, macOS, Windows)

Error Message

Error: Reference to undeclared resource. A dynamic block's "content" must refer to the block label or the "iterator" argument.
#terraform#dynamic-block#iterator#hcl

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 validate trướ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 plan và 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 dynamic mới, hãy dùng tên iterator được tạo tự động trước. Chỉ thêm iterator tườ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 content của khối đó trước khi commit.
  • Thêm terraform validate và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 dynamic lồ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ố iterator giúp phạm vi trở nên rõ ràng ngay khi nhìn vào.

Related Error Notes