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

引用IP地址以生成配置文件时Terraform循环依赖关系问题,terraform,terraform-provider-aws,cyclic-reference,terraform-template-file,Terraform,Terraform Provider Aws,Cyclic Reference,Terraform Template File,我正在尝试在VPC中设置一个AWS环境,其中包含2个ec2实例,这些实例被配置为运行一个软件,该软件需要一个包含另一个ec2 IP地址的配置文件。为此,我在一个模板中创建配置文件,运行该模板以启动ec2,如下所示: data "template_file" "init_relay" { template = file("${path.module}/initRelay.tpl") vars = { port = v

我正在尝试在VPC中设置一个AWS环境,其中包含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]


一般来说,EC2实例的用户数据不能包含实例的任何IP地址,因为用户数据是作为实例启动的一部分提交的,在实例启动后不能更改,并且在实例启动期间也会分配IP地址(除非在启动时指定显式IP地址),作为创建隐含主体的一部分

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

然而,一个更常见的问题是,当您有一组实例需要相互通信时,例如形成某种集群,因此它们除了需要自己的IP地址外,还需要同伴的IP地址。在这种情况下,大体上有两种方法:

  • 安排Terraform将IP地址发布到某个地方,以便在实例启动后,在实例中运行的软件能够检索这些IP地址

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

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

    例如,查找属于特定安全组的网络接口或携带特定标签等所使用的技术

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

    当我们创建一个
    aws_实例时
    没有说明任何关于网络接口的内容,EC2系统会隐式地创建一个主网络接口,并从实例绑定到的任何子网中选择一个空闲IP地址。但是,您可以选择创建自己的网络接口,这些接口与它们所连接的实例分开管理,这既允许您保留专用IP地址,而无需创建实例,也允许将特定网络接口从一个实例分离,然后连接到另一个实例,保留保留的IP地址

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

    resource "aws_network_interface" "example" {
      subnet_id = aws_subnet.example.id
    }    
    
    aws_网络_接口
    资源类型具有一个
    private_ip
    属性,其第一个元素与
    aws_实例
    上的
    private_ip
    属性相等,因此您可以参考
    aws_网络_接口。示例.private_ips[0]
    获取创建网络接口时分配给它的IP地址,即使它尚未连接到任何EC2实例

    当您声明
    aws_实例
    时,您可以包括
    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
      }
    }
    
    由于网络接口现在是一个单独的资源,因此可以将其属性用作实例配置的一部分。我在上面只展示了一个网络接口和一个实例,以便关注上面所述的问题,但您也可以对两个资源使用resource
    for_each
    count
    来创建一组实例,然后使用aws_网络接口。示例[*]。private_ips[0]将所有IP地址传递到
    用户数据中

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

    这种方法实际上也不适合用于自动缩放系统,因为它需要分配的IP地址数量根据当前实例的数量增长和收缩,并且对于现有的
    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
      }
    }