You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Terraform使用for_each引用远程状态报错的问题求助

解决Terraform for_each依赖远程状态导致的"known only after apply"问题

问题场景

使用Terraform搭建Apigee基础设施时,通过terraform_remote_state获取远程状态数据,在target_server子模块的apigee_target_server资源中使用for_each元参数时,触发以下错误:

  • var.target_server_json will be known only after apply
    The "for_each" map includes keys derived from resource attributes that cannot be determined until apply, and so Terraform cannot determine the full set of keys that will identify the instances of this resource.

相关代码片段

根模块核心代码

locals {
  artifacts_downloads_path        = "${path.cwd}/artifacts"
  proxy_zip_file_name             = "${var.proxy_name}.${var.proxy_version}.zip"
  local_proxy_artifact_directory  = "${local.artifacts_downloads_path}/${var.proxy_name}"
  script_file_directory           = "${path.cwd}/scripts"
  terraform_state_output          = data.terraform_remote_state.terraform_state_values.outputs
}

module "target_server" {
  for_each        = {
    for targetServer in var.list_of_target_servers_entries : targetServer => targetServer
  }
  source          = "../target_server"
  apigee_env      = var.apigee_env
  target_server_json = jsondecode(element(values(local.terraform_state_output), index(keys(local.terraform_state_output), each.value)))
  depends_on      = [data.terraform_remote_state.terraform_state_values]
}

data "terraform_remote_state" "terraform_state_values" {
  backend = "gcs"
  config = {
    bucket = var.terraform_state_bucket_name
    prefix = var.terraform_state_bucket_path_prefix
  }
}

原target_server模块代码(触发错误)

resource "apigee_target_server" "target_server" {
  for_each = {
    for index, entry in jsondecode(var.target_server_json) : index => entry
  }
  environment_name = var.apigee_env
  name             = each.value.name
  host             = strcontains(each.value.host, "https") ? split("https://", each.value.host)[1] : each.value.host
  port             = each.value.port
  is_enabled       = each.value.enabled ? true : false
  protocols        = each.value.protocol != [] ? [each.value.protocol] : []
}

尝试的count重构代码

locals {
items_count = length(jsondecode(var.target_server_json))
}
resource "apigee_target_server" "target_server" {
  count = local.items_count

  environment_name = var.apigee_env
  name = var.target_server_json[count.index].name
 host             = strcontains(var.target_server_json[count.index].host, "https") ? split("https://", var.target_server_json[count.index].host)[1] : var.target_server_json[count.index].host
 port             = var.target_server_json[count.index].port
 is_enabled       = var.target_server_json[count.index].enabled ? true : false
 protocols        = var.target_server_json[count.index].protocol != [] ? [var.target_server_json[count.index].protocol] : []
}

有效解决方案

方案1:优化远程状态输出与变量传递(推荐)

问题根源是var.target_server_json来自远程状态的动态查找,Terraform在plan阶段无法确定其内容。调整如下:

  1. 修改远程状态生成模块:将目标服务器数据处理为明确的map或list(object)结构输出,而非原始JSON字符串,确保plan阶段即可识别数据结构。
  2. 根模块直接引用结构化数据:避免使用element+index的动态查找方式,直接通过键名引用远程状态输出:
# 根模块中修改target_server模块调用
module "target_server" {
  for_each        = var.list_of_target_servers_entries
  source          = "../target_server"
  apigee_env      = var.apigee_env
  # 假设远程状态输出中直接有target_servers映射,键为targetServer名称
  target_servers  = local.terraform_state_output.target_servers[each.value]
  depends_on      = [data.terraform_remote_state.terraform_state_values]
}
  1. 子模块定义强类型变量:
# target_server子模块变量定义
variable "target_servers" {
  type = list(object({
    name     = string
    host     = string
    port     = number
    enabled  = bool
    protocol = list(string)
  }))
  description = "结构化的目标服务器列表"
}

# 子模块资源使用for_each(基于稳定的name字段作为键)
resource "apigee_target_server" "target_server" {
  for_each = { for ts in var.target_servers : ts.name => ts }

  environment_name = var.apigee_env
  name             = each.value.name
  host             = strcontains(each.value.host, "https") ? split("https://", each.value.host)[1] : each.value.host
  port             = each.value.port
  is_enabled       = each.value.enabled
  protocols        = each.value.protocol != [] ? each.value.protocol : []
}

这种方式让Terraform在plan阶段就能确定for_each的键集合,避免"known only after apply"错误,同时保证资源变更的稳定性(键基于名称而非索引)。

方案2:使用terraform_data延迟解析(Terraform 1.4+)

如果无法修改远程状态结构,可利用Terraform 1.4+新增的terraform_data资源,在apply阶段完成JSON解析,再基于解析结果创建资源:

# target_server子模块中
terraform_data "parsed_target_servers" {
  input = jsondecode(var.target_server_json)
}

resource "apigee_target_server" "target_server" {
  for_each = { for ts in terraform_data.parsed_target_servers.output : ts.name => ts }

  environment_name = var.apigee_env
  name             = each.value.name
  host             = strcontains(each.value.host, "https") ? split("https://", each.value.host)[1] : each.value.host
  port             = each.value.port
  is_enabled       = each.value.enabled
  protocols        = each.value.protocol != [] ? each.value.protocol : []
}

注意:此方式下plan阶段无法预览目标服务器资源,仅能在apply阶段创建,适合临时过渡场景。

方案3:优化count实现(仅临时过渡)

如果必须使用count,需避免在plan阶段依赖未知的JSON解析结果,可在根模块提前解析并传递结构化数据:

# 根模块中修改target_server调用
module "target_server" {
  for_each        = var.list_of_target_servers_entries
  source          = "../target_server"
  apigee_env      = var.apigee_env
  # 根模块提前解析JSON为结构化数据
  target_servers  = jsondecode(element(values(local.terraform_state_output), index(keys(local.terraform_state_output), each.value)))
  depends_on      = [data.terraform_remote_state.terraform_state_values]
}

# target_server子模块中
variable "target_servers" {
  type = list(object({
    name     = string
    host     = string
    port     = number
    enabled  = bool
    protocol = list(string)
  }))
}

resource "apigee_target_server" "target_server" {
  count = length(var.target_servers)

  environment_name = var.apigee_env
  name             = var.target_servers[count.index].name
  host             = strcontains(var.target_servers[count.index].host, "https") ? split("https://", var.target_servers[count.index].host)[1] : var.target_servers[count.index].host
  port             = var.target_servers[count.index].port
  is_enabled       = var.target_servers[count.index].enabled
  protocols        = var.target_servers[count.index].protocol != [] ? var.target_servers[count.index].protocol : []
}

注意:count依赖列表索引,若列表顺序变更会导致资源重建,稳定性不如for_each。

内容的提问来源于stack exchange,提问作者mukul

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.06 04:24:55