Back to the map

Module 12 · Microservices patterns · Lesson 1 of 8

Service discovery

How a service finds the current addresses of another when instances come and go every minute. Registries and health, client-side versus server-side discovery, DNS-based discovery and its caching trap.

CoreMust-knowMid 11 min Updated Nov 2026

Why it matters

In a monolith, calling another module is a function call. In a fleet of services, calling another service means knowing which of its instances exist right now, which are healthy, and where they are, when autoscaling and deploys change the answer continuously. Hard-coded addresses break on the first deploy; stale caches send traffic to dead instances. Service discovery is the plumbing that makes "call the orders service" work, and every platform provides a version of it that you should be able to explain.

The idea

A service registry holds the current set of instances for each service name with their addresses, ports, health and metadata. Instances register on start and deregister on stop (or are removed when health checks fail). Callers resolve a service name to healthy instances and pick one, with load balancing. The design choices are who does the lookup and balancing, and how the registry learns about health.

Orders svcRegistryinventory → 3 healthyresolve inventory10.0.1.5, .6, .9inventory 1inventory 2inventory 3register + heartbeatcall (client-side LB)instance 3 failing health → removed from answers within seconds
Instances register with health; callers resolve a name to healthy instances. Client-side discovery balances in the caller; server-side puts a router in the path.

Client-side versus server-side

 Client-side discoveryServer-side discovery
Who resolvesThe caller queries the registry and picks an instanceThe caller sends to a stable address (load balancer, proxy, virtual IP); the router resolves and forwards
HopsOne; direct connectionTwo; through the router
Load balancingIn the client library; can be smart (latency-aware, zone-aware)In the router; one place to configure
CouplingEvery language needs a discovery-aware clientClients are plain; the platform does the work
ExamplesNetflix Eureka with Ribbon, gRPC with xDS, Consul-aware clientsKubernetes Services, AWS load balancers, Envoy or NGINX in front, service mesh sidecars

Server-side discovery has largely won because it keeps application code simple and centralises policy, with the mesh sidecar as the current form: discovery happens in a proxy beside each instance, giving client-side efficiency with server-side simplicity.

Health and freshness

A registry is only useful if it reflects reality. Instances send heartbeats or the registry probes a health endpoint (the Heartbeats lesson's trade-offs apply); unhealthy instances are removed from answers within a few seconds. Registration should be automatic on startup and deregistration on graceful shutdown, with a drain period so in-flight requests finish before an instance disappears. Separate readiness (can serve traffic) from liveness (process is alive): a starting instance is alive but not ready, and should not receive traffic yet.

DNS as discovery and its trap

The simplest discovery is a DNS name that resolves to the current instances (A records or SRV records with ports). It needs no client library and works everywhere. The trap is caching: resolvers, operating systems and language runtimes cache answers, sometimes ignoring TTLs (the JVM once cached forever by default), so callers keep sending to instances that were terminated minutes ago. Use short TTLs, make sure every layer honours them, and prefer a platform that updates DNS and load balancers together.

The platforms

Kubernetes gives every Service a stable virtual IP and DNS name; kube-proxy or the CNI routes to ready pods, and readiness probes gate membership, so discovery is invisible to the application. Consul combines a registry, health checks, DNS and an API, and can drive a mesh. etcd and ZooKeeper are the consistent stores many registries are built on. Cloud load balancers with target groups and auto-registration are server-side discovery for VM and container fleets. Whichever platform, the application should depend only on a name.

Operating it

Make discovery itself highly available (a replicated registry) and make clients degrade gracefully when it is unreachable (keep the last known good list). Add metadata to registrations (version, zone, canary flag) so routing can prefer same-zone instances or send a percentage to a canary. Watch for registration leaks (instances that never deregister) and for flapping (instances marked unhealthy and healthy repeatedly), which usually indicates an overly aggressive health check.

Trade-offs

Client-side discovery buys a hop and smart balancing at the cost of per-language libraries and policy spread across clients. Server-side buys simple clients and central policy at the cost of a hop and another component to run. DNS-based discovery is universal and simple and suffers from caching. The platform usually makes the choice for you; the design responsibility is to depend on names, register and deregister correctly, and gate on readiness.

In an interview

Keep it short and correct: "Services call each other by name. On Kubernetes that resolves to a Service with readiness-gated endpoints, so autoscaling and deploys are invisible to callers; instances drain before termination. If we add a mesh, the sidecar does discovery and balancing with zone preference." If the platform is VMs: "Consul registry with health checks, short-TTL DNS, and clients that keep the last known instance list if the registry is unreachable."

Check yourself

After a deploy replaces every instance of the inventory service, the orders service keeps failing with connection refused for several minutes, even though DNS now returns the new addresses. What is the likely cause?

Service discovery · Backplane