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

Terraform创建新AWS账号时for_each参数依赖问题求助

解决Terraform创建新账号时CloudFormation StackSet实例的for_each错误

问题背景

通过本地变量映射AWS账号ID与账号类型,传递给子模块创建SSM补丁管理器的CloudFormation StackSet资源。现有账号配置运行正常,但通过module.accounts创建新账号时,执行terraform plan出现for_each参数无效错误,原因是新账号的ID要到apply阶段才能确定,导致Terraform无法提前预知for_each的键集合。

根模块本地变量与子模块调用代码

locals {
    org_sub_accounts_map = zipmap(module.accounts.account_id, module.accounts.tags_all.*.AccountType)
    org_sub_accounts = [for k, v in local.org_sub_accounts_map : {
      id   = k
      type = v
      }
    ]
  }
  
  module "ssm_patch_manager" {
  source                  = "../../../../modules/aws/cloudformation/stacksets"
  accounts                = local.org_sub_accounts
  account_exception_list  = var.account_exception_list
  regions                 = var.region_list
  stackset_name           = "SSM-PatchManager"
  template                = "ssm_patch_manager"
  parameters              = var.patch_manager_default_params
  parameter_overrides     = var.patch_manager_params_overrides
  stackset_admin_role_arn = module.stackset_admin_role.role_arn
  depends_on              = [module.accounts]
}

local.org_sub_accounts示例:

org_sub_accounts = [
    {
      "id" = "111111111111"
      "type" = "Dev"
    },
    {
      "id" = "222222222222"
      "type" = "Prod"
    },
    {
      "id" = "33333333333"
      "type" = "Dev"
    }
 ]

报错信息

Error: Invalid for_each argument

  on ../../../../modules/aws/cloudformation/stacksets/main.tf line 25, in resource "aws_cloudformation_stack_set_instance" "stack":
  25:   for_each = {
  26:     for stack_instance in local.instance_data : "${stack_instance.account}.${stack_instance.region}" => stack_instance if contains(var.account_exception_list, stack_instance.account) == false
  27:   }
    ├────────────────
    │ local.instance_data will be known only after apply
    │ var.account_exception_list is list of string with 1 element

The "for_each" value depends on resource attributes that cannot be determined
until apply, so Terraform cannot predict how many instances will be created.
To work around this, use the -target argument to first apply only the
resources that the for_each depends on.

子模块关键代码

locals {
  instance_data = flatten([
    for account in var.accounts : [
      for region in var.regions : {
        account = account.id
        type    = try(length(account.type), 0) > 0 ? account.type : "default" # 兼容旧版输入,未传递type的情况
        region  = region
      }
    ]
  ])
}

resource "aws_cloudformation_stack_set_instance" "stack" {
  for_each = {
    for stack_instance in local.instance_data : "${stack_instance.account}.${stack_instance.region}" => stack_instance if contains(var.account_exception_list, stack_instance.account) == false
  }
  account_id          = each.value.account
  region              = each.value.region
  parameter_overrides = lookup(var.parameter_overrides, each.value.type, null) # 根据子账号的AccountType标签使用不同参数
  stack_set_name      = aws_cloudformation_stack_set.stackset.name
}

解决方案(无需大幅修改现有结构)

方案1:分阶段执行(官方临时 workaround)

先单独创建账号资源,让Terraform确定账号ID后再执行完整部署:

  1. 执行terraform apply -target=module.accounts,优先创建新账号,此时账号ID会被写入状态文件
  2. 再执行terraform plan和terraform apply,此时for_each能获取到已知的账号ID,不会再触发报错

方案2:用可预测标识生成for_each键(持久化解决)

如果不想每次分阶段执行,可以调整子模块的for_each键生成逻辑,用plan阶段就能确定的账号标识(比如预先定义的账号名称)代替账号ID:

  1. 根模块传递账号名称(假设module.accounts输出account_names与account_id一一对应):
    locals {
        org_sub_accounts = [for idx, id in module.accounts.account_id : {
          id   = id
          name = module.accounts.account_names[idx]
          type = module.accounts.tags_all[idx].AccountType
          }
        ]
      }
    
  2. 子模块修改instance_data和for_each:
    locals {
      instance_data = flatten([
        for account in var.accounts : [
          for region in var.regions : {
            account_id = account.id
            account_name = account.name
            type    = try(length(account.type), 0) > 0 ? account.type : "default"
            region  = region
          }
        ]
      ])
    }
    
    resource "aws_cloudformation_stack_set_instance" "stack" {
      # 用账号名称+区域作为for_each键,名称在plan阶段即可确定
      for_each = {
        for stack_instance in local.instance_data : "${stack_instance.account_name}.${stack_instance.region}" => stack_instance if contains(var.account_exception_list, stack_instance.account_id) == false
      }
      account_id          = each.value.account_id
      region              = each.value.region
      parameter_overrides = lookup(var.parameter_overrides, each.value.type, null)
      stack_set_name      = aws_cloudformation_stack_set.stackset.name
    }
    

方案3:用terraform_data延迟资源创建(Terraform 1.4+适用)

通过terraform_data作为依赖屏障,确保账号创建完成后再处理StackSet实例:

resource "terraform_data" "wait_for_accounts" {
  input = var.accounts
}

resource "aws_cloudformation_stack_set_instance" "stack" {
  for_each = {
    for stack_instance in local.instance_data : "${stack_instance.account}.${stack_instance.region}" => stack_instance if contains(var.account_exception_list, stack_instance.account) == false
  }
  account_id          = each.value.account
  region              = each.value.region
  parameter_overrides = lookup(var.parameter_overrides, each.value.type, null)
  stack_set_name      = aws_cloudformation_stack_set.stackset.name
  
  depends_on = [terraform_data.wait_for_accounts]
}

注:此方案仅在部分场景下有效,核心仍需确保for_each的键可提前预测。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.01 02:05:44