Terraform 如何获取 aws_lb 的 IP 地址

Terraform how to get IP address of aws_lb

问题

是否有办法在 Terraform 创建 aws_lb 时获取 aws_lb 资源的分配 IP 地址?

AWS documentation - NLB - To find the private IP addresses to whitelist一样,我们可以找出ELB关联的IP地址。

  1. https://console.aws.amazon.com/ec2/ 打开 Amazon EC2 控制台。
  2. 在导航窗格中,选择网络接口。
  3. 在搜索字段中,输入您的网络负载均衡器的名称。 每个负载均衡器子网有一个网络接口。
  4. 在每个网络接口的详细信息选项卡上,从 主要私有 IPv4 IP.

背景

能够设置安全组以将 ELB IP 地址列入白名单,因为网络负载均衡器不能像 Network Load Balancers don't have Security Groups 中那样具有安全组。

已考虑 aws_network_interface 但它无法正常工作并出现错误。

Error: no matching network interface found

另外我认为数据源假设资源已经存在并且不能用于 Terraform 创建的资源。

使用外部供应商

使用 Python/boto3 从外部提供商调用获取 NLB IP。

nlb_private_ips.tf

variable "nlb_name" {
}
variable "vpc_id" {
}
variable "region" {
}

data "external" "get_nlb_ips" {
  program = ["python", "${path.module}/get_nlb_private_ips.py"]
  query = {
    aws_nlb_name  = "${var.nlb_name}"
    aws_vpc_id    = "${var.vpc_id}"
    aws_region    = "${var.region}"
  }
}

output "aws_nlb_ip_decoded" {
  value = "${jsondecode(data.external.get_nlb_ips.result.private_ips)}"
}

output "aws_nlb_ip_encoded" {
  value = "${data.external.get_nlb_ips.result.private_ips}"
}

get_nlb_private_ips.py

import boto3
import json
import sys


def json_serial(obj):
    """JSON serializer for objects not serializable by default json code
        Args:
            obj: object to serialize into JSON
    """
    _serialize = {
        "int": lambda o: int(o),
        "float": lambda o: float(o),
        "decimal": lambda o: float(o) if o % 1 > 0 else int(o),
        "date": lambda o: o.isoformat(),
        "datetime": lambda o: o.isoformat(),
        "str": lambda o: o,
    }
    return _serialize[type(obj).__name__.lower()](obj)


def pretty_json(dict):
    """
    Pretty print Python dictionary
    Args:
        dict: Python dictionary
    Returns:
        Pretty JSON
    """
    return json.dumps(dict, indent=2, default=json_serial, sort_keys=True, )


def get_nlb_private_ips(data):
    ec2 = boto3.client('ec2', region_name=data['aws_region'])
    response = ec2.describe_network_interfaces(
        Filters=[
            {
                'Name': 'description',
                'Values': [
                    "ELB net/{AWS_NLB_NAME}/*".format(
                        AWS_NLB_NAME=data['aws_nlb_name'])
                ]
            },
            {
                'Name': 'vpc-id',
                'Values': [
                    data['aws_vpc_id']
                ]
            },
            {
                'Name': 'status',
                'Values': [
                    "in-use"
                ]
            },
            {
                'Name': 'attachment.status',
                'Values': [
                    "attached"
                ]
            }
        ]
    )

    # print(pretty_json(response))
    interfaces = response['NetworkInterfaces']

    # ifs = list(map(lamba index: interfaces[index]['PrivateIpAddresses'], xrange(len(interfaces))))
    # --------------------------------------------------------------------------------
    # Private IP addresses associated to an interface (ENI)
    # Each association has the format:
    #   {
    #     "Association": {
    #       "IpOwnerId": "693054447076",
    #       "PublicDnsName": "ec2-52-88-47-177.us-west-2.compute.amazonaws.com",
    #       "PublicIp": "52.88.47.177"
    #     },
    #     "Primary": true,
    #     "PrivateDnsName": "ip-10-5-1-205.us-west-2.compute.internal",
    #     "PrivateIpAddress": "10.5.1.205"
    #   },
    # --------------------------------------------------------------------------------
    associations = [
        association for interface in interfaces
        for association in interface['PrivateIpAddresses']
    ]

    # --------------------------------------------------------------------------------
    # Get IP from each IP association
    # --------------------------------------------------------------------------------
    private_ips = [
        association['PrivateIpAddress'] for association in associations
    ]

    return private_ips


def load_json():
    data = json.load(sys.stdin)
    return data


def main():
    data = load_json()
    """
    print(data['aws_region'])
    print(data['aws_vpc_id'])
    print(data['aws_nlb_name'])
    """
    ips = get_nlb_private_ips(data)
    print(json.dumps({"private_ips": json.dumps(ips)}))


if __name__ == '__main__':
    main()

使用 aws_network_interfaces 数据源

创建 aws_lb 之后。

data "aws_network_interfaces" "this" {
  filter {
    name = "description"
    values = ["ELB net/${aws_lb.this.name}/*"]
  }
  filter {
    name = "vpc-id"
    values = ["${var.vpc_id}"]
  }
  filter {
    name = "status"
    values = ["in-use"]
  }
  filter {
    name = "attachment.status"
    values = ["attached"]
  }
}

locals {
  nlb_interface_ids = "${flatten(["${data.aws_network_interfaces.this.ids}"])}"
}

data "aws_network_interface" "ifs" {
  count = "${length(local.nlb_interface_ids)}"
  id = "${local.nlb_interface_ids[count.index]}"
}

output "aws_lb_network_interface_ips" {
  value = "${flatten([data.aws_network_interface.ifs.*.private_ips])}"
}

在 Terraform 中仅使用 HCL 的更优雅的解决方案:

data "aws_network_interface" "lb" {
  for_each = var.subnets

  filter {
    name   = "description"
    values = ["ELB ${aws_lb.example_lb.arn_suffix}"]
  }

  filter {
    name   = "subnet-id"
    values = [each.value]
  }
}

resource "aws_security_group" "lb_sg" {
  vpc_id = var.vpc_id

  ingress {
    from_port   = 0
    to_port     = 0
    protocol    = "tcp"
    cidr_blocks = formatlist("%s/32", [for eni in data.aws_network_interface.lb : eni.private_ip])
    description = "Allow connection from NLB"
  }
}

来源:https://github.com/terraform-providers/terraform-provider-aws/issues/3007

希望对您有所帮助。

来自@user1297406 的解决方案导致异常。 data.aws_network_interface.lb is tuple with 2 elements. 正确的语法是:

data "aws_network_interface" "lb" {
count = length(var.vpc_private_subnets)

  filter {
    name   = "description"
    values = ["ELB ${aws_alb.lb.arn_suffix}"]
  }
  filter {
    name   = "subnet-id"
    values = [var.vpc_private_subnets[count.index]]
  }
}


resource "aws_security_group_rule" "lb_sg" {
  from_port         = 0
  protocol          = "TCP"
  to_port           = 0
  type              = "ingress"
  cidr_blocks = formatlist("%s/32", data.aws_network_interface.lb.*.private_ip)
}