如何解决 Terraform 不可预测的实例创建问题?
how to fix terraform unpredict instance creation issue?
我在 运行 terraform 计划和应用时遇到以下错误
on main.tf line 517, in resource "aws_lb_target_group_attachment" "ecom-tga":
│ 517: for_each = local.service_instance_map
│ ├────────────────
│ │ local.service_instance_map will be known only after apply
│
│ 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.
我的配置文件如下
variable "instance_count" {
type = string
default = 3
}
variable "service-names" {
type = list
default = ["valid","jsc","test"]
}
locals {
helper_map = {for idx, val in setproduct(var.service-names, range(var.instance_count)):
idx => {service_name = val[0]}
}
}
resource "aws_instance" "ecom-validation-service" {
for_each = local.helper_map
ami = data.aws_ami.ecom.id
instance_type = "t3.micro"
tags = {
Name = "${each.value.service_name}-service"
}
vpc_security_group_ids = [data.aws_security_group.ecom-sg[each.value.service_name].id]
subnet_id = data.aws_subnet.ecom-subnet[each.value.service_name].id
}
data "aws_instances" "ecom-instances" {
for_each = toset(var.service-names)
instance_tags = {
Name = "${each.value}-service"
}
instance_state_names = ["running", "stopped"]
depends_on = [
aws_instance.ecom-validation-service
]
}
locals {
service_instance_map = merge([for env, value in data.aws_instances.ecom-instances:
{
for id in value.ids:
"${env}-${id}" => {
"service-name" = env
"id" = id
}
}
]...)
}
resource "aws_lb_target_group_attachment" "ecom-tga" {
for_each = local.service_instance_map
target_group_arn = aws_lb_target_group.ecom-nlb-tgp[each.value.service-name].arn
port = 80
target_id = each.value.id
depends_on = [aws_lb_target_group.ecom-nlb-tgp]
}
因为我将 count 作为 var 传递并且它的值为 3,我认为 terraform 会预测,因为它需要创建 9 instances.But 它似乎没有并且抛出错误无法预测。
我们是否必须通过为实例计数预测或本地 service_instance_map 提供一些默认值来绕过此问题?
尝试了 try 功能,但还是不行
Error: Invalid for_each argument
│
│ on main.tf line 527, in resource "aws_lb_target_group_attachment" "ecom-tga":
│ 527: for_each = try(local.service_instance_map,[])
│ ├────────────────
│ │ local.service_instance_map will be known only after apply
│
│ 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.
我的要求发生了变化,现在我必须在可用的 3 个子网中创建 3 个实例 region.I 更改了局部变量,如下所示但是相同的预测问题
locals {
merged_subnet_svc = try(flatten([
for service in var.service-names : [
for subnet in aws_subnet.ecom-private.*.id : {
service = service
subnet = subnet
}
]
]), {})
variable "azs" {
type = list(any)
default = ["ap-south-1a", "ap-south-1b", "ap-south-1c"]
}
variable "private-subnets" {
type = list(any)
default = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
}
resource "aws_instance" "ecom-instances" {
for_each = {
for svc in local.merged_subnet_svc : "${svc.service}-${svc.subnet}" => svc
}
ami = data.aws_ami.ecom.id
instance_type = "t3.micro"
tags = {
Name = "ecom-${each.value.service}-service"
}
vpc_security_group_ids = [aws_security_group.ecom-sg[each.value.service].id]
subnet_id = each.value.subnet
}
}
在您的配置中,您已声明 data "aws_instances" "ecom-instances"
依赖于 aws_instance.ecom-validation-service
。由于该其他对象在您的第一个 运行 上尚不存在,因此 Terraform 必须等到应用步骤才能读取 data.aws_instances.ecom-instances
,否则它将无法遵守您声明的依赖项,因为 aws_instance.ecom-validation-service
还不存在。
为避免您在此处看到的错误消息,您需要确保 for_each
仅引用 Terraform 在 实际创建任何对象之前 知道的值。因为 EC2 仅在实例创建后分配实例 ID,所以使用 EC2 实例 ID 作为 for_each
实例键的一部分是不正确的。
此外,这里不需要 data "aws_instances"
块来检索实例信息,因为作为 resource "aws_instance" "ecom-validation-service"
块的结果,您已经拥有相关的实例信息。
综上所述,让我们从您的输入变量开始并重新构建,同时确保我们只根据我们在规划期间知道的值构建实例键。您拥有的变量基本保持不变;我只是稍微调整了类型约束以匹配我们如何使用每个约束:
variable "instance_count" {
type = string
default = 3
}
variable "service_names" {
type = set(string)
default = ["valid", "jsc", "test"]
}
我从您示例的其余部分了解到您打算为 var.service_names
的每个不同元素创建 var.instance_count
个实例。您的 setproduct
生成所有这些组合也很好,但我将对其进行调整以分配包含服务名称的实例唯一键:
locals {
instance_configs = tomap({
for pair in setproduct(var.service_names, range(var.instance_count)) :
"${pair[0]}${pair[1]}" => {
service_name = pair[0]
}
})
}
这将产生如下数据结构:
{
valid0 = { service_name = "valid" }
valid1 = { service_name = "valid" }
valid2 = { service_name = "valid" }
jsc0 = { service_name = "jsc" }
jsc1 = { service_name = "jsc" }
jsc2 = { service_name = "jsc" }
test0 = { service_name = "test" }
test1 = { service_name = "test" }
test2 = { service_name = "test" }
}
这与 for_each
期望的形状匹配,因此我们可以直接使用它来声明九个 aws_instance
个实例:
resource "aws_instance" "ecom-validation-service" {
for_each = local.instance_configs
instance_type = "t3.micro"
ami = data.aws_ami.ecom.id
subnet_id = data.aws_subnet.ecom-subnet[each.value.service_name].id
vpc_security_group_ids = [
data.aws_security_group.ecom-sg[each.value.service_name].id,
]
tags = {
Name = "${each.value.service_name}-service"
Service = each.value_service_name
}
}
到目前为止,这与您分享的内容基本相同。但这是我要朝着完全不同的方向前进的一点:现在我不再尝试使用单独的数据资源回读声明的实例,而是直接从 aws_instance.ecom-validation-service
资源。 Terraform 配置通常最好 管理特定对象 或 读取它,而不是同时读取,因为这样必要的依赖性排序会自动显示为参考文献。
请注意,我在每个实例上添加了一个额外的标签 Service
,以提供一种更方便的方法来取回服务名称。如果你不能这样做,那么你可以通过从 Name
标签中删除 -service
后缀来获得相同的信息,但我更喜欢尽可能直接。
您当时的目标似乎是每个实例有一个 aws_lb_target_group_attachment
个实例,每个实例都根据服务名称连接到适当的目标组。因为 aws_instance
资源设置了 for_each
,所以其他地方的表达式中的 aws_instance.ecom-validation-service
是一个对象映射,其中的键与 var.instance_configs
中的键相同。这意味着该值 也 与 for_each
的要求兼容,因此我们可以直接使用它来声明目标组附件:
resource "aws_lb_target_group_attachment" "ecom-tga" {
for_each = aws_instance.ecom-validation-service
target_group_arn = aws_lb_target_group.ecom-nlb-tgp[each.value.tags.Service].arn
port = 80
target_id = each.value.id
}
我依靠之前的额外 Service
标记轻松确定每个实例属于哪个服务,以便查找适当的目标组 ARN。 each.value.id
在这里工作是因为 each.value
始终是一个 aws_instance
对象,它导出 id
属性。
结果是两组实例,每个实例都具有与 local.instance_configs
中的键匹配的键:
aws_instance.ecom-validation-service["valid0"]
aws_instance.ecom-validation-service["valid1"]
aws_instance.ecom-validation-service["valid2"]
aws_instance.ecom-validation-service["jsc0"]
aws_instance.ecom-validation-service["jsc1"]
aws_instance.ecom-validation-service["jsc2"]
- ...
aws_lb_target_group_attachment.ecom-tga["valid0"]
aws_lb_target_group_attachment.ecom-tga["valid1"]
aws_lb_target_group_attachment.ecom-tga["valid2"]
aws_lb_target_group_attachment.ecom-tga["jsc0"]
aws_lb_target_group_attachment.ecom-tga["jsc1"]
aws_lb_target_group_attachment.ecom-tga["jsc2"]
- ...
请注意,所有这些键仅包含在配置中直接指定的信息,而不包含远程系统决定的任何信息。这意味着我们避免了“无效的 for_each 参数”错误,即使每个实例仍然有一个适当的唯一键。如果您稍后要向 var.service_names
添加新元素或增加 var.instance_count
,那么 Terraform 还将从这些实例键的形状中看到它应该只添加每个资源的新实例,而不是 renaming/renumbering 任何现有实例。
我在 运行 terraform 计划和应用时遇到以下错误
on main.tf line 517, in resource "aws_lb_target_group_attachment" "ecom-tga":
│ 517: for_each = local.service_instance_map
│ ├────────────────
│ │ local.service_instance_map will be known only after apply
│
│ 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.
我的配置文件如下
variable "instance_count" {
type = string
default = 3
}
variable "service-names" {
type = list
default = ["valid","jsc","test"]
}
locals {
helper_map = {for idx, val in setproduct(var.service-names, range(var.instance_count)):
idx => {service_name = val[0]}
}
}
resource "aws_instance" "ecom-validation-service" {
for_each = local.helper_map
ami = data.aws_ami.ecom.id
instance_type = "t3.micro"
tags = {
Name = "${each.value.service_name}-service"
}
vpc_security_group_ids = [data.aws_security_group.ecom-sg[each.value.service_name].id]
subnet_id = data.aws_subnet.ecom-subnet[each.value.service_name].id
}
data "aws_instances" "ecom-instances" {
for_each = toset(var.service-names)
instance_tags = {
Name = "${each.value}-service"
}
instance_state_names = ["running", "stopped"]
depends_on = [
aws_instance.ecom-validation-service
]
}
locals {
service_instance_map = merge([for env, value in data.aws_instances.ecom-instances:
{
for id in value.ids:
"${env}-${id}" => {
"service-name" = env
"id" = id
}
}
]...)
}
resource "aws_lb_target_group_attachment" "ecom-tga" {
for_each = local.service_instance_map
target_group_arn = aws_lb_target_group.ecom-nlb-tgp[each.value.service-name].arn
port = 80
target_id = each.value.id
depends_on = [aws_lb_target_group.ecom-nlb-tgp]
}
因为我将 count 作为 var 传递并且它的值为 3,我认为 terraform 会预测,因为它需要创建 9 instances.But 它似乎没有并且抛出错误无法预测。
我们是否必须通过为实例计数预测或本地 service_instance_map 提供一些默认值来绕过此问题?
尝试了 try 功能,但还是不行
Error: Invalid for_each argument
│
│ on main.tf line 527, in resource "aws_lb_target_group_attachment" "ecom-tga":
│ 527: for_each = try(local.service_instance_map,[])
│ ├────────────────
│ │ local.service_instance_map will be known only after apply
│
│ 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.
我的要求发生了变化,现在我必须在可用的 3 个子网中创建 3 个实例 region.I 更改了局部变量,如下所示但是相同的预测问题
locals {
merged_subnet_svc = try(flatten([
for service in var.service-names : [
for subnet in aws_subnet.ecom-private.*.id : {
service = service
subnet = subnet
}
]
]), {})
variable "azs" {
type = list(any)
default = ["ap-south-1a", "ap-south-1b", "ap-south-1c"]
}
variable "private-subnets" {
type = list(any)
default = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
}
resource "aws_instance" "ecom-instances" {
for_each = {
for svc in local.merged_subnet_svc : "${svc.service}-${svc.subnet}" => svc
}
ami = data.aws_ami.ecom.id
instance_type = "t3.micro"
tags = {
Name = "ecom-${each.value.service}-service"
}
vpc_security_group_ids = [aws_security_group.ecom-sg[each.value.service].id]
subnet_id = each.value.subnet
}
}
在您的配置中,您已声明 data "aws_instances" "ecom-instances"
依赖于 aws_instance.ecom-validation-service
。由于该其他对象在您的第一个 运行 上尚不存在,因此 Terraform 必须等到应用步骤才能读取 data.aws_instances.ecom-instances
,否则它将无法遵守您声明的依赖项,因为 aws_instance.ecom-validation-service
还不存在。
为避免您在此处看到的错误消息,您需要确保 for_each
仅引用 Terraform 在 实际创建任何对象之前 知道的值。因为 EC2 仅在实例创建后分配实例 ID,所以使用 EC2 实例 ID 作为 for_each
实例键的一部分是不正确的。
此外,这里不需要 data "aws_instances"
块来检索实例信息,因为作为 resource "aws_instance" "ecom-validation-service"
块的结果,您已经拥有相关的实例信息。
综上所述,让我们从您的输入变量开始并重新构建,同时确保我们只根据我们在规划期间知道的值构建实例键。您拥有的变量基本保持不变;我只是稍微调整了类型约束以匹配我们如何使用每个约束:
variable "instance_count" {
type = string
default = 3
}
variable "service_names" {
type = set(string)
default = ["valid", "jsc", "test"]
}
我从您示例的其余部分了解到您打算为 var.service_names
的每个不同元素创建 var.instance_count
个实例。您的 setproduct
生成所有这些组合也很好,但我将对其进行调整以分配包含服务名称的实例唯一键:
locals {
instance_configs = tomap({
for pair in setproduct(var.service_names, range(var.instance_count)) :
"${pair[0]}${pair[1]}" => {
service_name = pair[0]
}
})
}
这将产生如下数据结构:
{
valid0 = { service_name = "valid" }
valid1 = { service_name = "valid" }
valid2 = { service_name = "valid" }
jsc0 = { service_name = "jsc" }
jsc1 = { service_name = "jsc" }
jsc2 = { service_name = "jsc" }
test0 = { service_name = "test" }
test1 = { service_name = "test" }
test2 = { service_name = "test" }
}
这与 for_each
期望的形状匹配,因此我们可以直接使用它来声明九个 aws_instance
个实例:
resource "aws_instance" "ecom-validation-service" {
for_each = local.instance_configs
instance_type = "t3.micro"
ami = data.aws_ami.ecom.id
subnet_id = data.aws_subnet.ecom-subnet[each.value.service_name].id
vpc_security_group_ids = [
data.aws_security_group.ecom-sg[each.value.service_name].id,
]
tags = {
Name = "${each.value.service_name}-service"
Service = each.value_service_name
}
}
到目前为止,这与您分享的内容基本相同。但这是我要朝着完全不同的方向前进的一点:现在我不再尝试使用单独的数据资源回读声明的实例,而是直接从 aws_instance.ecom-validation-service
资源。 Terraform 配置通常最好 管理特定对象 或 读取它,而不是同时读取,因为这样必要的依赖性排序会自动显示为参考文献。
请注意,我在每个实例上添加了一个额外的标签 Service
,以提供一种更方便的方法来取回服务名称。如果你不能这样做,那么你可以通过从 Name
标签中删除 -service
后缀来获得相同的信息,但我更喜欢尽可能直接。
您当时的目标似乎是每个实例有一个 aws_lb_target_group_attachment
个实例,每个实例都根据服务名称连接到适当的目标组。因为 aws_instance
资源设置了 for_each
,所以其他地方的表达式中的 aws_instance.ecom-validation-service
是一个对象映射,其中的键与 var.instance_configs
中的键相同。这意味着该值 也 与 for_each
的要求兼容,因此我们可以直接使用它来声明目标组附件:
resource "aws_lb_target_group_attachment" "ecom-tga" {
for_each = aws_instance.ecom-validation-service
target_group_arn = aws_lb_target_group.ecom-nlb-tgp[each.value.tags.Service].arn
port = 80
target_id = each.value.id
}
我依靠之前的额外 Service
标记轻松确定每个实例属于哪个服务,以便查找适当的目标组 ARN。 each.value.id
在这里工作是因为 each.value
始终是一个 aws_instance
对象,它导出 id
属性。
结果是两组实例,每个实例都具有与 local.instance_configs
中的键匹配的键:
aws_instance.ecom-validation-service["valid0"]
aws_instance.ecom-validation-service["valid1"]
aws_instance.ecom-validation-service["valid2"]
aws_instance.ecom-validation-service["jsc0"]
aws_instance.ecom-validation-service["jsc1"]
aws_instance.ecom-validation-service["jsc2"]
- ...
aws_lb_target_group_attachment.ecom-tga["valid0"]
aws_lb_target_group_attachment.ecom-tga["valid1"]
aws_lb_target_group_attachment.ecom-tga["valid2"]
aws_lb_target_group_attachment.ecom-tga["jsc0"]
aws_lb_target_group_attachment.ecom-tga["jsc1"]
aws_lb_target_group_attachment.ecom-tga["jsc2"]
- ...
请注意,所有这些键仅包含在配置中直接指定的信息,而不包含远程系统决定的任何信息。这意味着我们避免了“无效的 for_each 参数”错误,即使每个实例仍然有一个适当的唯一键。如果您稍后要向 var.service_names
添加新元素或增加 var.instance_count
,那么 Terraform 还将从这些实例键的形状中看到它应该只添加每个资源的新实例,而不是 renaming/renumbering 任何现有实例。