引用 IP 地址生成配置文件时的 Terraform 循环依赖问题

Terraform cyclic dependency issue when referencing IP addresses to generate config file

我正在尝试设置一个 AWS 环境,其中 VPC 中有 2 个 ec2 实例,这些实例被配置为 运行 一个需要包含另一个 ec2 的 IP 地址的配置文件的软件。为此,我在模板中创建配置文件,我是 运行 像这样启动 ec2:

data "template_file" "init_relay" {
  template = file("${path.module}/initRelay.tpl")
  vars = {
    port    = var.node_communication_port
    ip      = module.block-producing-node.private_ip[0]
    self_ip = module.relay-node.public_ip
  }
}

module "relay-node" {
  source                      = "terraform-aws-modules/ec2-instance/aws"
  name                        = "relay-node"
  ami                         = var.node_ami
  key_name                    = "aws-keys"
  user_data                   = data.template_file.init_relay.rendered
  instance_type               = var.instance_type
  subnet_id                   = module.vpc.public_subnets[0]
  vpc_security_group_ids      = [module.relay_node_sg.this_security_group_id]
  associate_public_ip_address = true
  monitoring                  = true
  root_block_device = [
    {
      volume_type = "gp2"
      volume_size = 35
    },
  ]
  tags = {
    Name        = "Relay Node"
    Environment = var.environment_tag
    Version     = var.pool_version
  }
}

data "template_file" "init_block_producer" {
  template = "${file("${path.module}/initBlockProducer.tpl")}"
  vars = {
    port = var.node_communication_port
    ip = module.relay-node.private_ip
    self_ip       = module.block-producing-node.private_ip
  }
}

module "block-producing-node" {
  source                      = "terraform-aws-modules/ec2-instance/aws"
  name                        = "block-producing-node"
  ami                         = var.node_ami
  key_name                    = "aws-keys"
  user_data                   = data.template_file.init_block_producer.rendered
  instance_type               = var.instance_type
  subnet_id                   = module.vpc.public_subnets[0]
  vpc_security_group_ids      = [module.block_producing_node_sg.this_security_group_id]
  associate_public_ip_address = true
  monitoring                  = true
  root_block_device = [
    {
      volume_type = "gp2"
      volume_size = 35
    },
  ]
  tags = {
    Name        = "Block Producing Node"
    Environment = var.environment_tag
    Version     = var.pool_version
  }
}

但这给了我一个循环依赖错误:

» terraform apply

Error: Cycle: module.relay-node.output.public_ip, module.block-producing-node.output.private_ip, data.template_file.init_relay, module.relay-node.var.user_data, module.relay-node.aws_instance.this, module.relay-node.output.private_ip, data.template_file.init_block_producer, module.block-producing-node.var.user_data, module.block-producing-node.aws_instance.this

对我来说,我收到此错误的原因是有道理的,因为要为一个 ec2 生成配置文件,另一个 ec2 已经存在并分配了一个 IP 地址。但我不知道该怎么做。

如何在模板文件中引用其他 EC2 的 IP 地址而不导致循环依赖问题?

您的模板依赖于您的模块,而您的模块又依赖于您的模板 - 这导致了循环。

ip  = module.block-producing-node.private_ip[0]

user_data = data.template_file.init_block_producer.rendered

一般而言,EC2实例的用户数据不能包含实例的任何IP地址,因为用户数据是作为启动实例的一部分提交的,实例启动后无法更改,并且IP 地址(除非您在启动时指定一个明确的地址)也在实例启动期间分配,作为创建隐含主 network interface.

的一部分

如果您只有一个实例并且它需要知道自己的 IP 地址,那么最简单的答案是安装在您的实例中的某些软件询问操作系统哪个 IP 地址已分配给主网络接口。作为使用 DHCP 配置接口的一部分,操作系统已经知道 IP 地址,因此无需通过用户数据也将其传递。

不过,一个更常见的问题是,当您有一组实例都需要 相互通信 时,例如形成某种集群,等等除了自己的 IP 地址之外,他们还需要其他人的 IP 地址。在这种情况下,大致有两种方法:

  • 安排 Terraform 在某处发布 IP 地址,以允许实例中的软件 运行 在实例启动后检索它们。

    例如,您可以使用 aws_ssm_parameter 在 AWS SSM Parameter Store 中发布列表,然后让实例中的软件从那里检索它,或者您可以将所有实例分配到一个 VPC 安全组中然后让您实例中的软件查询 VPC API 以枚举属于该安全组的所有网络接口的 IP 地址。

    此策略的所有变体都有一个问题,即您的实例中的软件可能会在 IP 地址数据可用或完成之前启动。因此,通常有必要定期轮询任何提供 IP 地址的数据源,以防出现新地址。另一方面,该功能也适用于 Terraform 不直接管理实例的自动缩放系统。

    这是ElasticSearch EC2 Discovery使用的技术,例如寻找属于特定安全组的网络接口,或者携带特定标签等

  • 在创建实例之前为您的实例保留 IP 地址,以便在创建实例之前知道这些地址。

    当我们创建一个 aws_instance 而没有说明网络接口时,EC2 系统会隐式创建一个主要网络接口,并从实例绑定到的任何子网中选择一个空闲 IP 地址。但是,您可以选择创建自己的网络接口,这些接口与它们所连接的实例分开管理,这两者都允许您在不创建实例的情况下保留私有 IP 地址 允许要从一个实例分离然后连接到另一个实例的特定网络接口,保留保留的 IP 地址。

    aws_network_interface 是用于创建独立管理的网络接口的 AWS 供应商资源类型。例如:

    resource "aws_network_interface" "example" {
      subnet_id = aws_subnet.example.id
    }    
    

    aws_network_interface资源类型有一个private_ips属性,其第一个元素相当于aws_instance上的private_ip属性,所以可以参考[=19] =] 获取创建时分配给网络接口的 IP 地址,即使它尚未附加到任何 EC2 实例。

    当您声明 aws_instance 时,您可以包含一个 network_interface 块来要求 EC2 附加预先存在的网络接口而不是创建一个新的:

    resource "aws_instance" "example" {
      # ...
    
      user_data = templatefile("${path.module}/user_data.tmpl", {
        private_ip = aws_network_interface.example.private_ips[0]
      })
    
      network_interface {
        device_index         = 0 # primary interface
        network_interface_id = aws_network_interface.example.id
      }
    }
    

    由于网络接口现在是一个单独的资源,您可以将其属性用作实例配置的一部分。我在上面只展示了一个网络接口和一个实例,以便专注于所述问题,但您也可以在两个资源上使用资源 for_eachcount 来创建一组实例,然后使用 aws_network_interface.example[*].private_ips[0] 所有 IP 地址传递到您的 user_data 模板中。

    这种方法的一个警告是因为网络接口和实例是分开的,未来的更改很可能会导致实例被替换而不同时替换其关联的网络界面。这意味着新实例将被分配与已经是集群成员的旧实例相同的 IP 地址,这可能会使使用 IP 地址唯一标识集群成员的系统感到困惑。这是否重要以及您需要做什么来适应它取决于您使用什么软件来形成集群。

    这种方法也不太适合与自动缩放系统一起使用,因为它需要分配的 IP 地址数量根据当前实例数量增减,并让现有实例以某种方式感知当另一个实例加入或离开集群时。