每个 AZ 中没有 Target 的 AWS 网络负载均衡器

AWS Network Load Balancer without Target in every AZ

在 AWS 网络负载均衡器文档中,它说当为目标组指定实例时,它必须在负载均衡器注册的每个 AZ 中包含一个实例。这不是强制执行的。

如果您在 3 个 AZ 中注册了一个 NLB,但在 AZ1 中只有一个目标 EC2 实例,流量会怎样?如果您启用跨 AZ 负载平衡,会有什么不同吗?

What happens to traffic if you have an NLB registered in 3 AZs, but only a single target EC2 instance in AZ1? What if you enable cross AZ load balancing, does that make any difference?

在此特定场景中(NLB 在 3 个可用区中,单个实例在 1 个可用区中),什么都没有发生。从 end-user 的角度来看,有或没有 cross-zone 负载平衡没有明显的区别。在任何一种情况下都可以访问该实例。

为了验证这一点,我开发了一个简单的 CloudFormation 模板,它创建了 NLB,有或没有 cross-zone 负载平衡,以及 1 个实例。该模板允许使用不同的 NLB、cross-zone 和实例位置进行 简单实验 。我在 us-east-1 区域和默认 VPC 中使用了模板。

您为模板指定了几个参数,包括:

  • NLBSubnetsIds - 启用 NLB 的子网。您必须先在控制台中检查,哪些子网在哪些可用区中。

  • InstanceSubnetId - 实例的子网。如果您想使用实例位置,您可以再次检查哪个子网在哪个 AZ 中。您必须确保在为您的 NLB 设置的可用区之一中创建该实例。

  • CrossZoneEnabled - 为 NLB 启用或禁用 [​​=65=] 平衡。

从模板创建堆栈并通过实例健康检查(可能需要 1 或 2 分钟)后,您可以在浏览器中访问 NLB DNS 以查看托管的示例网页 在实例上。

---

Parameters:

  VpcId:
    Type: AWS::EC2::VPC::Id
    
  NLBSubnetsIds:
    Type: List<AWS::EC2::Subnet::Id>
    
  InstanceSubnetId:
    Type: AWS::EC2::Subnet::Id 
    
  AmazonLinux2AMIId:
    Type: AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>
    Default: /aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2
    
  CrossZoneEnabled:
    Type: String  
    Default: false
    AllowedValues: [true, false]

Resources:


  BasicSecurityGroup:                                                        
      Type: AWS::EC2::SecurityGroup                                          
      Properties: 
        GroupDescription: Enable www port
        SecurityGroupIngress:
          - IpProtocol: tcp
            FromPort: 80
            ToPort: 80
            CidrIp: 0.0.0.0/0            
        VpcId:  !Ref VpcId

  MyInstance1:
    Type: AWS::EC2::Instance

    CreationPolicy:
        ResourceSignal:
          Timeout: PT5M
                
    Properties:                
      ImageId: !Ref AmazonLinux2AMIId  
      InstanceType: t2.micro        
      Monitoring: false
      SecurityGroupIds: [!Ref BasicSecurityGroup]
      SubnetId: !Ref InstanceSubnetId
      UserData: 
        Fn::Base64: !Sub |
            #!/bin/bash -xe

            yum install -y httpd aws-cfn-bootstrap

            echo "<h2>Hello world from $(hostname -f)</h2>" \
              > /var/www/html/index.html

            systemctl start httpd

            # check if website is working
            curl -s localhost | grep "Hello"

            # Signal the status from cfn-init
            /opt/aws/bin/cfn-signal -e $? \
                --stack ${AWS::StackName} \
                --resource MyInstance1 \
                --region ${AWS::Region}
                
                
  MyNLB:
    Type: AWS::ElasticLoadBalancingV2::LoadBalancer
    Properties: 
      IpAddressType: ipv4
      LoadBalancerAttributes:  
        - Key: load_balancing.cross_zone.enabled
          Value: !Ref CrossZoneEnabled
      Scheme: internet-facing 
      Subnets: !Ref NLBSubnetsIds
      Type: network
      
  MyListner1:      
    Type: AWS::ElasticLoadBalancingV2::Listener
    Properties: 
      DefaultActions: 
        - TargetGroupArn: !Ref MyTargetGroup
          Type: forward 
      LoadBalancerArn: !Ref MyNLB
      Port: 80 
      Protocol: TCP 

  MyTargetGroup: 
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    Properties: 
      HealthCheckEnabled: true
      HealthCheckIntervalSeconds: 10
      HealthCheckPath: /
      HealthCheckProtocol: HTTP 
      HealthyThresholdCount: 2
      UnhealthyThresholdCount: 2
      Port: 80
      Protocol: TCP 
      TargetGroupAttributes: 
        - Key: deregistration_delay.timeout_seconds
          Value: 30
      Targets:
        - Id: !Ref MyInstance1
          Port: 80
      TargetType: instance 
      VpcId: !Ref VpcId
      
      
Outputs:
    
  DNSName:
    Value: !GetAtt MyNLB.DNSName

从 end-user 的角度来看,在您的场景中,在 NLB 中启用或禁用 [​​=65=] 之间没有明显区别。但是,long-term 差异可能在于 高可用性 。也就是说,如果您禁用了 cross-zone,并且如果实例所在 AZ 中的 NLB 节点出现问题,NLB 将无法将流量从其他 AZ 路由到您的实例。这是我的猜测,因为这不是您可以手动检查的内容。原因是,一旦您将 AZ/subnet 与您的 NLB 关联,就无法取消关联,以检查在这种情况下会发生什么。

相反,如果启用 cross-zone,在上述情况下,来自其他区域的 NLB 节点可能会将流量路由到跨区域的实例。

启用 cross-zone 流量的主要好处是当您 different number of instances 在不同的可用区时。在这种情况下,cross-zone 平衡使所有实例将获得大致相同的流量。如果没有 cross-zone 平衡,隔离实例将获得比其他 AZ 中的实例集合更多的流量。

您可以使用 第二个模板 检查 zone-balancing 的效果。模板和之前差不多,但是现在1个AZ有3个实例,而另一个有1个AZ。

---

Parameters:

  VpcId:
    Type: AWS::EC2::VPC::Id
    
  NLBSubnetsIds:
    Type: List<AWS::EC2::Subnet::Id>
    
  InstanceSubnetId1:
    Type: AWS::EC2::Subnet::Id 

  InstanceSubnetId2:
    Type: AWS::EC2::Subnet::Id     
    
  AmazonLinux2AMIId:
    Type: AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>
    Default: /aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2
    
  CrossZoneEnabled:
    Type: String  
    Default: false
    AllowedValues: [true, false]

Resources:


  BasicSecurityGroup:                                                        
      Type: AWS::EC2::SecurityGroup                                          
      Properties: 
        GroupDescription: Enable www port
        SecurityGroupIngress:
          - IpProtocol: tcp
            FromPort: 80
            ToPort: 80
            CidrIp: 0.0.0.0/0            
        VpcId:  !Ref VpcId

  MyInstance1:
    Type: AWS::EC2::Instance

    CreationPolicy:
        ResourceSignal:
          Timeout: PT3M
                
    Properties:                
      ImageId: !Ref AmazonLinux2AMIId  
      InstanceType: t2.micro        
      Monitoring: false
      SecurityGroupIds: [!Ref BasicSecurityGroup]
      SubnetId: !Ref InstanceSubnetId1
      UserData: 
        Fn::Base64: !Sub |
            #!/bin/bash -xe

            yum install -y httpd aws-cfn-bootstrap

            echo "<h2>Hello world from $(hostname -f)</h2>" \
              > /var/www/html/index.html

            systemctl start httpd

            # check if website is working
            curl -s localhost | grep "Hello"

            # Signal the status from cfn-init
            /opt/aws/bin/cfn-signal -e $? \
                --stack ${AWS::StackName} \
                --resource MyInstance1 \
                --region ${AWS::Region}
                

  MyInstance2:
    Type: AWS::EC2::Instance

    CreationPolicy:
        ResourceSignal:
          Timeout: PT3M
                
    Properties:                
      ImageId: !Ref AmazonLinux2AMIId  
      InstanceType: t2.micro        
      Monitoring: false
      SecurityGroupIds: [!Ref BasicSecurityGroup]
      SubnetId: !Ref InstanceSubnetId2
      UserData: 
        Fn::Base64: !Sub |
            #!/bin/bash -xe

            yum install -y httpd aws-cfn-bootstrap

            echo "<h2>Hello2 world from $(hostname -f)</h2>" \
              > /var/www/html/index.html

            systemctl start httpd

            # check if website is working
            curl -s localhost | grep "Hello"

            # Signal the status from cfn-init
            /opt/aws/bin/cfn-signal -e $? \
                --stack ${AWS::StackName} \
                --resource MyInstance2 \
                --region ${AWS::Region}


  MyInstance3:
    Type: AWS::EC2::Instance

    CreationPolicy:
        ResourceSignal:
          Timeout: PT3M
                
    Properties:                
      ImageId: !Ref AmazonLinux2AMIId  
      InstanceType: t2.micro        
      Monitoring: false
      SecurityGroupIds: [!Ref BasicSecurityGroup]
      SubnetId: !Ref InstanceSubnetId2
      UserData: 
        Fn::Base64: !Sub |
            #!/bin/bash -xe

            yum install -y httpd aws-cfn-bootstrap

            echo "<h2>Hello2 world from $(hostname -f)</h2>" \
              > /var/www/html/index.html

            systemctl start httpd

            # check if website is working
            curl -s localhost | grep "Hello"

            # Signal the status from cfn-init
            /opt/aws/bin/cfn-signal -e $? \
                --stack ${AWS::StackName} \
                --resource MyInstance3 \
                --region ${AWS::Region}


  MyInstance4:
    Type: AWS::EC2::Instance

    CreationPolicy:
        ResourceSignal:
          Timeout: PT3M
                
    Properties:                
      ImageId: !Ref AmazonLinux2AMIId  
      InstanceType: t2.micro        
      Monitoring: false
      SecurityGroupIds: [!Ref BasicSecurityGroup]
      SubnetId: !Ref InstanceSubnetId2
      UserData: 
        Fn::Base64: !Sub |
            #!/bin/bash -xe

            yum install -y httpd aws-cfn-bootstrap

            echo "<h2>Hello2 world from $(hostname -f)</h2>" \
              > /var/www/html/index.html

            systemctl start httpd

            # check if website is working
            curl -s localhost | grep "Hello"

            # Signal the status from cfn-init
            /opt/aws/bin/cfn-signal -e $? \
                --stack ${AWS::StackName} \
                --resource MyInstance4 \
                --region ${AWS::Region}                
                
  MyNLB:
    Type: AWS::ElasticLoadBalancingV2::LoadBalancer
    Properties: 
      IpAddressType: ipv4
      LoadBalancerAttributes:  
        - Key: load_balancing.cross_zone.enabled
          Value: !Ref CrossZoneEnabled
      Scheme: internet-facing 
      Subnets: !Ref NLBSubnetsIds
      Type: network
      
  MyListner1:      
    Type: AWS::ElasticLoadBalancingV2::Listener
    Properties: 
      DefaultActions: 
        - TargetGroupArn: !Ref MyTargetGroup
          Type: forward 
      LoadBalancerArn: !Ref MyNLB
      Port: 80 
      Protocol: TCP 

  MyTargetGroup: 
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    Properties: 
      HealthCheckEnabled: true
      HealthCheckIntervalSeconds: 10
      HealthCheckPath: /
      HealthCheckProtocol: HTTP 
      HealthyThresholdCount: 2
      UnhealthyThresholdCount: 2
      Port: 80
      Protocol: TCP 
      TargetGroupAttributes: 
        - Key: deregistration_delay.timeout_seconds
          Value: 30
      Targets:
        - Id: !Ref MyInstance1
          Port: 80
        - Id: !Ref MyInstance2
          Port: 80
        - Id: !Ref MyInstance3
          Port: 80
        - Id: !Ref MyInstance4
          Port: 80                              
      TargetType: instance 
      VpcId: !Ref VpcId
      
      
Outputs:
    
  DNSName:
    Value: !GetAtt MyNLB.DNSName

如果您使用上述模板,并重复请求 NLB url,您将看到隔离实例将获得大约 50% 的流量而没有 cross-zone 平衡。启用 cross-zone 平衡后,约为 20%。以下是我基于 100 个请求的结果:

您的方案不需要启用跨区域负载平衡。正如 Marcin 指出的那样,它对您没有任何作用。事实上,解析您的 NLB 的 DNS,您会看到它只有 returns 每个 AZ 的 A 记录,该 AZ 在 NLB 的所有目标组中聚合了一个健康的实例。 Marcin 的回应非常适合深入探讨。

有些人在这里说“是的,但我们仍然会超时。”。这是因为您的场景比 OP 更复杂。简而言之,您可能拥有一个包含多个目标组的 NLB,其中多个 AZ 中存在不同的目标。启用跨区域负载平衡将解决您的次优配置问题,但代价是账单上会产生额外的数据传输费用。 Duck tape 和 gum wrappers 在 AWS VPC 中工作。

更多信息:

NLB 对 DNS 很“聪明”,因为它们的 VIP 只会解析到具有健康目标的 A 记录(在及时的原因内)。如果一个 NLB 有几个目标组,这些目标组在三个 AZ 中具有不同的实例,您将获得三个返回的 A 记录(每个 AZ 都有一个健康目标)。这就是 NLB + RR DNS 的工作原理。

但是,如果您的目标组在单个 AZ 中包含 EC2 实例,则 DNS 循环法将解析正确 AZ 的可能性为 33%(给定三个 AZ)。

最好的解决办法是开启跨区负载均衡。这会增加数据传输成本,但与断开 NLB 的替代方案相比,它没有那么复杂。请注意,启用跨区域负载平衡需要几分钟才能生效。不要启用它,立即启动一个 telnet,当它不起作用时会很伤心。等待 5 - 10 分钟,然后启动您的 telnet。

资料来源:使用 AWS 和 janky EC2 解决方案的轶事和实践经验。