AWS:如何使用 CloudFormation 创建网络负载均衡器目标组?

AWS: how do I create a Network Load Balancer Target Group using CloudFormation?

我们正在使用 CloudFormation 创建网络负载平衡器。目标类型为IP,需要指向VPC端点的IP地址。

因此我们需要创建一个“ip”类型的目标组,并指定一个目标列表:

  NetworkLoadBalancerTargetGroup:
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    Properties:
      Name: !Sub '${AWS::StackName}-NLT'
      Port: 443
      Protocol: TLS
      VpcId: !Ref 'VPC'
      TargetGroupAttributes:
        - Key: deregistration_delay.timeout_seconds
          Value: 300
      TargetType: ip
      Targets: # list of the primary IP addresses of the Network interface(s) associated with the VPC endpoint
        - ?????

目标需要是 VPC 端点的 IP 地址。我如何引用这些?

VPC 端点是这样创建的:

  VpcEndpoint:
    Type: AWS::EC2::VPCEndpoint
    Properties:
      VpcEndpointType: Interface
      SubnetIds:
      - !Ref ProtectedSubnetA
      - !If [IsProd, !Ref ProtectedSubnetB, !Ref 'AWS::NoValue']
      - !If [IsProd, !Ref ProtectedSubnetC, !Ref 'AWS::NoValue']
      SecurityGroupIds:
      - !Ref SecurityGroupHttpsInInternal
      - !Ref SecurityGroupHttpsOutInternal
      PrivateDnsEnabled: true
      ServiceName: !Sub com.amazonaws.${AWS::Region}.execute-api
      VpcId: !Ref VPC
      PolicyDocument: '{
          "Statement": [
            {
              "Action": "*",
              "Effect": "Allow",
              "Resource": "*",
              "Principal": "*"
            }
          ]
        }'

我可以获得这样的网络接口 ID 列表: !GetAtt VpcEndpoint.NetworkInterfaceIds

但是,这是一个字符串列表。如何获取 ID 列表中每个网络接口的 PrimaryPrivateIpAddress 属性?

为了完整起见,这里是网络负载均衡器和关联侦听器的定义:

  NetworkLoadBalancer:
    Type: AWS::ElasticLoadBalancingV2::LoadBalancer
    Properties:
      LoadBalancerAttributes:
        - Key: load_balancing.cross_zone.enabled
          Value: true
      Name: !Sub '${AWS::StackName}-NLB-Protected'
      Scheme: internal
      Subnets:
        - !Ref ProtectedSubnetA
        - !If [IsProd, !Ref ProtectedSubnetB, !Ref 'AWS::NoValue']
        - !If [IsProd, !Ref ProtectedSubnetC, !Ref 'AWS::NoValue']
      Type: network

  NetworkLoadBalancerListener:
    Type: AWS::ElasticLoadBalancingV2::Listener
    Properties:
      DefaultActions:
      - Type: forward
        TargetGroupArn: !Ref NetworkLoadBalancerTargetGroup
      LoadBalancerArn: !Ref NetworkLoadBalancer
      Port: '443'
      Protocol: TLS
      SslPolicy: ELBSecurityPolicy-TLS-1-2-Ext-2018-06
      Certificates:
        - CertificateArn: !Ref ACMCertificate

However, that's a list of strings. How do I get the PrimaryPrivateIpAddress attribute for each network interface in the list of ids?

如您所述,AWS::EC2::VPCEndpoint returns NetworkInterfaceIds 而不是 IP 地址。因此,要获得实际的 IP 地址,您必须开发 custom resource.

这将采用 lambda 函数 的形式,您将向其传递 ENI ID。该函数将使用 AWS SDK(例如 boto3)来获取相应的 IP 地址。该函数将 return CFN 的 IP 地址,您将在目标组中使用该地址。

我发现您 post 正在寻找完全相同问题的解决方案。让我分享我的结论

一旦GetAtt我们无法访问IP列表,那么我们需要编写一个CustomResource来获取它们。

正如 AWS 在此建议的那样 link 我实施了一个 CustomResource 来获取这些 IP。 还有一个相关的 issue.

首先我们需要将 IAM 角色设置为 lambda。请注意,如果没有这些角色,我们将无法在 CloudWatch

上查看日志
LambdaRole:
Type: AWS::IAM::Role
Properties:
  AssumeRolePolicyDocument:
    Version: '2012-10-17'
    Statement:
      - Effect: Allow
        Principal:
          Service:
            - lambda.amazonaws.com
        Action:
          - sts:AssumeRole
LambdaPolicy:
    Type: AWS::IAM::Policy
    Properties:
      PolicyName: LambdaPolicy
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Action:
              - ec2:*
              - logs:*
            Resource: '*'
      Roles:
        - !Ref LambdaRole

并创建将接收 NetworkId 列表并搜索 NetworkInterface IP 的 lambda 函数:

LambdaFunction:
    Type: AWS::Lambda::Function
    Condition: IsNotProd
    DeletionPolicy: 'Delete'
    Properties:
      Code:
        ZipFile: !Sub |
          import cfnresponse
          import json
          import boto3
          def lambda_handler(event, context):
              print('REQUEST RECEIVED:\n' + json.dumps(event))
              responseData = {}
              if event['RequestType'] == 'Delete':
                cfnresponse.send(event, context, cfnresponse.SUCCESS, {})
                return
              if event['RequestType'] == 'Create':
                try:
                  ec2 = boto3.resource('ec2')
                  enis = event['ResourceProperties']['NetworkInterfaceIds']
                  for index, eni in enumerate(enis):
                    network_interface = ec2.NetworkInterface(eni)
                    responseData['IP' + str(index)] = network_interface.private_ip_address
                    print(responseData)
                except Exception as e:
                  responseData = {'error': str(e)}
                  cfnresponse.send(event, context, cfnresponse.FAILED, responseData)
                  return
                cfnresponse.send(event, context, cfnresponse.SUCCESS, responseData)
      Handler: index.lambda_handler
      Role: !GetAtt LambdaRole.Arn
      Runtime: python3.7
      Timeout: 10

然后创建自定义资源

GetPrivateIPs:
    DependsOn:
      - VPCEndpoint
    Type: AWS::CloudFormation::CustomResource
    Condition: IsNotProd
    Properties:
      ServiceToken: !GetAtt LambdaFunction.Arn
      NetworkInterfaceIds: !GetAtt VPCEndpoint.NetworkInterfaceIds

您终于可以访问这些 IP

Targets:
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    Properties:
      HealthCheckEnabled: true
      HealthCheckIntervalSeconds: 10
      HealthCheckPort: 443
      HealthCheckProtocol: TCP
      HealthCheckTimeoutSeconds: 10
      HealthyThresholdCount: 3
      UnhealthyThresholdCount: 3
      Port: 443
      Protocol: TLS
      Targets:
        - Id: !GetAtt GetPrivateIPs.IP0
          Port: 443
        - Id: !GetAtt GetPrivateIPs.IP1
          Port: 443
      TargetType: ip
      VpcId: !Ref VPC