如何解决 terraform 不可预测的实例创建问题? [英] how to fix terraform unpredict instance creation issue?

查看:42
本文介绍了如何解决 terraform 不可预测的实例创建问题?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我在运行 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_eachaws_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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆