Terraform动态创建AWS EFS挂载目标遇执行计划错误求助
问题描述
在Terraform部署流程中,VPC模块通过data "aws_availability_zones" "available"自动为目标区域的每个可用区创建公网和私有子网。但在EFS模块中尝试为每个私有子网创建挂载目标时,执行terraform plan报错:
Error: Invalid for_each argument │ │ on modules/efs/main.tf line 55, in resource "aws_efs_mount_target" "efs_mount_targets_private": │ XX: for_each = toset(var.private_subnets) │ ├──────────────── │ │ var.private_subnets is a list of string, known only after apply
尝试在EFS模块内添加aws_subnets数据块后问题依旧,需要找到无需分阶段部署的解决办法,同时疑惑:为什么基于aws_availability_zones创建子网可行,而EFS场景会触发这个错误?
相关代码片段如下:
根模块main.tf片段
module "vpc" { count = terraform.workspace == "backend" ? 0 : 1 source = "./modules/vpc" vpc_flow_logs_to_cloud_watch_group = module.cw[0].my-vpc-flow-logs-group vpc_flow_logs_to_cloud_watch_role = module.iam[0].vpc_flow_logs_to_cloud_watch_role vpn_vgw_id = module.vpn[0].vpn-vgw-id nat_gw_eip = module.ec2[0].nat_gw_eip } module "efs" { count = terraform.workspace == "backend" ? 0 : 1 source = "./modules/efs" vpc_id = module.vpc[0].my_vpc_id allow-nfs-ingress-my-vpc = module.sg-ingress[0].allow-nfs-ingress-my-vpc-id private_subnets = module.vpc[0].my_vpc_private_subnets }
VPC模块main.tf片段
# Create private subnets out of odd octets resource "aws_subnet" "my_vpc_private_subnets" { for_each = toset(data.aws_availability_zones.available.names) vpc_id = aws_vpc.my_vpc.id availability_zone = each.value cidr_block = "172.25.${2*(index(data.aws_availability_zones.available.names, each.value))+1}.0/24" tags = { auto-delete = "no" Name = "${each.value} subnet ${2*(index(data.aws_availability_zones.available.names, each.value))+1}" tier = "private" } } data "aws_subnets" "private_subnets" { filter { name = "vpc-id" values = [aws_vpc.my_vpc.id] } tags = { tier = "private" } # Without the below, this data block might evaluate to empty depends_on = [ aws_subnet.my_vpc_private_subnets ] }
VPC模块outputs.tf片段
output "my_vpc_private_subnets" { value = data.aws_subnets.private_subnets.ids }
EFS模块挂载目标代码
resource "aws_efs_mount_target" "efs_mount_targets_private" { for_each = toset(var.private_subnets) file_system_id = aws_efs_file_system.pgsql_databases.id security_groups = [ "${var.allow-nfs-ingress-my-vpc}" ] subnet_id = each.value }
解决办法
核心问题是var.private_subnets的值是apply阶段才能生成的未知值,而for_each要求在plan阶段就明确知道集合的所有元素。以下两种方案可解决问题:
方案1:复用可用区列表,在EFS模块内查询子网
放弃直接传递子网ID列表,改为传递可用区列表,在EFS模块内通过可用区+标签筛选私有子网:
- 修改EFS模块变量定义:
variable "vpc_id" { type = string } variable "private_subnet_azs" { type = set(string) } variable "allow-nfs-ingress-my-vpc" { type = string }
- 根模块传递参数:
module "efs" { count = terraform.workspace == "backend" ? 0 : 1 source = "./modules/efs" vpc_id = module.vpc[0].my_vpc_id allow-nfs-ingress-my-vpc = module.sg-ingress[0].allow-nfs-ingress-my-vpc-id private_subnet_azs = module.vpc[0].my_vpc_azs # 需要在VPC模块新增该输出 }
- VPC模块添加可用区输出:
output "my_vpc_azs" { value = toset(data.aws_availability_zones.available.names) }
- EFS模块内创建挂载目标:
data "aws_subnet" "private_subnets" { for_each = var.private_subnet_azs vpc_id = var.vpc_id availability_zone = each.value tags = { tier = "private" } } resource "aws_efs_mount_target" "efs_mount_targets_private" { for_each = data.aws_subnet.private_subnets file_system_id = aws_efs_file_system.pgsql_databases.id security_groups = [var.allow-nfs-ingress-my-vpc] subnet_id = each.value.id }
方案2:直接传递VPC模块的子网资源映射
在VPC模块中输出子网资源的完整映射,而非通过数据块获取的ID列表:
- 修改VPC模块outputs.tf:
output "my_vpc_private_subnets_map" { value = aws_subnet.my_vpc_private_subnets }
- 根模块传递参数:
module "efs" { count = terraform.workspace == "backend" ? 0 : 1 source = "./modules/efs" vpc_id = module.vpc[0].my_vpc_id allow-nfs-ingress-my-vpc = module.sg-ingress[0].allow-nfs-ingress-my-vpc-id private_subnets_map = module.vpc[0].my_vpc_private_subnets_map }
- EFS模块变量与挂载目标代码:
variable "private_subnets_map" { type = map(object({ id = string availability_zone = string })) } resource "aws_efs_mount_target" "efs_mount_targets_private" { for_each = var.private_subnets_map file_system_id = aws_efs_file_system.pgsql_databases.id security_groups = [var.allow-nfs-ingress-my-vpc] subnet_id = each.value.id }
场景差异原因解释
- VPC模块创建子网时,
for_each使用的data.aws_availability_zones.available.names是plan阶段就能确定的已知数据——Terraform可以提前查询AWS获取目标区域的可用区列表,因此能在plan阶段明确子网的创建数量和对应参数。 - 而EFS模块中使用的
var.private_subnets来自VPC模块的data.aws_subnets.private_subnets.ids,这个数据块依赖于aws_subnet.my_vpc_private_subnets资源,资源的ID只有在apply阶段创建完成后才会生成,属于plan阶段的未知值,不符合for_each的参数要求。
内容的提问来源于stack exchange,提问作者RiverRook
相关产品推荐
相关产品推荐

