Load Balancer


The presented resiliency recommendations in this guidance include Load Balancer and associated Load Balancer settings.

Summary of Recommendations

The below table shows the list of resiliency recommendations for Load Balancer and associated resources.

Recommendations Details

LB-1 - Use Standard Load Balancer SKU

Category: Availability

Impact: High

Guidance

Select Standard SKU Standard Load Balancer provides a dimension of reliability that Basic does not - that of availability zones and zone resiliency. This means when a zone goes down, your zone-redundant Standard Load Balancer will not be impacted. This ensures your deployments can withstand zone failures within a region. In addition, Standard Load Balancer supports global load balancing ensuring your application is not impacted by region failures either. Basic load balancers don’t have a Service Level Agreement (SLA).

Resources

Resource Graph Query

// Azure Resource Graph Query
// Find all LoadBalancers using Basic SKU
resources
| where type =~ 'Microsoft.Network/loadBalancers'
| where sku.name == 'Basic'
| project recommendationId = "lb-1", name, id, tags, Param1=strcat("sku-tier: basic")



LB-2 - Ensure the Backend Pool contains at least two instances

Category: Availability

Impact: High

Guidance

Deploy Azure LB with at least two instances in the backend. A single instance could result in a single point of failure. In order to build for scale, you might want to pair LB with Virtual Machine Scale Sets.

Resources

Resource Graph Query

// Azure Resource Graph Query
// Find all LoadBalancers which only have 1 backend pool defined or only 1 VM in the backend pool
resources
| where type =~ 'Microsoft.Network/loadBalancers'
| extend bep = properties.backendAddressPools
| extend BackEndPools = array_length(bep)
| where BackEndPools == 0
| project recommendationId = "lb-2", name, id, Param1=BackEndPools, Param2=0, tags
| union (resources
        | where type =~ 'Microsoft.Network/loadBalancers'
        | extend bep = properties.backendAddressPools
        | extend BackEndPools = array_length(bep)
        | mv-expand bip = properties.backendAddressPools
        | extend BackendAddresses = array_length(bip.properties.loadBalancerBackendAddresses)
        | where BackendAddresses <= 1
        | project recommendationId = "lb-2", name, id, tags, Param1=BackEndPools, Param2=BackendAddresses)



LB-3 - Use NAT Gateway instead of Outbound Rules for Production Workloads

Category: Availability

Impact: Medium

Guidance

Outbound rules for Standard Public Load Balancer requires you to manually allocate fixed amounts of ports to each of your backend pool instances. Because the SNAT port allocation is fixed, outbound rules does not provide the most scalable method for outbound connectivity. For production workloads, we recommend using NAT Gateway instead in order to prevent the risk of connection failures due to SNAT port exhaustion. NAT Gateway scales dynamically and provides secure connectivity to the internet.

Resources

Resource Graph Query

// Azure Resource Graph Query
// Find all LoadBalancers with Outbound rules configured
resources
| where type =~ 'Microsoft.Network/loadBalancers'
| extend outboundRules = array_length(properties.outboundRules)
| where outboundRules > 0
| project recommendationId = "lb-3", name, id, tags, Param1 = "outboundRules: >=1"



LB-4 - Ensure Standard Load Balancer is zone-redundant

Category: Availability

Impact: High

Guidance

In a region with Availability Zones, a Standard Load Balancer can be made zone-redundant by assigning it with a zone-redundant frontend IP address. With a zone-redundant frontend IP, the load balancer will continue to distribute traffic even when one availability zone fails, as long as there are other healthy zones and corresponding healthy backend instances in these zones that can receive traffic.

Resources

Resource Graph Query

// Azure Resource Graph Query
// Find all LoadBalancers with with regional or zonal public IP Addresses
resources
| where type == "microsoft.network/loadbalancers"
| where tolower(sku.name) != 'basic'
| mv-expand feIPconfigs = properties.frontendIPConfigurations
| extend
    feConfigName = (feIPconfigs.name),
    PrivateSubnetId = toupper(feIPconfigs.properties.subnet.id),
    PrivateIPZones = feIPconfigs.zones,
    PIPid = toupper(feIPconfigs.properties.publicIPAddress.id),
    JoinID = toupper(id)
| where isnotempty(PrivateSubnetId)
| where isnull(PrivateIPZones) or array_length(PrivateIPZones) < 2
| project name, feConfigName, id
| union (resources
    | where type == "microsoft.network/loadbalancers"
    | where tolower(sku.name) != 'basic'
    | mv-expand feIPconfigs = properties.frontendIPConfigurations
    | extend
        feConfigName = (feIPconfigs.name),
        PIPid = toupper(feIPconfigs.properties.publicIPAddress.id),
        JoinID = toupper(id)
    | where isnotempty(PIPid)
    | join kind=innerunique (
        resources
        | where type == "microsoft.network/publicipaddresses"
        | where isnull(zones) or array_length(zones) < 2
        | extend
            LBid = toupper(substring(properties.ipConfiguration.id, 0, indexof(properties.ipConfiguration.id, '/frontendIPConfigurations'))),
            InnerID = toupper(id)
    ) on $left.PIPid == $right.InnerID)
| project recommendationId = "lb-4", name, id, tags, param1="Zones: No Zone or Zonal", param2=strcat("Frontend IP Configuration:", " ", feConfigName)