如何在Terraform for_each循环内获取资源ID配置Azure防火墙规则
问题复现
使用Terraform创建Azure防火墙网络规则时,规则集合需要引用已创建的ipg_onpremiseIP组,直接在配置中传入IP组字符串名称时,部署触发如下报错:
Error: creating/updating Network Rule Collection "onprem-hub" in Firewall "fw_shared" (Resource Group "rg-shared"): network.AzureFirewallsClient#CreateOrUpdate: Failure sending request: StatusCode=400 -- Original Error: Code="AzureIpGroupsInvalidResourceId" Message="The resourceId ipg_onpremise is an invalid resourceId of an IpGroup." Details=[]
涉及的原始配置如下:firewall.tf 核心配置:
resource "azurerm_firewall_network_rule_collection" "nrc_fw_shared_onprem_hub" { name = "onprem-hub" azure_firewall_name = azurerm_firewall.fw_shared.name resource_group_name = azurerm_resource_group.rg_shared.name priority = var.nrc_fw_shared_onprem_hub_rule_priority action = var.nrc_fw_shared_onprem_hub_rule_action dynamic "rule" { for_each = var.nrc_fw_shared_onprem_hub_rules content { name = rule.value.name description = lookup(rule.value, "description", null) source_addresses = lookup(rule.value, "source_addresses", null) source_ip_groups = lookup(rule.value, "source_ip_groups" , null) destination_addresses = lookup(rule.value, "destination_addresses", null) destination_ip_groups = lookup(rule.value, "destination_ip_groups", null) destination_ports = lookup(rule.value, "destination_ports", null) protocols = lookup(rule.value, "protocols", null) } } } resource "azurerm_ip_group" "ipg_onpremise" { name = "ipg-onpremise" location = azurerm_resource_group.rg_shared.location resource_group_name = azurerm_resource_group.rg_shared.name cidrs = var.ipg_onpremise_cidrs }
terraform.tfvars 核心配置:
nrc_fw_shared_onprem_hub_rules = [ { name = "onprem->hub" source_addresses = [] destination_addresses = ["10.0.0.0/16"] destination_ports = ["*"] protocols = ["Any"] source_ip_groups = ["ipg_onpremise"] destination_ip_groups = [] }, ]
根本原因
Azure RM Terraform提供程序中,防火墙规则的source_ip_groups、destination_ip_groups字段仅接受IP组的完整Azure资源ID作为合法入参,不支持直接传入IP组名称。当前配置在tfvars中传入的是字符串形式的IP组自定义名称,不符合API参数要求,因此触发资源ID无效报错。
额外注意:当前配置中声明的IP组资源实际命名为ipg-onpremise(中划线分隔),tfvars中写的ipg_onpremise是下划线分隔的自定义标识,和资源实际名称不匹配,后续做名称映射时需要注意对齐。
可行实现方案
根据IP组是否在当前Terraform配置中管理,分两类场景处理:
场景1:IP组由当前Terraform配置管理
即当前场景,IP组资源已经在同一份配置中声明,Terraform会自动为该资源导出id属性,属性值就是符合格式要求的完整资源ID,无需手动拼接。由于Terraform不会自动将变量中传入的字符串解析为对应资源引用,可选择以下两种改法:
- 改法1:固定引用(适合IP组引用关系固定的场景)
不需要在tfvars中传递IP组名称,直接在动态规则块中引用资源ID,给规则增加一个布尔类型的标记位控制是否启用该IP组即可。
调整动态块逻辑:
对应调整tfvars规则配置:dynamic "rule" { for_each = var.nrc_fw_shared_onprem_hub_rules content { name = rule.value.name description = lookup(rule.value, "description", null) source_addresses = lookup(rule.value, "source_addresses", null) source_ip_groups = lookup(rule.value, "use_onprem_source_ipg", false) ? [azurerm_ip_group.ipg_onpremise.id] : lookup(rule.value, "source_ip_groups" , null) destination_addresses = lookup(rule.value, "destination_addresses", null) destination_ip_groups = lookup(rule.value, "destination_ip_groups", null) destination_ports = lookup(rule.value, "destination_ports", null) protocols = lookup(rule.value, "protocols", null) } }nrc_fw_shared_onprem_hub_rules = [ { name = "onprem->hub" source_addresses = [] destination_addresses = ["10.0.0.0/16"] destination_ports = ["*"] protocols = ["Any"] use_onprem_source_ipg = true destination_ip_groups = [] }, ] - 改法2:名称映射(适合需要灵活引用多个同配置IP组的场景)
通过本地值维护IP组自定义名称到资源ID的映射关系,在动态块中自动将变量传入的名称转换为对应ID,不需要改动现有tfvars的结构。
首先在配置中添加本地值映射:
再调整动态块中IP组字段的赋值逻辑:locals { ip_group_id_map = { "ipg_onpremise" = azurerm_ip_group.ipg_onpremise.id # 后续新增同配置管理的IP组,可在此处追加映射关系 } }dynamic "rule" { for_each = var.nrc_fw_shared_onprem_hub_rules content { name = rule.value.name description = lookup(rule.value, "description", null) source_addresses = lookup(rule.value, "source_addresses", null) source_ip_groups = [for ipg_name in lookup(rule.value, "source_ip_groups" , []) : local.ip_group_id_map[ipg_name]] destination_addresses = lookup(rule.value, "destination_addresses", null) destination_ip_groups = [for ipg_name in lookup(rule.value, "destination_ip_groups" , []) : local.ip_group_id_map[ipg_name]] destination_ports = lookup(rule.value, "destination_ports", null) protocols = lookup(rule.value, "protocols", null) } }
场景2:IP组不在当前Terraform配置中管理
如果要引用的IP组是提前创建、由其他配置或人工管理的资源,不要硬编码完整资源ID,通过azurerm_ip_group数据源查询获取资源ID即可:
data "azurerm_ip_group" "ipg_onpremise" { name = "ipg-onpremise" resource_group_name = "rg-shared" }
后续在规则中引用data.azurerm_ip_group.ipg_onpremise.id即可获得合法的资源ID,用法和引用当前配置内管理的IP组完全一致。
最佳实践提示
- 禁止手动拼接Azure资源ID,不同环境的订阅ID、资源组名称、资源命名可能存在差异,直接引用资源或数据源导出的
id属性可以完全避免格式错误、环境适配问题 - 做IP组名称映射时,注意对齐自定义标识和实际资源的对应关系,避免出现索引不存在的报错
内容的提问来源于stack exchange,提问作者Timbo

