Traffic and messaging
Service discovery and service meshes
How clients learn which instances exist right now, the three discovery styles, client-side balancing, and what a service mesh costs.
A load balancer is only useful if it knows which instances exist right now. Instances come and go with autoscaling, deploys and crashes. Service discovery is how a balancer or client learns the current list. A service mesh goes further: it puts a small proxy beside every instance, so balancing, retries and encryption happen there instead of in each app.
Concept
A sidecar is a second process that runs next to the app on the same machine or pod. In a mesh, the sidecar proxy handles all traffic in and out, so the app only talks to localhost.
The big idea
Discovery comes in three common styles.
- DNS-based. The service name resolves to a list of instance IPs. It is simple, but many HTTP clients resolve once at startup and never again, so they keep old addresses.
- Registry-based. Instances register in Consul, etcd or ZooKeeper and send a heartbeat with a time-to-live (TTL). If the heartbeats stop, the entry expires. Clients watch the registry for changes.
- Platform-based. Kubernetes tracks pods in EndpointSlices. A
ClusterIPService is a virtual IP that kube-proxy turns into rules on every node. That balances connections, not requests.
With client-side load balancing, there is no middle box. The client library gets the endpoint list from discovery and picks a backend itself. gRPC supports this natively. You save a hop and a single point of failure, and you can balance per request even on HTTP/2. The cost is that balancing logic lives in every client, in every language, and a fix means redeploying every caller.
Walking through it
Here is a call between two services in a mesh.
App A never picks a backend. Sidecar A asks discovery for endpoints, balances the call, encrypts it and sends it. The control plane pushes config to every sidecar. The price is an extra proxy hop on each side, often 1 to 3 ms at p99 (the latency that 99% of calls beat). You also pay memory for each sidecar, and the control plane becomes critical infrastructure. Istio's newer ambient mode replaces per-pod sidecars with one proxy per node, which cuts that cost.
| Option | Who balances | Good for | Costs |
|---|---|---|---|
| DNS discovery and a plain client | The client, from a cached IP list | Simple setups | Stale addresses |
| Client-side library | The client | Few languages, gRPC or HTTP/2 | Logic in every client |
| Service mesh | A sidecar beside each instance | Dozens of services in several languages | Extra hop, memory, control plane |
A mesh earns its keep at dozens of services in several languages. For five services in one language, a good HTTP client library is usually enough.
Common mistakes
- Trusting DNS alone. Clients cache resolved IPs and miss new or removed instances.
- Assuming a ClusterIP Service balances requests. It balances connections, so many HTTP/2 requests on one connection land on one pod.
- Adopting a mesh too early. A mesh adds a hop, memory and a control plane. Add it when the number of services and languages makes per-client logic painful.
Check yourself
-
Why can an app that resolved DNS once keep sending traffic to dead instances?
Its HTTP client cached the IP and never resolves again, so it can't see removed or new instances.
-
Why is a ClusterIP Service a weak tool for balancing HTTP/2 traffic?
It balances connections. All the requests on one persistent connection go to the same pod.
Key takeaways
- Discovery answers "which instances exist now?" Each style trades simplicity for freshness.
- Client-side balancing removes a hop but puts the logic in every client.
- A service mesh moves balancing, retries, timeouts and mTLS into a sidecar beside each instance.
- A mesh adds a hop, memory and a critical control plane, so adopt it when the size of the system justifies it.
Further learning
Next: global load balancing, which covers how a user's request reaches the right region before the regional balancers take over.
Related
Load balancing and proxies
How a load balancer chooses which server gets each request, and how a badly set up balancer can cause the outage it was meant to prevent.
Consensus and Raft
How a cluster agrees on one value or one log despite crashes and partitions, how Raft works, and where etcd, ZooKeeper and consensus-backed databases fit.
API design
An API is a contract that outlives the code behind it. Design the styles, methods, pagination, errors and async flows so changes stay additive.