Cilium Gateway API Part 2: An External Load Balancer in Front
Compare LoadBalancer, NodePort, and hostNetwork paths from an existing external load balancer to Cilium Gateway API.
Key takeaways
- Use the default
LoadBalancermode when the external LB integration can manage Kubernetes Services directly - For an independent appliance or virtual LB, target a generated
NodePortand automate port synchronization - Use selected-node
hostNetworklisteners when fixed node ports80/443matter more than the Service-mode flexibility - Health checks must exercise the Gateway data path instead of checking the Kubernetes API server
- Choose either L4 source preservation or trusted L7 forwarding headers and enforce the same trust boundary at the node firewall
Separate LB and Gateway responsibilities
- The external LB should select healthy nodes, while Cilium Gateway owns TLS and L7 routing in the baseline design
- TLS passthrough keeps certificates and host routing at the Gateway
- TLS termination at the LB requires an explicit re-encryption and forwarding-header policy
Select one of three integration patterns
-
Pattern A lets a cloud controller synchronize the generated
LoadBalancerService with the external LB- It uses the default Cilium Gateway behavior and needs no custom GatewayClass
- Cloud-specific annotations can be passed to the generated Service through
Gateway.spec.infrastructure.annotations - A missing external address usually indicates a LoadBalancer implementation or permission problem
-
Pattern B configures a generated
NodePortService as the external LB targetCiliumGatewayClassConfigcan change the generated Service type- Pin and test Cilium upgrades because this configuration API is
v2alpha1
apiVersion: cilium.io/v2alpha1
kind: CiliumGatewayClassConfig
metadata:
name: external-lb
namespace: edge
spec:
service:
type: NodePort
externalTrafficPolicy: Cluster
---
apiVersion: gateway.networking.k8s.io/v1
kind: GatewayClass
metadata:
name: cilium-external-lb
spec:
controllerName: io.cilium/gateway-controller
parametersRef:
group: cilium.io
kind: CiliumGatewayClassConfig
name: external-lb
namespace: edge
---
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: public
namespace: edge
spec:
gatewayClassName: cilium-external-lb
listeners:
- name: https
protocol: HTTPS
port: 443
tls:
mode: Terminate
certificateRefs:
- name: wildcard-example-com- Synchronize the allocated node port into the external LB configuration
kubectl -n edge get svc cilium-gateway-public \
-o jsonpath='{range .spec.ports[*]}{.name}{"="}{.nodePort}{"\n"}{end}'- Pattern C binds listeners directly with
hostNetwork- The LB can target
nodeIP:443without discovering a NodePort - This mode is mutually exclusive with LoadBalancer Service mode and has
TCPRouteandUDPRoutelimitations - Part 4 contains the full host-network configuration
- The LB can target
Limit targets to ingress nodes
- Make the external LB target set an explicit operational role
kubectl label node worker-a worker-b gateway.cilium.io/expose=true
kubectl get nodes -l gateway.cilium.io/expose=true -o wide-
NodePort can listen broadly, so the LB target group and firewall must narrow actual exposure
- Publish only the external LB VIP to the Internet
- Permit the NodePort only from LB source CIDRs
- Exclude control-plane nodes with LB target filters or
node.kubernetes.io/exclude-from-external-load-balancers
-
externalTrafficPolicy: Clusterremains a practical default for this Gateway Service- Cilium's Envoy TPROXY path differs from a conventional Service source-IP path
- Avoid
Localwhen Cilium Node IPAM LB is used with Gateway API because of its dummy Endpoint limitation
Check the complete Gateway path
- A TCP health check only proves that a port accepts connections
- Use it when quick node removal is the dominant requirement
- Use a dedicated hostname and
/healthzRoute when TLS, Route, and backend readiness must also be tested
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: gateway-health
namespace: edge
spec:
parentRefs:
- name: public
hostnames: ["gateway-health.example.com"]
rules:
- matches:
- path:
type: Exact
value: /healthz
backendRefs:
- name: gateway-health
port: 8080- Never use the API server
/readyzendpoint as the Gateway health check- A healthy API server does not prove that Envoy, Route, and backend traffic work
- An already programmed Envoy data path can continue serving during a short API-server disruption
Establish one client-IP authority
-
Keep
gatewayAPI.useRemoteAddress=truewhen an L4 LB preserves the source address- If the LB applies SNAT, evaluate DSR or the provider's source-preservation mode
-
Configure trusted hops only for a controlled L7 LB that creates
X-Forwarded-For
gatewayAPI:
enabled: true
useRemoteAddress: false
xffNumTrustedHops: 1- The trusted-hop count must match the real proxy chain
- Check append behavior when both a CDN and an external LB are present
- Block direct node access so a client cannot forge a trusted header
Test failures and changes
-
Drain or stop Envoy on one ingress node and measure LB removal time
- Record health-check intervals and failure thresholds
- Observe existing keep-alive connections separately from new connections
- Confirm that recovered targets return to service safely
-
Recreate a Gateway and verify that NodePort changes propagate automatically
- If the external appliance only supports static manual targets, evaluate
hostNetworkor a separately managed stable Service
- If the external appliance only supports static manual targets, evaluate
Recommended action
- Evaluate default
LoadBalancer, thenNodePort, thenhostNetworkin that order based on the LB's Kubernetes integration and fixed-port requirements - Do not release until a health check and failure drill cover
LB → node → Envoy → Route → backend