Terraform 如何修复地形不可预测的实例创建问题?

Terraform 如何修复地形不可预测的实例创建问题?,terraform,terraform-provider-aws,Terraform,Terraform Provider Aws,运行terraform plan和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

运行terraform plan和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个实例。但它似乎没有这样做,并且抛出了无法预测的错误

我们是否必须通过为实例计数预测或本地服务实例映射提供一些默认值来绕过这个问题

尝试了函数,但仍然没有成功

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个实例。我改变了本地值,如下所示,但预测问题相同

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
}

}

在您的配置中,您已声明
数据“aws\U实例”“ecom实例”
取决于
aws\U实例。ecom验证服务
。由于在第一次运行时还不存在另一个对象,Terraform因此必须等到应用步骤读取
数据.aws_instances.ecom instances
,否则它将无法遵守您声明的依赖关系,因为
aws_instance.ecom验证服务
还不存在

为了避免您在这里看到的错误消息,您需要确保
for_each
只引用Terraform在实际创建任何对象之前会知道的值。由于EC2仅在创建实例后才分配实例id,因此将EC2实例id用作每个实例键的
的一部分是不正确的

此外,这里不需要
数据“aws\U实例”
块来检索实例信息,因为
资源“aws\U实例”“ecom验证服务”
块已经提供了相关实例信息

说到这里,让我们从您的输入变量开始重新构建,同时确保我们只根据我们在规划期间知道的值构建实例键。你拥有的变量基本上保持不变;我刚刚稍微调整了类型约束,以匹配我们使用每个约束的方式:

variable "instance_count" {
  type    = string
  default = 3
}
variable "service_names" {
  type    = set(string)
  default = ["valid", "jsc", "test"]
}
我从您的示例的其余部分了解到,您打算为
var.service\u名称的每个不同元素创建
var.instance\u count
实例。您可以生成所有这些组合,这也很好,但我将对其进行调整,以分配包含服务名称的实例唯一键:

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" }
}
这与
为每个
所期望的形状相匹配,因此我们可以直接使用它来声明九个
aws\u实例
实例:

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验证服务
资源中收集相同的数据。通常情况下,地形配置最好要么管理特定对象,要么读取特定对象,而不是同时读取这两个对象,因为通过这种方式,所需的依赖关系顺序将自动显示为引用

请注意,我在每个实例上都添加了一个额外的标记
Service
,以提供一种更方便的方法来获取服务名称。如果你不能做到这一点,那么你可以通过从
Name
标签中删除
-service
后缀来获得相同的信息,但我更喜欢尽可能直接地进行操作

您当时的目标似乎是为每个实例创建一个
aws\u lb\u target\u group\u附件
实例,每个实例都根据服务名称连接到相应的目标组。因为
aws\u实例
资源为每个
集合设置了
,所以其他表达式中的
aws\u实例.ecom验证服务
是一个对象映射,其中键与
var.instance\u配置中的键相同
。这意味着该值也与每个
要求兼容,因此我们可以直接使用它来声明目标组附件:

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
}
我依靠前面的extra
Service
标记轻松确定每个实例属于哪个服务,以便查找适当的目标组ARN
each.value.id
在这里工作,因为
each.value
始终是一个
aws\u实例
对象,它导出该
id
属性

其结果是两组实例,每个实例的密钥与
本地中的密钥相匹配。实例\u配置

  • aws\u实例.ecom验证服务[“valid0”]
  • aws\u实例.ecom验证服务[“valid1”]
  • aws\u实例.ecom验证服务[“valid2”]
  • aws\u实例.ecom验证服务[“jsc0”]
  • aws\u实例.ecom验证服务[“jsc1”]
  • aws\u实例.ecom验证服务[“jsc2”]
  • aws_lb_目标_集团_附件。ecom tga[“valid0”]
  • aws_lb_目标_集团_附件。ecom tga[“valid1”]
  • aws_lb_目标_集团_附件。ecom tga[“valid2”]
  • aws\u lb\u target\u group\u attachment.ecom tga[“jsc0”]
  • aws\u lb\u target\u group\u attachment.ecom tga[“jsc1”]
  • aws_lb_目标_集团_附件。ecom tga[“jsc2”]
请注意,所有这些键只包含配置中直接指定的信息,而不包含远程系统决定的任何信息。这意味着我们避免了“Invalid for_each argument”错误,即使每个实例仍然有一个适当的唯一键。如果您稍后要将一个新元素添加到
var.service\u name
或增加
var.instance\u count
,则Terraform也将从这些实例的形状中看到