System designDeep dive10 min

Service mesh & sidecars

Every service needs the same things from the network: encryption, identity, retries, timeouts, load balancing and metrics. A service mesh moves them out of each service's code into a proxy beside it, configured centrally, so sixty services in four languages behave alike. It costs a proxy hop on every call and a new system to run, and newer designs try to shrink that bill.

1 · The problem

Every service needs the same network features, in every language

A hotel booking platform has 60 services written in Java, Go, Python and Node. Every call between them should be encrypted and authenticated, have a timeout and a sensible retry, be spread across healthy instances, and show up in the metrics. Each team built those features with a library for its language:

Need

Where it lives without a mesh

What drifts

Mutual TLS and service identity

A TLS library and certificate handling per service

Rotation scripts, expired certificates

Timeouts and retries

An HTTP client wrapper per language

Different defaults in every service

Load balancing across instances

A discovery client library

Old versions balancing per connection

Metrics per call

Instrumentation in each codebase

Different names and buckets

Sending 10% to a new version

Custom routing code

Usually not built at all

The real cost is change. Fixing a retry bug means a release of four libraries and sixty services. Deploying library upgrades across a large service architecture can be incredibly painful. A service mesh takes the other route: put the networking in a separate process beside each service, the same process for every language, and upgrade it without touching application code.

2 · The sidecar

A proxy beside each service handles its traffic, and a control plane configures them all

In the classic design, every instance of every service gets a sidecar proxy in the same pod. Rules in the pod's network setup redirect all its inbound and outbound traffic through the proxy, so the application talks plain HTTP to what looks like the service it called, and never knows the proxy is there:

Booking serviceplain HTTP, localIts sidecarretry, mTLS, metricsPricing's sidecarmTLS, policy, metricsPricing serviceplain HTTP, localmutual TLSBooking serviceplain HTTP, localIts sidecarretry, mTLS, metricsPricing's sidecarmTLS, policy, metricsPricing serviceplain HTTP, localmutual TLS

One call, two proxies: the caller's sidecar picks an instance, applies timeouts and retries and opens mTLS; the receiver's checks identity and policy. Both report metrics.

The proxies are the data plane: they carry every request. The control plane never touches requests; it configures the proxies. In Istio, the control plane (istiod) turns routing rules into Envoy configuration and pushes it to every sidecar at runtime, tracks service discovery, and acts as a certificate authority for the proxies' identities. The protocol Envoy uses to receive that configuration, xDS, has become a common language: gRPC can speak it directly.

Linkerd follows the same split with its own small proxy written in Rust, built only for the mesh rather than as a general proxy. Kubernetes now supports sidecars natively, stable since v1.33: a sidecar starts before the application container and, when the pod shuts down, is stopped only after the application has stopped, so the application never loses its network on the way in or out.

3 · What it does

The mesh gives every call identity, policy, resilience and telemetry

With the proxies in place, the platform team configures behavior once, for all 60 services:

Feature

How the mesh provides it

At the hotel platform

Identity and encryption

Each workload gets an X.509 certificate; agents rotate keys automatically; proxies use mTLS

Pricing knows a call came from booking, not just from an IP

Authorization

Policies by service identity, enforced in the proxy

Only booking and search may call pricing

Load balancing

Per request; Istio's default picks the less busy of two random instances

New pricing instances get traffic at once

Resilience

Timeouts, retries, circuit breakers configured per destination

Pricing calls: 2-second timeout per try

Traffic splitting

Weighted routes between versions

75% to pricing v1, 25% to v2

Telemetry

Metrics for every call, from the proxies

Error rate between any two services

Turning on mutual TLS (TLS in which both sides present a certificate, so each knows who the other is) in a running system has a migration path built in. Istio's PERMISSIVE mode lets a workload accept both mTLS and plain text while callers are still being moved into the mesh; once every caller has a proxy, STRICT mode accepts mutual TLS only. That makes "encrypt everything inside the data center" a configuration change rather than sixty code changes (Encryption in transit & at rest).

Per-request load balancing also fixes a problem plain Kubernetes Services have with long-lived connections: a connection-level balancer never moves an open connection to new instances, while a proxy chooses an instance for each request (Service discovery & registration). Traffic splitting lets the mesh run a canary without touching the deployment (Deployment strategies).

4 · The costs

Every call pays two proxy hops, and the mesh is one more system to run

Nothing in the path is free. Each call now passes through two extra proxies, and each proxy needs CPU and memory. Istio publishes its own measurements: in Istio 1.24, at 1,000 requests a second with 1 KB payloads, one sidecar with two worker threads uses about 0.20 vCPU and 60 MB of memory. Memory grows with the amount of routing configuration the proxy holds.

At the hotel platform's 400 pods, memory alone adds up:

400 sidecars × 60 MB
24,000 MB
40 node proxies × 12 MB
480 MB

Istio's measured figures: about 60 MB per sidecar, about 12 MB per ztunnel node proxy in ambient mode (below). The platform's 400 pods on 40 nodes are the example's.

Two subtler costs catch teams out:

  • Retries multiply. Istio retries a failed HTTP request twice by default, so up to three attempts per hop. If booking calls pricing, pricing calls rates, and rates calls inventory, a failing inventory can receive 3 × 3 × 3 = 27 attempts for one user request, more if the applications retry too. Set retries in one place and budget them (Timeouts, retries, backoff & jitter).
  • Tracing still needs the application. Proxies send spans for every call, but they can't know which outgoing request belongs to which incoming one. Each service must copy the trace headers across:

In Istio, applications must forward headers such as traceparent and x-request-id from each incoming request to the outgoing requests it triggers, or the spans can't be joined into one trace (Observability: metrics, logs & traces).

And the mesh itself must be operated: upgraded, scaled, monitored and understood when it misbehaves. A wrong routing rule pushed by the control plane reaches every proxy, so mesh configuration deserves the same staged rollout as code (Feature flags & configuration).

5 · Without sidecars

Newer designs move the proxy off the pod, or into the library

The per-pod sidecar is where most of the cost lives, so meshes now offer alternatives:

  • Ambient mode in Istio splits the job in two. A per-node proxy, ztunnel, written in Rust, handles only layer 4: mutual TLS, authentication, layer 4 authorization and telemetry, without parsing HTTP. Only where layer 7 features are needed, such as HTTP routing, retries or authorization by path, is a waypoint proxy added, running outside the application pods.
  • Proxyless gRPC removes the proxy altogether for gRPC services: the gRPC library speaks xDS itself, receiving routing, load balancing and mTLS configuration from the same control plane.

Model

Proxies

Per-request cost

Limits

Sidecar

One per pod

About 0.20 vCPU, 60 MB per sidecar at 1,000 req/s

Most capable; most overhead

Ambient, layer 4 only

One ztunnel per node

About 0.06 vCPU, 12 MB per ztunnel

mTLS and layer 4 policy only

Ambient with waypoints

Per node, plus shared waypoints

Waypoint ~0.25 vCPU, 60 MB

Full features where enabled

Proxyless gRPC

None

Inside the application

gRPC only; library must support xDS

Istio's own tests of proxyless gRPC found a marginal latency increase over plain gRPC, which it called a massive improvement over going through Envoy. The modes also mix: sidecar and ambient workloads can coexist in one mesh, so a platform can start with layer 4 encryption everywhere and add layer 7 features only for the services that need them.

6 · In practice

Adopt a mesh for many services and languages, not for a few

Situation

Better choice

A handful of services, one language

A shared library, or the platform's load balancer

Dozens of services, several languages, encryption required

A mesh

Mainly need mTLS and identity everywhere

A layer 4 mesh, such as ambient mode without waypoints

All gRPC, latency-sensitive

Proxyless gRPC with an xDS control plane

Traffic from the internet

An API gateway at the edge, which a mesh doesn't replace

A mesh handles traffic between services; the edge still needs a gateway for authentication of users, rate limits and public routing (Reverse proxies & API gateways). For the hotel platform, the checklist:

  • Start in permissive mode, move every service into the mesh, then switch to strict mTLS.
  • Write authorization policies by service identity, not by network address.
  • Configure timeouts and retries in one place, and remove duplicate retries from application code.
  • Forward trace headers in every service.
  • Budget the proxies' CPU and memory, and consider ambient or proxyless modes where the overhead matters.
  • Roll out mesh configuration in stages, like any other production change.

Check yourself

5 questions

1. What is the main reason to move networking features from libraries into a sidecar proxy?
2. What does a mesh's control plane do?
3. Requests go booking → pricing → rates → inventory, and the mesh makes up to three attempts at every hop. Inventory keeps failing. How many attempts can it receive for one user request?
4. Why does distributed tracing still need application changes in a mesh?
5. A platform mainly needs mutual TLS and identity everywhere, at low overhead. Which mesh model fits?

Takeaways

Remember this

  • A service mesh moves encryption, identity, retries, timeouts, load balancing and telemetry out of application code into proxies, the same for every language.
  • Proxies form the data plane; a control plane pushes them routing, discovery and certificates.
  • Meshes enable strict mTLS by service identity, per-request balancing and traffic splitting as configuration, not code.
  • Each call pays two proxy hops and each proxy costs CPU and memory; retries must be set in one place, and apps must still forward trace headers.
  • Ambient (per-node) and proxyless designs cut the per-pod cost, and mixing modes lets features be added only where needed.