L7 load balancing with kgateway
Configure load balancing and failover for traffic entering the ambient mesh through Solo Enterprise for kgateway.
About this guide
Traffic entering your ambient mesh from external clients can be load balanced and failed over using Solo Enterprise for kgateway as the ingress gateway. This guide shows how to configure Solo Enterprise for kgateway to route traffic to services in your ambient mesh, including routing through a waypoint proxy for L7 policy enforcement.
Solo Enterprise for kgateway does not originate HBONE connections. Instead, it uses a ztunnel to capture mesh traffic, behaving like a normal client within the mesh. This means that load balancing and failover policies are deferred to the ambient mesh components (ztunnel and waypoint) rather than being applied at the ingress gateway level.
For conceptual information about how load balancing works with ingress gateways, see the load balancing and failover overview.
Before you begin
Set up an ambient mesh in one cluster by using the Gloo Operator or Helm.Step 1: Set up Solo Enterprise for kgateway
Install Solo Enterprise for kgateway in your cluster, with the Istio integration enabled. For more information, see the Solo Enterprise for kgateway setup guide and Istio integration guide.
Set your Solo Enterprise for kgateway license key as an environment variable. If you do not have one, contact an account representative.
export LICENSE_KEY=<license-key>Deploy the Kubernetes Gateway API CRDs.
kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.5.0/standard-install.yamlSave the following Istio integration settings in an
enterprise-kgateway.yamlHelm values file.cat <<EOF > enterprise-kgateway.yaml controller: extraEnv: KGW_ENABLE_ISTIO_INTEGRATION: true EOFDeploy the Solo Enterprise for kgateway CRDs by using Helm.
helm upgrade -i enterprise-kgateway-crds oci://us-docker.pkg.dev/solo-public/enterprise-kgateway/charts/enterprise-kgateway-crds \ --create-namespace \ --namespace kgateway-system \ --version 2.2.0-rc.2If you already installed the external auth, rate limit, and Enterprise listener set CRDs, such as by previously installing another Solo product in the same cluster, include the--set installExtAuthCRDs=false,--set installRateLimitCRDs=false, and--set installEnterpriseListenerSetCRDflags in the Helm installation.Install Solo Enterprise for kgateway, including your custom values file, by using Helm.
helm upgrade -i enterprise-kgateway oci://us-docker.pkg.dev/solo-public/enterprise-kgateway/charts/enterprise-kgateway \ -n kgateway-system \ --version 2.2.0-rc.2 \ --set-string licensing.licenseKey=${LICENSE_KEY} \ -f enterprise-kgateway.yamlMake sure that the
enterprise-kgatewaycontrol plane is running.kubectl get pods -n kgateway-systemExample output:
NAME READY STATUS RESTARTS AGE enterprise-kgateway-5495d98459-46dpk 1/1 Running 0 19sAdd the
kgateway-systemnamespace to your ambient mesh. The label instructs istiod to configure a ztunnel socket on all the pods in that namespace so that traffic from these pods is secured via mutual TLS (mTLS).kubectl label ns kgateway-system istio.io/dataplane-mode=ambientCreate a Gateway resource in the
kgateway-systemnamespace with an HTTP listener.kubectl apply -f- <<EOF apiVersion: gateway.networking.k8s.io/v1 kind: Gateway metadata: name: http namespace: kgateway-system spec: gatewayClassName: enterprise-kgateway listeners: - name: http port: 8080 protocol: HTTP allowedRoutes: namespaces: from: All EOFWait for the Gateway to be ready.
kubectl get gateway http -n kgateway-systemExample output:
NAME CLASS ADDRESS PROGRAMMED AGE http enterprise-kgateway 10.96.123.45 True 30sGet the external address of the gateway and save it in an environment variable.
export KGATEWAY_IP=$(kubectl get svc -n kgateway-system http -o=jsonpath="{.status.loadBalancer.ingress[0]['hostname','ip']}") echo $KGATEWAY_IPkubectl port-forward -n kgateway-system deployment/http 8080:8080 & export KGATEWAY_IP=localhost
Step 2: Deploy a sample app and test ingress routing
Deploy a backend app and configure an HTTPRoute to route traffic from the ingress gateway.
Traffic flow without waypoint: The following diagram shows how external traffic flows through the ingress gateway to reach backend services in the ambient mesh. Solo Enterprise for kgateway uses a ztunnel to enter the mesh, behaving like a normal mesh client. The ztunnel handles L4 load balancing.
graph LR
External[External client] -->|HTTP| KGW[Solo Enterprise<br/>for kgateway]
KGW -->|mTLS mesh<br/>traffic| Ztunnel[kgateway ztunnel<br/>L4 load balancing]
Ztunnel -->|Round-robin| Backend1[in-ambient pod 1]
Ztunnel -->|Round-robin| Backend2[in-ambient pod 2]
Ztunnel -->|Round-robin| Backend3[in-ambient pod 3]
style KGW fill:#2068F3,color:#fff
Deploy the
in-ambienthttpbin sample app. This manifest creates thehttpbinnamespace with anin-ambientbackend service. The app is already labeled for inclusion in the ambient mesh withistio.io/dataplane-mode: ambient.kubectl apply -f https://raw.githubusercontent.com/solo-io/doc-examples/main/istio/sample-apps/in-ambient.yamlScale the
in-ambientdeployment to 3 replicas so that you can observe load balancing across multiple endpoints.kubectl scale deployment in-ambient -n httpbin --replicas=3Verify that all pods are running.
kubectl get pods -n httpbinExample output, in which
in-ambientruns as 3 replicas:NAME READY STATUS RESTARTS AGE in-ambient-7d8f9b6c54-abc12 1/1 Running 0 45s in-ambient-7d8f9b6c54-def34 1/1 Running 0 20s in-ambient-7d8f9b6c54-ghi56 1/1 Running 0 20sCreate an HTTPRoute to route ingress traffic from the ingress gateway to the
in-ambientservice.kubectl apply -f- <<EOF apiVersion: gateway.networking.k8s.io/v1 kind: HTTPRoute metadata: name: in-ambient namespace: httpbin spec: parentRefs: - name: http namespace: kgateway-system hostnames: - "httpbin.example.com" rules: - matches: - path: type: PathPrefix value: / backendRefs: - name: in-ambient port: 8000 EOFVerify that the HTTPRoute is accepted.
kubectl get httproute -n httpbinExample output:
NAME HOSTNAMES AGE in-ambient ["httpbin.example.com"] 30sSend requests to the in-ambient app through the gateway. The
/hostnameendpoint returns the pod hostname, showing which replica handled each request. Traffic follows the path of kgateway -> ztunnel -> backend. The ztunnel uses round robin L4 load balancing across the backend endpoints. Verify that you see responses from all 3 replicas.for i in $(seq 1 9); do curl -s -H "Host: httpbin.example.com" http://$KGATEWAY_IP/hostname doneExample output:
in-ambient-7d8f9b6c54-abc12 in-ambient-7d8f9b6c54-def34 in-ambient-7d8f9b6c54-ghi56 in-ambient-7d8f9b6c54-abc12 ...Verify that traffic between the gateway proxy and the in-ambient app is secured via mutual TLS. Because traffic in an ambient mesh is intercepted by the ztunnels that are co-located on the same node as the sending and receiving service, you can check the logs of the ztunnels.
Find the
NODEthat one of the in-ambient backend pods runs on.kubectl get pods -n httpbin -o wideExample output:
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES in-ambient-7d8f9b6c54-abc12 1/1 Running 0 22h 10.XX.X.XX gke-ambient-default-pool-bb9a8da5-bdf4 <none> <none>Find the ztunnel that runs on the same node as the in-ambient backend pod.
kubectl get pods -n istio-system -o wide | grep ztunnelCheck the logs of that ztunnel instance and verify that the source and destination workloads have a SPIFFE ID.
kubectl logs -n istio-system <ztunnel-instance>Example output:
2025-03-19T17:32:42.762545Z info http access request complete src.addr=10.0.71.117:42468 src.workload="http-9db6c8995-l54dw" src.namespace="kgateway-system" src.identity="spiffe://cluster.local/ns/kgateway-system/sa/http" dst.addr=10.0.65.144:15008 dst.hbone_addr=10.0.65.144:8080 dst.service="in-ambient.httpbin.svc.cluster.local" dst.workload="in-ambient-7d8f9b6c54-abc12" dst.namespace="httpbin" dst.identity="spiffe://cluster.local/ns/httpbin/sa/in-ambient" direction="inbound" method=GET path="/headers" protocol=HTTP1 response_code=200 host="www.example.com:8080" user_agent="curl/8.7.1" request_id="4c5fc679-c5cd-4721-8735-51bcdbea6e0f" duration="0ms" 2025-03-19T17:32:46.810472Z info access connection complete src.addr=10.0.71.117:42468 src.workload="http-9db6c8995-l54dw" src.namespace="kgateway-system" src.identity="spiffe://cluster.local/ns/kgateway-system/sa/http" dst.addr=10.0.65.144:15008 dst.hbone_addr=10.0.65.144:8080 dst.service="in-ambient.httpbin.svc.cluster.local" dst.workload="in-ambient-7d8f9b6c54-abc12" dst.namespace="httpbin" dst.identity="spiffe://cluster.local/ns/httpbin/sa/in-ambient" direction="inbound" bytes_sent=1290 bytes_recv=550 duration="6742ms"
Step 3: Route ingress traffic through a waypoint
To apply L7 policies such as DestinationRules for HTTP-aware failover, configure ingress traffic to flow through a waypoint proxy.
Traffic flow with waypoint: The following diagram shows how traffic flows when the istio.io/ingress-use-waypoint=true label is set on the namespace or service. The ingress gateway ztunnel routes to the waypoint. The waypoint then enforces L7 policies and routes to backend endpoints.
graph LR
External[External client] -->|HTTP| KGW[Solo Enterprise<br/>for kgateway]
KGW -->|mTLS mesh<br/>traffic| Ztunnel[kgateway ztunnel<br/>Routes to waypoint]
Ztunnel -->|HBONE| Waypoint[Waypoint proxy<br/>L7 policy enforcement]
Waypoint -->|L7 load balancing| Backend1[in-ambient pod 1]
Waypoint -->|L7 load balancing| Backend2[in-ambient pod 2]
Waypoint -->|L7 load balancing| Backend3[in-ambient pod 3]
style KGW fill:#2068F3,color:#fff
style Waypoint fill:#2068F3,color:#fff
Create a waypoint Gateway in the
httpbinnamespace. The waypoint enforces L7 policies for services in the namespace.kubectl apply -f- <<EOF apiVersion: gateway.networking.k8s.io/v1 kind: Gateway metadata: name: httpbin-waypoint namespace: httpbin spec: gatewayClassName: istio-waypoint listeners: - name: mesh port: 15008 protocol: HBONE allowedRoutes: namespaces: from: Same EOFWait for the waypoint to be fully deployed.
kubectl -n httpbin rollout status deployment/httpbin-waypointExample output:
deployment "httpbin-waypoint" successfully rolled outAdd the
istio.io/ingress-use-waypoint=truelabel to thein-ambientservice so that ingress traffic is routed through the waypoint.kubectl label service in-ambient -n httpbin \ istio.io/ingress-use-waypoint=trueWhenistio.io/ingress-use-waypoint=trueis set, the ingress gateway collapses the endpoints for each backend pod into a single virtual IP address (VIP). The ingress gateway's ztunnel then sends all traffic to that VIP, so that traffic follows the path of ingress gateway -> ztunnel -> waypoint -> backend. For this reason, the ambient mesh handles all routing to backend pods and L7 policy application.Send a request through the gateway to verify that traffic flows through the waypoint.
curl -s -H "Host: httpbin.example.com" http://$KGATEWAY_IP/hostnameReview the waypoint logs to confirm traffic is being processed.
kubectl logs -n httpbin deploy/httpbin-waypoint | tail -10Example output:
[2025-03-06T17:15:23.456Z] "GET /hostname HTTP/1.1" 200 - via_upstream - "-" 0 32 5 4 "-" "curl/7.88.1" "abc123-def456" "in-ambient.httpbin.svc.cluster.local:8000" "10.10.0.15:8080" inbound-vip|8000|http|in-ambient.httpbin.svc.cluster.local 10.10.0.14:45678 10.96.45.123:8000 10.10.0.14:45678 - defaultSend multiple requests to verify round-robin load balancing at the waypoint.
for i in $(seq 1 9); do curl -s -H "Host: httpbin.example.com" http://$KGATEWAY_IP/hostname doneExample output showing traffic distributed across all 3 replicas:
in-ambient-7d8f9b6c54-abc12 in-ambient-7d8f9b6c54-def34 in-ambient-7d8f9b6c54-ghi56 in-ambient-7d8f9b6c54-abc12 ...
Step 4: Test ingress failover
Test how failover works when backend endpoints become unavailable.
Apply an Istio DestinationRule at the waypoint for failover control. The DestinationRule is enforced at the waypoint, not at the ingress gateway, which allows for HTTP-aware failover with outlier detection. The outlier detection settings eject an endpoint for 30 seconds after 5 consecutive 5xx errors, with detection checks running every 10 seconds.
kubectl apply -f- <<EOF apiVersion: networking.istio.io/v1 kind: DestinationRule metadata: name: in-ambient-failover namespace: httpbin spec: host: in-ambient trafficPolicy: loadBalancer: simple: ROUND_ROBIN outlierDetection: consecutive5xxErrors: 5 interval: 10s baseEjectionTime: 30s EOFDelete one of the
in-ambientbackend pods to simulate an endpoint failure.kubectl delete pod -n httpbin -l app=in-ambient --field-selector=status.phase=Running --wait=false | head -1Send requests through the gateway. Traffic is distributed among the remaining healthy endpoints. The waypoint applies the outlier detection rules from the DestinationRule.
for i in $(seq 1 6); do curl -s --max-time 2 -H "Host: httpbin.example.com" http://$KGATEWAY_IP/hostname || echo 'request failed' doneExample output showing traffic distributed only among the 2 remaining healthy replicas:
in-ambient-7d8f9b6c54-def34 in-ambient-7d8f9b6c54-ghi56 in-ambient-7d8f9b6c54-def34 in-ambient-7d8f9b6c54-ghi56 in-ambient-7d8f9b6c54-def34 in-ambient-7d8f9b6c54-ghi56Check the pod status immediately after deletion to see one replica unavailable.
kubectl get pods -n httpbin -l app=in-ambientExample output showing one pod terminating or in ContainerCreating state:
NAME READY STATUS RESTARTS AGE in-ambient-7d8f9b6c54-abc12 1/1 Terminating 0 5m in-ambient-7d8f9b6c54-def34 1/1 Running 0 5m in-ambient-7d8f9b6c54-ghi56 1/1 Running 0 5m in-ambient-7d8f9b6c54-jkl78 0/1 ContainerCreating 0 2sWait for the replacement pod to start and verify that all three replicas are running again.
sleep 10 kubectl get pods -n httpbin -l app=in-ambientExample output showing all replicas healthy:
NAME READY STATUS RESTARTS AGE in-ambient-7d8f9b6c54-def34 1/1 Running 0 5m in-ambient-7d8f9b6c54-ghi56 1/1 Running 0 5m in-ambient-7d8f9b6c54-jkl78 1/1 Running 0 12s
Cleanup
You can optionally remove the resources that you created in this guide. If you want to continue to the other load balancing and failover guides, you can keep the
httpbinnamespace and apps for use in those guides as well.kubectl delete destinationrule in-ambient-failover -n httpbin kubectl delete httproute in-ambient -n httpbin kubectl delete gateway httpbin-waypoint -n httpbin kubectl delete namespace httpbin kubectl delete gateway http -n kgateway-systemTo uninstall Solo Enterprise for kgateway, see the uninstall guide.
Next steps
For multicluster failover scenarios, see Multicluster zone and region failover.