Benchmarking gRPC Load Balancing on K8s in 2026: Linkerd vs. Istio vs. Cilium

darksoul1 pts0 comments

Benchmarking gRPC Load Balancing on Kubernetes in 2026: Linkerd vs Istio vs Cilium

Skip to main content

Pricing<br>Resources

Academy

About

Contact usDocs

Get Service Mesh Certified with Buoyant.<br>Enroll now!

Blog home<br>Benchmarking gRPC Load Balancing on Kubernetes in 2026: Linkerd vs Istio vs Cilium

Linkerd Production Readiness Pre-Launch Checklist

Download checklist

Heading

Relevant articles<br>Linkerd vs Istio

eBPF, Sidecars, and the<br>Future of The Service Mesh

A Kubernetes engineer’s guide to mTLS

Ehsan Shirazi

Ivan Porta

July 14, 2026

Linkerd

gRPC is a popular protocol for inter-service communication. However, when gRPC is used on Kubernetes, it exhibits a surprising problem: the default Kubernetes load balancer and the default gRPC client behavior combine to actually negate load balancing and route all traffic to a single pod! This unpleasant surprise is made worse by the fact that it manifests not as errors but as high-tail latencies that can crop up randomly and intensify when pods are under stress.<br>Almost eight years ago, we described this phenomenon in gRPC Load Balancing on Kubernetes without Tears and gave an easy solution: deploy Linkerd. Linkerd’s intelligent connection management and request-level load balancing ensure proper load balancing of gRPC traffic without requiring code changes (or even configuration).<br>Today, the challenges with gRPC on Kubernetes remain. But there are now several service meshes to choose from. For the purposes of handling gRPC traffic, are they equivalent? To find out, Buoyant's Escalation Engineering team ran 700 benchmark tests across 5 isolated EKS clusters, testing how vanilla Kubernetes, Cilium (L7), Istio Ambient, Istio Sidecar, and Linkerd each handle a single degraded backend pod under gRPC load. The test harness and raw results can be reviewed or replicated here. The results are stark:<br>Linkerd held its tail latency flat as a backend pod degraded, and under the most severe delay its p99 came in more than 8x lower than Cilium L7 or either Istio mode.<br>Why do L4 load balancers struggle to distribute gRPC traffic effectively?<br>Why is this a problem in the first place? It comes down to an interaction between Kubernetes’s default load balancer, which works at the TCP (L4) layer, and gRPC, which is built on top of HTTP/2.<br>L4 load balancers make routing decisions at connection time. That works well with HTTP/1.1. Clients open multiple connections to parallelize requests, which gives the load balancer multiple chances to distribute work across backends. If one connection lands on a slow pod, others land elsewhere.<br>gRPC runs over HTTP/2, which multiplexes all requests over a single long-lived connection. One connection is sufficient and optimal by design, so clients don't open more. An L4 load balancer picks a pod when the connection opens, and every request on that connection goes to that pod until the connection closes or drops.<br>The result: no load balancing.<br>The test setup<br>The benchmark used an identical gRPC echo application (unary request/response) deployed to 5 isolated EKS clusters, one per mesh, on Kubernetes 1.35. Each cluster ran 5 server pods of the echo Deployment, one per node across 5 m5.2xlarge nodes (8 vCPU / 32 GiB, Guaranteed QoS, single AZ), with a dedicated m5.2xlarge load-generator node so the client never competed with the servers for CPU. 4 pods were healthy, and 1 had a delay injected via an in-app timer. Sleep controlled by ARTIFICIAL_DELAY_MS.<br>The target was a single Kubernetes ClusterIP Service backed by all 5 pods. The load generator resolved one Service address for every request, so on each request the networking layer in front of the pods (kube-proxy, Cilium's eBPF datapath, Istio's ztunnel, or the mesh's L7 proxy) chose which of the 5 endpoints would serve it. That choice is the entire experiment: a connection-level (L4) balancer makes it once when the connection opens and pins every subsequent request to that pod; a request-level (L7) balancer makes it fresh for each request.<br>We drove load with ghz, a dedicated gRPC benchmarking tool. Each run opened one connection (-C 1) carrying 200 concurrent in-flight streams (-Q 200) and ran continuously for 300 seconds (-d 300s) after a discarded 15-second warm-up. 200 is the concurrency level, the number of streams kept in flight at once, separate from the total request count. With one connection driving 200 streams for 300 seconds, each run completes roughly 0.3 to 4.7 million requests (the fast conditions land near the top of that range, and a self-throttled slow-pinned connection sits near the bottom), so every reported percentile is computed over hundreds of thousands of requests. Each scenario was repeated 20 times, producing 700 total runs and over 2.5 billion requests. Error rates stayed well under 0.01% across all conditions, consistent with end-of-window HTTP/2 stream cancellations.<br>Why ghz? We evaluated k6 and the gRPC test-infra tooling first. ghz won on the one axis this study...

load grpc connection kubernetes linkerd request

Related Articles