Managing service traffic with a service mesh
Open in the interactive app: practice, quiz, mock interview →In one minute
- A service mesh is an infrastructure layer that handles service-to-service communication: mutual TLS, load balancing, retries, timeouts, traffic shifting, and telemetry, without changes to application code.
- It has a data plane of proxies (sidecars next to each workload, or shared node-level proxies) that carry the traffic, and a control plane that pushes configuration, certificates, and service discovery to those proxies.
- It gives uniform security and observability across a polyglot fleet and lets platform teams change traffic policy centrally.
- It costs CPU, memory, latency per hop, and significant operational complexity. A mesh is a platform product, not a library you add on a Friday.
How it works
Data plane
In the classic sidecar model, every pod gets a proxy (commonly Envoy, or Linkerd's Rust proxy). Iptables rules redirect the pod's inbound and outbound traffic through it. When service A calls service B, A's proxy resolves B's healthy endpoints, picks one with a load-balancing algorithm (round robin, least requests, or a peak-EWMA latency-aware choice), opens an mTLS connection to B's proxy, applies timeouts and retries, and records metrics and trace spans. B's proxy terminates mTLS, checks authorisation policy, and forwards to B over localhost.
Newer sidecarless designs (Istio ambient mode, eBPF-based meshes) move layer 4 work (mTLS, basic telemetry) into a per-node component and run layer 7 proxies only where needed, cutting per-pod overhead. In Istio's ambient mode, generally available since 1.24 in November 2024 (Istio), a Rust per-node proxy called ztunnel handles mTLS and L4 policy and tunnels traffic over HBONE, an HTTP CONNECT-based protocol; optional per-namespace Envoy "waypoint" proxies add L7 routing and policy (Istio docs). Istio's benchmark at 1,000 requests per second measures ztunnel at about 0.06 vCPU and 12 MB against 0.20 vCPU and 60 MB for a sidecar Envoy (Istio performance).
Control plane
The control plane watches the cluster for services and endpoints, compiles routing and policy into proxy configuration, and streams it to every proxy (Envoy's xDS APIs). It also runs a certificate authority that issues short-lived workload certificates, often valid for hours, tied to a workload identity such as a Kubernetes service account. The identity is written as a SPIFFE ID, a URI of the form spiffe://<trust domain>/<workload path> (SPIFFE). Proxies rotate certificates automatically, which is what makes mesh-wide mTLS practical.
What you get
- Security. Encrypted, mutually authenticated traffic by default, plus identity-based authorisation ("only
checkoutmay callpaymentsonPOST /charge"). - Reliability controls. Timeouts, retries with budgets, circuit breaking, and outlier detection that ejects misbehaving endpoints.
- Traffic management. Weighted routing for canaries, header-based routing for testing, mirroring for shadow traffic, and fault injection for chaos tests.
- Observability. Golden metrics (rate, errors, latency) per service pair, and trace spans, without instrumenting each service.
What it does not do
The mesh cannot make a non-idempotent call safe to retry, cannot propagate trace context unless the application forwards the headers, and does not replace application-level authorisation on data.
Down the stack
Layer 1The contract
The mesh promises that traffic between workloads is encrypted and mutually authenticated with an identity tied to the workload, not its IP; that authorisation policy is enforced on every request; that configuration changes reach all proxies eventually; and that if the control plane is unavailable, proxies keep serving with their last accepted configuration and certificates until those expire. Envoy's xDS protocol states this directly: the last known configuration persists until the connection to the management server is re-established (Envoy xDS protocol).
Layer 2The mechanism
Envoy-based meshes use the xDS APIs over long-lived gRPC streams: LDS for listeners, RDS for routes, CDS for clusters, EDS for endpoints, and SDS for secrets, often multiplexed on one ADS stream. Each response carries a version_info and nonce; the proxy ACKs by echoing the new version, or NACKs by sending back the previous version plus an error_detail, so a bad config is rejected without replacing the good one. Workload identity follows SPIFFE: the control plane's CA signs a short-lived X.509 certificate whose URI SAN is a SPIFFE ID like spiffe://cluster.local/ns/payments/sa/payments-api, delivered over SDS by the local Istio agent and rotated before expiry (the agent requests 24-hour certificates by default, SECRET_TTL=24h; Istio docs). Authorisation policies match on that peer identity plus method and path.
Layer 3The machine
A TLS 1.3 handshake costs one round trip and, on each side, an ECDHE key exchange and an ECDSA signature or verification, tens to low hundreds of microseconds of CPU. Bulk encryption with AES-GCM runs at several GB per second per core on CPUs with AES-NI, so encrypting the data itself is cheap; handshakes are the expensive part, and pooled connections between proxies amortise them. Proxy memory scales with the config it holds, roughly clusters times endpoints, so a full mesh view of thousands of endpoints in every proxy wastes gigabytes fleet-wide unless scoped. Each endpoint change triggers EDS pushes to every interested proxy; a rolling deploy of a large service can generate thousands of pushes per second, so control planes debounce and batch.
Why it's built this way
- Pod IPs change constantly, so identity is bound to a service account through certificates rather than to addresses.
- Long-lived keys are dangerous if leaked, so certificates are short-lived and rotated automatically, which requires automation.
- Thousands of proxies need consistent config without restarts, so it is streamed with versions and ACK or NACK.
- Handshakes are costly while bulk encryption is cheap, so proxies keep connections open and reuse them.
Diagram
flowchart TB
CP["Control plane (config, discovery, CA)"]
subgraph PODA["Pod: checkout"]
A["checkout app"] --> PA["Proxy"]
end
subgraph PODB["Pod: payments"]
PB["Proxy"] --> B["payments app"]
end
PA -->|"mTLS, retries, LB"| PB
CP -.->|"xDS config + certs"| PA
CP -.->|"xDS config + certs"| PB
PA -.->|"metrics, spans"| OBS["Telemetry backend"]
PB -.->|"metrics, spans"| OBSWorked example
A fintech runs 220 services on Kubernetes across 2,500 pods, in Go, Java, and Python. Auditors require encryption in transit and proof of which services can reach the card vault. Today, each language has its own TLS setup, and retry behaviour differs by team.
They adopt a sidecar mesh. Measured overhead per proxy: about 40 MB memory and 0.1 vCPU at their median load, so roughly 100 GB of memory and 250 vCPU fleet-wide, about 7% of cluster cost. Each hop adds two proxy traversals: 0.6 ms at p50 and 2.5 ms at p99. A checkout request crossing 6 services gains about 4 ms at p50.
In return, mTLS reaches 100% of traffic within two months, authorisation policy restricts the card vault to 3 caller identities, and every service pair has latency and error dashboards. A canary of a new pricing service at 5% weighted traffic catches a 2% error regression, rolled back by changing one routing resource.
Two incidents follow. A control plane upgrade pushes a malformed config that breaks routing for 6 minutes, so they add config validation and a staged rollout per namespace. And a mesh-level retry policy of 3 attempts amplifies load on a struggling service; they switch to retry budgets capping retries at 20% of requests.
In the wild
- Monzo, 2017: the mesh proxies take the bank down. Monzo routed calls between several hundred microservices through Linkerd, which learned endpoints from the Kubernetes API. A Kubernetes and etcd client bug, set up by an etcd resize two weeks earlier, made Linkerd miss updates, so a payments deploy at 14:10 on 27 October started failing. Engineers restarted all several hundred Linkerd instances, which exposed a second bug: the Linkerd version in use threw a
NullPointerExceptionon a Kubernetes service with no endpoints, left over from a scale-down the day before. By 15:13 the whole platform was down; deleting the empty service at 15:31 restored traffic. Monzo listed the restart-everything decision as a mistake and upgraded Linkerd (Monzo). - Spotify, 2022: a managed xDS control plane fails, and clients fail with it. Most Spotify services used DNS-based discovery, but some used Google Cloud's Traffic Director, an xDS control plane. On 8 March Traffic Director had an outage, which, combined with a bug in the gRPC client library (grpc-java issue 8950), broke discovery for the services that depended on it. Logged-out users worldwide could not log back in for about 2 h 23 min, until Spotify moved those services back to DNS discovery (Spotify).
Trade-offs
| Option | Strengths | Weaknesses | Choose when |
|---|---|---|---|
| Libraries in each service | Lowest latency, no extra infrastructure | Per-language implementations, upgrades need redeploys | Few languages, small number of services |
| Sidecar mesh | Uniform policy and telemetry, per-workload control | Per-pod resource cost, extra hops, operational complexity | Large polyglot fleets with security requirements |
| Sidecarless or ambient mesh | Lower overhead, simpler pod lifecycle, GA in Istio since 1.24 | L7 features need separately run waypoint proxies; shared per-node L4 proxy | Large fleets where sidecar cost dominates |
| No mesh, gateway only | Simple, controls only north-south traffic | No east-west security or visibility | Small systems or a monolith with few services |
Pitfalls in production
- Control plane as a single point of failure. A bad push breaks routing everywhere. Validate configs, roll out per namespace, and confirm proxies keep their last good config if the control plane is down.
- Retry amplification. Mesh retries stack with application retries. Use retry budgets and pick one layer to own retries. Envoy's retry budget defaults to 20% of active requests, with a floor of 3 concurrent retries (Envoy docs).
- Mystery 503s. Proxy-generated errors are confused with application errors. Log proxy response flags and teach teams to read them.
- Certificate expiry and clock skew. Short-lived certs fail when rotation breaks or clocks drift. Alert on certificate age and rotation errors.
- Adopting everything at once. Enabling every feature on every namespace causes a pile of hard-to-attribute incidents. Start with mTLS and telemetry, then add traffic policy gradually.
Leadership lens
- Ask which concrete problem the mesh solves: mTLS mandate, polyglot reliability, or observability. If only one, a lighter tool may suffice.
- Staff it like a product: a platform team, upgrade cadence, runbooks, and on-call.
- Check the supply chain, not just the features. AWS ended support for App Mesh on 30 September 2026, removing console and resource access (AWS docs), and since February 2024 the Linkerd open source project ships only edge releases, leaving stable builds to vendors (Linkerd). Ask who provides patched, stable releases for the next five years.
- Quantify the resource and latency tax and get agreement before rollout.
- Define ownership boundaries: the platform team owns mesh defaults; service teams own their routes and policies through reviewed config.
Check yourself
What is the difference between the data plane and the control plane?
Show answer
The data plane is the proxies that carry and act on every request; the control plane computes configuration, discovery, and certificates and distributes them to the proxies, without sitting on the request path.
Why can a mesh not fully implement distributed tracing on its own?
Show answer
Proxies see separate inbound and outbound requests and cannot link them unless the application copies trace headers from the incoming request to its outgoing calls.
Why is mesh-level retry configuration risky?
Show answer
Retries at the mesh multiply with application and client retries, so a struggling service can receive several times its normal load. Retry budgets and a single owning layer prevent this.
Sources and further study
Primary
- Envoy: xDS REST and gRPC protocol: the ACK/NACK, versioning and ADS rules every Envoy-based control plane implements.
- SPIFFE concepts: SPIFFE IDs, X.509 and JWT SVIDs, and the Workload API.
Systems docs
- Istio: ambient mode reaches GA: what was declared stable in 1.24 and the claimed savings over sidecars.
- Istio: performance and scalability: measured cost of sidecars, ztunnel and waypoints.
- Linkerd: releases and versions: the edge-only release model of the open source project.
- Envoy: circuit breaker and retry budget config: the defaults behind mesh retry budgets.
Books
- Seeking SRE, ed. David N. Blank-Edelman, chapter 26 "The Service Mesh: Wrangler of Your Microservices?": the operational case for a mesh and what it looks like in practice.
- Understanding Distributed Systems (2nd edition), Roberto Vitillo, chapter "Control planes and data planes": the split every mesh is built on, seen as a general systems pattern.
- Building Microservices (2nd edition), Sam Newman, chapter 5 "Implementing Microservice Communication": its "Service Meshes" section, read next to the one on API gateways.
Other curricula
- roadmap.sh Software Architect: lists Service Mesh as a node among architecture tools; the System Design roadmap covers only its building blocks, such as Sidecar.
- donnemartin/system-design-primer: has no mesh coverage; service-to-service concerns stop at service discovery with Consul, etcd or ZooKeeper.
Watch
- Istio & Service Mesh, simply explained in 15 mins (TechWorld with Nana): architecture of a sidecar mesh and what each part does.
- Istio Service Mesh Explained (IBM Technology): a short overview of mesh capabilities.
Free resources
- What is a service mesh? (Linkerd): a clear explanation from one of the first meshes.
- What is Istio?: architecture, features, and deployment modes.
- Istio ambient mode overview: the sidecarless design and its trade-offs.
- Envoy outlier detection: how proxies eject unhealthy endpoints.
- SPIFFE overview: the workload identity standard behind mesh mTLS.