Terraform v0.14引用EC2模块输出关联NLB目标组报错求助
Invalid for_each argument错误的方案 嘿,我帮你分析下这个Terraform报错的根源,再给你几个可行的修复方案:
这个错误的核心原因是:Terraform的for_each要求在**计划阶段(terraform plan)**就能确定集合里的每一个元素,必须是已知、唯一且不可变的值。但你代码里的var.target_id是通过EC2实例的私有IP(这是资源创建后才会生成的动态属性)拼接出来的,这些值在plan阶段是未知的,所以Terraform没法处理,直接抛出了错误。
下面是两种修复方案,还有一些优化建议:
方案一:改用实例索引作为for_each的稳定键(推荐)
这个方案能避免依赖未知的动态属性,同时保留for_each的优势(资源变更时不会不必要地销毁重建)。
1. 修改项目级main.tf的NLB模块调用
把原来拼接好的target_id替换成传递EC2实例的私有IP列表和目标端口:
module "network_load_balancer" { depends_on=[module.ec2_detail] source = "../../../../modules/nlb_new2" for_each = var.all_load_balancer name = format("%s-${var.ec2_name.suffix}",each.key) load_balancer_type = each.value.load_balancer_type vpc_name = var.ec2_common_prop.vpc_name subnet_names = var.security.subnet_names internal = lookup(merge(var.lb_tags, each.value.lb_tags), "internal", true) # 替换原来的target_id,传递原始的私有IP列表和端口 target_private_ips = module.ec2_detail["memsql-child"].private_ip target_port = lookup(var.lb_common_prop,"target_port") target_groups = each.value.target_groups http_tcp_listeners = each.value.http_tcp_listeners tags = merge(var.lb_tags, each.value.lb_tags) }
2. 在NLB模块中新增变量
在../../../../modules/nlb_new2/variables.tf里添加:
variable "target_private_ips" { type = list(string) description = "需要关联到目标组的子节点EC2实例私有IP列表" default = [] } variable "target_port" { type = number description = "目标组关联的后端端口" }
3. 重构NLB模块中的目标组关联资源
修改../../../../modules/nlb_new2/main.tf里的aws_lb_target_group_attachment:
resource "aws_lb_target_group_attachment" "tgr_attachment" { depends_on = [aws_lb.this] # 使用实例IP的索引作为for_each的键,索引在plan阶段是已知的(长度由instance_count决定) for_each = { for idx, private_ip in var.target_private_ips : idx => { target_group_arn = aws_lb_target_group.main[0].arn target_id = private_ip port = var.target_port } } target_group_arn = var.create_lb ? each.value.target_group_arn : null target_id = var.create_lb ? each.value.target_id : null port = var.create_lb ? each.value.port : null }
⚠️ 注意:如果你的目标组target_type设置的是"instance"(不是"ip"),那要把target_private_ips换成module.ec2_detail["memsql-child"].instance_ids,用实例ID作为target_id。
方案二:改用count代替for_each
如果不想调整模块参数,也可以用count来替代for_each,因为count只需要知道列表的长度(这个在plan阶段是已知的,由instance_count决定):
修改NLB模块中的aws_lb_target_group_attachment:
resource "aws_lb_target_group_attachment" "tgr_attachment" { depends_on = [aws_lb.this] # 根据target_id的长度决定创建多少个关联 count = var.create_lb ? length(var.target_id) : 0 target_group_arn = aws_lb_target_group.main[0].arn target_id = length(split(":", var.target_id[count.index])) > 0 ? split(":", var.target_id[count.index])[0] : null port = length(split(":", var.target_id[count.index])) == 2 ? split(":", var.target_id[count.index])[1] : null }
👉 小提醒:count的缺点是如果EC2实例的顺序变化(比如新增/删除实例),Terraform会销毁重建后面的关联资源,而for_each用稳定键的话只会处理变化的元素,所以优先推荐方案一。
额外的最佳实践
- 确保目标组的
target_type和你传入的target_id类型匹配:ip对应私有IP,instance对应实例ID,不匹配会导致关联失败。 - 模块间的依赖尽量通过输出变量自动传递,
depends_on可以作为兜底,但能不用就不用,让Terraform自动推断依赖更可靠。 - Terraform v0.14比较老了,条件允许的话尽量升级到较新的稳定版本(比如1.x系列),能获得更多功能和bug修复。
内容的提问来源于stack exchange,提问作者avinash khajure

