如何解决 terraform 不可预测的实例创建问题? [英] how to fix terraform unpredict instance creation issue?
问题描述
我在运行 terraform plan 和 apply 时遇到以下错误
I'm getting the below error while running terraform plan and apply
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 个实例.但它似乎没有,并且抛出错误无法预测.
Since i'm passing count as var and its value is 3,i thought terraform will predict as it needs to create 9 instances.But it didn't it seems and throwing error as unable to predict.
无论如何,我们是否必须通过为实例计数预测或本地 service_instance_map 提供一些默认值来绕过它?
Do we have anyway to by pass this by giving some default values for instances count prediction or for that local service_instance_map?
尝试过尝试功能,但仍然没有运气
Tried try function but still no luck
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 个实例.我像下面那样更改了本地人但同样的预测问题
My requirement got changed and now i have to create 3 instances in 3 subnets available in that region.I changed the locals as like below But same prediction issue
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
尚不存在.
In your configuration you've declared that data "aws_instances" "ecom-instances"
depends on aws_instance.ecom-validation-service
. Since that other object won't exist yet on your first run, Terraform must therefore wait until the apply step to read data.aws_instances.ecom-instances
because otherwise it would fail to honor the dependency you've declared, because aws_instance.ecom-validation-service
wouldn't exist yet.
为了避免您在此处看到的错误消息,您需要确保 for_each
仅引用 Terraform 在实际创建任何对象之前 知道的值.由于 EC2 仅在创建实例后分配实例 ID,因此将 EC2 实例 ID 用作 for_each
实例键的一部分是不正确的.
To avoid the error message you saw here, you need to make sure that for_each
only refers to values that Terraform will know before any objects are actually created. Because EC2 assigns instance ids only once the instance is created, it's not correct to use an EC2 instance id as part of a for_each
instance key.
此外,这里不需要 data aws_instances"
块来检索实例信息,因为作为 资源aws_instance"的结果,您已经拥有相关的实例信息.ecom-validation-service"
块.
Furthermore, there's no need for a data "aws_instances"
block to retrieve instance information here because you already have the relevant instance information as a result of the resource "aws_instance" "ecom-validation-service"
block.
综上所述,让我们从您的输入变量开始并重新构建,同时确保我们仅根据我们在计划期间知道的值构建实例键.您拥有的变量基本保持不变;我只是稍微调整了类型约束以匹配我们使用每个约束的方式:
With all of that said, let's start from your input variables and build things up again while making sure that we only build instance keys only from values we'll know during planning. The variables you have stay essentially the same; I've just tweaked the type constraints a little to match how we're using each one:
variable "instance_count" {
type = string
default = 3
}
variable "service_names" {
type = set(string)
default = ["valid", "jsc", "test"]
}
我从您的示例的其余部分了解到您打算为 var.service_names
的每个不同元素创建 var.instance_count
实例.您的 setproduct
用于生成所有这些组合也很好,但我将调整它以分配包含服务名称的实例唯一键:
I understand from the rest of your example that you are intending to create var.instance_count
instances for each distinct element of var.service_names
. Your setproduct
to produce all of the combinations of those is also good, but I'm going to tweak it to assign the instances unique keys that include the service name:
locals {
instance_configs = tomap({
for pair in setproduct(var.service_names, range(var.instance_count)) :
"${pair[0]}${pair[1]}" => {
service_name = pair[0]
}
})
}
这将产生如下数据结构:
This will produce a data structure like the following:
{
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
实例:
This matches the shape that for_each
expects, so we can use it directly to declare nine aws_instance
instances:
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 配置最好管理特定对象或读取它,而不是同时读取它,因为这样会自动显示必要的依赖关系排序参考资料.
So far this has been mostly the same as what you shared. But this is the point where I'm going to go in a totally different direction: rather than now trying to read back the instances this declared using a separate data resource, I'll just gather the same data directly from the aws_instance.ecom-validation-service
resource. It's generally best for a Terraform configuration to either manage a particular object or read it, not both at the same time, because this way the necessary dependency ordering is revealed automatically be the references.
请注意,我在每个实例上都包含了一个额外的标记 Service
以提供一种更方便的方法来获取服务名称.如果您不能这样做,那么您可以通过从 Name
标记中修剪 -service
后缀来获得相同的信息,但我更喜欢尽可能保持直接的内容.
Notice that I included an extra tag Service
on each of the instances to give a more convenient way to get the service name back. If you can't do that then you could get the same information by trimming the -service
suffix from the Name
tag, but I prefer to keep things direct where possible.
您当时的目标似乎是为每个实例创建一个 aws_lb_target_group_attachment
实例,每个实例都根据服务名称连接到相应的目标组.因为 aws_instance
资源设置了 for_each
,aws_instance.ecom-validation-service
在其他地方的表达式中是键相同的对象映射作为 var.instance_configs
中的键.这意味着该值也符合 for_each
的要求,因此我们可以直接使用它来声明目标组附件:
It seemed like your goal then was to have a aws_lb_target_group_attachment
instance per instance, with each one connected to the appropriate target group based on the service name. Because that aws_instance
resource has for_each
set, aws_instance.ecom-validation-service
in expressions elsewhere is a map of objects where the keys are the same as the keys in var.instance_configs
. That means that value is also compatible with the requirements for for_each
and so we can use it directly to declare the target group attachments:
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
属性.
I relied on the extra Service
tag from earlier to easily determine which service each instance belongs to in order to look up the appropriate target group ARN. each.value.id
works here because each.value
is always an aws_instance
object, which exports that id
attribute.
这样做的结果是两组实例,每个实例的键都与 local.instance_configs
中的键匹配:
The result of this is two sets of instances that each have keys matching those in 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"]
- ...
请注意,所有这些键都只包含在配置中直接指定的信息,而不是由远程系统决定的任何信息.这意味着我们避免了Invalid for_each 参数".即使每个实例仍然有一个合适的唯一键,也会出错.如果您稍后将新元素添加到 var.service_names
或增加 var.instance_count
然后 Terraform 还将从这些实例键的形状中看到它应该添加新元素每个资源的实例,而不是重命名/重新编号任何现有实例.
Notice that all of these keys contain only information specified directly in the configuration, and not any information decided by the remote system. That means we avoid the "Invalid for_each argument" error even though each instance still has an appropriate unique key. If you were to add a new element to var.service_names
or increase var.instance_count
later then Terraform will also see from the shape of these instance keys that it should just add new instances of each resource, rather than renaming/renumbering any existing instances.
这篇关于如何解决 terraform 不可预测的实例创建问题?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!