Scenario 07 · Single Load Balancer

One Load Balancer. Thousands of routes.

Azure's Standard Load Balancer allows roughly 300 inbound rules per node NIC, and AKS puts every Service type=LoadBalancer on one Load Balancer — where each Service port costs about one rule. Gateway API implementations that provision a dedicated proxy and a LoadBalancer Service per Gateway burn through that budget fast: a Gateway per team or tenant, and a few hundred of them later, new ingress simply stops provisioning. The fix isn't a bigger limit — it's not minting an L4 frontend for every route in the first place.

Architecture

A rule per Gateway, or a rule per cluster

PER-GATEWAY MODEL · one LoadBalancer Service per Gateway Gateway A Gateway B Gateway C…N LB Service · IP1 · :443 LB Service · IP2 · :443 LB Service · IPn · :443,:8443 Azure NIC rule budget ≈ 1 rule per Service port 300 / NIC — exhausted fast VS ZENOINGRESS · single shared data plane Gateway A Gateway B Gateway C…N One LoadBalancer shared data plane host/SNI/path routing :80 / :443 ~2 Azure rules N Gateways · 1000s of routes · flat

Hostnames, SNI and paths are matched inside the proxy — above the Layer-4 Load Balancer — so adding tenants, Gateways or routes never touches the Azure rule budget. The L4 frontend count stays at one.

The setup

One LoadBalancer Service for the whole cluster

The 300-rule limit is a Layer-4 limit: it grows with the number of L4 frontends you create, not with HTTP routes or hostnames. So you expose the data plane once — a single LoadBalancer Service on standard ports — and let it carry every Gateway:

values.yaml helm · zenoingress data plane
dataPlane:
  service:
    type: LoadBalancer        # the ONLY L4 frontend in the cluster
    ports:
      - { name: http,  port: 80 }
      - { name: https, port: 443 }   # 2 ports ≈ 2 Azure rules, flat
  replicas: 3                  # scale the proxy, not the LB count

Every team then ships its own Gateway as usual. Because they all bind the same shared gatewayClassName, ZenoIngress folds them into that one data plane — no new LoadBalancer Service, and +0 Azure rules per Gateway:

gateways.yaml gateway.networking.k8s.io/v1
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata: { name: team-a, namespace: team-a }
spec:
  gatewayClassName: zenoingress   # shared data plane — no new LB Service
  listeners:
    - name: https
      hostname: "a.example.com"
      port: 443
      protocol: HTTPS
---
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata: { name: team-b, namespace: team-b }
spec:
  gatewayClassName: zenoingress   # same data plane, same LB, +0 Azure rules
  listeners:
    - name: https
      hostname: "b.example.com"
      port: 443
      protocol: HTTPS

Add Gateway C, D … N and a thousand HTTPRoutes behind them — the cluster still owns exactly one LoadBalancer Service.

What happens

Where the rules go

  1. By default AKS places every Service type=LoadBalancer on one Standard Load Balancer, and each Service port consumes roughly one inbound rule on the node NIC — capped at 300 per NIC.
  2. A per-Gateway controller provisions a dedicated proxy and its own LoadBalancer Service for every Gateway. Tenants × listeners × ports each take a slice of the budget. budget burns
  3. ZenoIngress instead registers all Gateways, listeners and routes against a single shared data plane. No matter how many Gateways exist, it provisions no extra LoadBalancer Service.
  4. That one Service exposes :80 and :443 — two ports, so about two Azure rules, and the count stays flat as Gateways and routes grow. ~2 rules
  5. Routing happens above Layer 4: hostname, SNI and path are matched inside the proxy, so adding hostnames or routes never touches the Azure rule budget at all.

Outcome

The rule budget stops being your scaling limit

~2 rulesN Gateways, thousands of routes
1 LoadBalancerone shared data plane
300 / NICthe Azure limit you avoid
Why ZenoIngress

One shared data plane behind a single LoadBalancer Service means tenants, Gateways and routes scale on HTTP routing — not on Azure's Layer-4 rule budget. The L4 frontend count is a constant, not a function of how many teams you onboard.

The honest caveats

Run this yourself

Count your Azure rules in 30 seconds

List every LoadBalancer Service and its ports. The total of Services × ports is roughly your Azure inbound-rule count today — under the shared model that list collapses to a single entry:

terminal — Services × ports ≈ your Azure rule count
$ # per-Gateway cluster: one LB Service per Gateway
$ kubectl get svc -A --field-selector spec.type=LoadBalancer \
    -o custom-columns=NS:.metadata.namespace,NAME:.metadata.name,PORTS:.spec.ports[*].port
NS          NAME              PORTS
team-a      gw-team-a-lb      443
team-b      gw-team-b-lb      443
team-c      gw-team-c-lb      443,8443
…           (one per Gateway)  …
# 240 Services × ports ≈ 240 Azure rules — closing in on 300

$ # same command, under ZenoIngress' shared data plane
$ kubectl get svc -A --field-selector spec.type=LoadBalancer \
    -o custom-columns=NS:.metadata.namespace,NAME:.metadata.name,PORTS:.spec.ports[*].port
NS            NAME               PORTS
zeno-system   zenoingress-data   80,443
# 1 Service × 2 ports ≈ 2 Azure rules — flat as Gateways grow

Sources: Microsoft Learn — Azure Load Balancer limits, Standard Load Balancer in AKS, Multiple Standard Load Balancers in AKS.

Thousands of routes. One rule budget barely touched.

A single shared data plane for the whole cluster. Free for one production cluster.

Deploy Free