1

我正在尝试在 VPC 中设置具有 2 个 ec2 实例的 AWS 环境,这些实例配置为运行需要包含另一个 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 地址?

4

2 回答 2

2

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

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

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

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

    例如,您可以使用 AWS SSM Parameter Store 发布列表,aws_ssm_parameter然后让实例中的软件从那里检索它,或者您可以将所有实例分配到 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一个元素等同private_ip于 an 上的属性aws_instance,因此您可以参考以aws_network_interface.example.private_ips[0]获取在创建网络接口时分配给网络接口的 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 地址的数量根据当前的实例数量来增长和缩小,并且现有实例在另一个实例加入或加入时以某种方式意识到离开集群。

于 2020-07-15T17:55:39.837 回答
0

您的模板取决于您的模块和模板上的模块 - 这导致了循环。

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

user_data = data.template_file.init_block_producer.rendered
于 2020-07-15T03:43:44.380 回答