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

Posted

技术标签:

【中文标题】引用 IP 地址生成配置文件时的 Terraform 循环依赖问题【英文标题】:Terraform cyclic dependency issue when referencing IP addresses to generate config file 【发布时间】:2020-11-04 11:03:53 【问题描述】:

我正在尝试在 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 地址?

【问题讨论】:

【参考方案1】:

一般来说,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 属性,因此您可以参考aws_network_interface.example.private_ips[0] 来获取分配给创建时的网络接口,即使它尚未附加到任何 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 地址的数量根据当前实例的数量来增长和缩小,并且让现有的实例以某种方式意识到另一个实例实例加入或离开集群。

【讨论】:

【参考方案2】:

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

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

user_data = data.template_file.init_block_producer.rendered

【讨论】:

绝对可以,如果EC2还没有创建,如何引用模板中的IP地址呢? 您可以创建一个 EIP terraform.io/docs/providers/aws/r/eip.html 或创建一个网络接口 terraform.io/docs/providers/aws/r/network_interface.html 在您的模板中使用它并将其作为参数传递给 EC2。 terraform.io/docs/providers/aws/r/…. 请注意,EIP 是公共 IP 地址。您可能想要创建一个 ENI 并将其附加到一个私有 IP 地址。此外,这个答案并没有真正添加任何内容(OP 已经可以看到模块之间存在一个循环,并且正在寻找规避这种循环的方法),而最有用的是上面的评论。如果您将其扩展为包含您的评论和创建 ENI 并附加它的工作示例,将会大大改进。

以上是关于引用 IP 地址生成配置文件时的 Terraform 循环依赖问题的主要内容,如果未能解决你的问题,请参考以下文章

使用静态路由连通全网

linux中想在脚本中实现修改一个ip地址参数的配置文件,用sed命令如何实现?

有 Web 引用时的 .NET DLL 设置和配置 - 发生了啥?

Ubuntu下单网卡多IP地址的配置

Eclipse 生成的文件夹

mongodb安装到配置问题