Service Discovery
Service discovery explained: client-side vs server-side discovery, service registries, DNS-based discovery and Kubernetes services.
Service discovery is how a service finds the current, healthy instances of another service it wants to call, without hard-coded addresses. In a dynamic microservices environment instances start and stop constantly: autoscaling adds them, deploys replace them, failures remove them, and their IP addresses change every time. A list typed into a config file can't keep up.
Why it matters
In a monolith, component A calls component B with a function call: they share a process. In microservices they run on different machines, so service A needs B's IP address and port to send an HTTP or gRPC request.
Hard-coding addresses breaks as soon as services move or scale. Service B runs on 10.0.1.5 today; tomorrow autoscaling launches a new instance on 10.0.1.12, and service A has no idea it exists. Updating configuration by hand doesn't scale.
Discovery fixes this with a registry. Every service registers its location when it starts; services ask the registry where their dependencies are; and as instances come and go, the registry updates automatically. A always finds B with no manual work.
How it works
A service registry is a database of service locations. When an instance of service B starts, it registers its service name, IP address, port and health-check endpoint. When service A needs B, it asks "where is service B?" and the registry returns the available instances, for example 10.0.1.5:8080 and 10.0.1.12:8080. A picks one and sends the request.
The registry keeps the list honest with health checks: every 10 seconds it pings each instance's health endpoint, and an instance that fails three checks in a row is removed, so A only ever gets healthy instances. When a new instance starts at 10.0.1.20:8080 it registers immediately, and A's next lookup returns all three, spreading load across them automatically.
Instances get into the registry in one of two ways:
- Self-registration: each instance registers on startup, sends heartbeats, and deregisters on shutdown (Eureka, Consul agents).
- Third-party registration: the platform registers instances for them. Kubernetes tracks pods and updates endpoints automatically; cloud load balancers track target groups.
The registry must itself be highly available and is often built on a consensus store (Consul and etcd use Raft; see consensus).
Client-side discovery
The client queries the registry, gets the list of service B instances and picks one itself, so the client does the load balancing too. Netflix Eureka works this way: each service keeps a local cache of the registry, refreshed every 30 seconds. When A needs B it checks its local cache and chooses an instance, which is fast because no network call to the registry is needed per request.
Clients get full control: they can send more traffic to faster instances or pin certain users to certain servers for cache locality. The cost is complexity: every client, in every language, needs discovery and load-balancing logic.
Server-side discovery
The client calls a load balancer at a fixed address, say loadbalancer.example.com. The load balancer looks up the instances in the registry and forwards the request to a suitable one. Kubernetes uses this pattern: a Service object acts as the load balancer, clients call the service by name, Kubernetes DNS resolves the name to the service's virtual IP, and traffic is forwarded to healthy pods.
Clients stay simple: they always call one stable address. But the load balancer is an extra hop and can become a bottleneck or single point of failure unless it is itself redundant.
| Client-side | Server-side | |
|---|---|---|
| Who picks the instance | The calling service (a library) | A load balancer or proxy |
| Extra hop | No | Yes |
| Smart balancing (least requests, zone-aware, cache affinity) | Easy in the client | In the load balancer |
| Language support | Library needed per language | Language-agnostic |
| Risk | Complex clients | The load balancer can bottleneck or fail |
| Examples | Netflix Eureka + Ribbon, gRPC xDS clients | AWS internal ALB, Kubernetes Service, Consul + proxy |
A service mesh combines the two: a local sidecar proxy does client-side load balancing on behalf of an application that simply calls payments:8080.
Platform-provided discovery
Orchestration platforms such as Kubernetes and Cloud Foundry build discovery in. Deploy a service named inventory and Kubernetes creates a DNS entry; other services call inventory.default.svc.cluster.local and Kubernetes routes the request to a healthy pod. There's no separate registry to run and discovery is automatic, but it ties you to the platform: move off Kubernetes and you need a different mechanism.
Plain DNS-based discovery works with every language and tool, but DNS caching and TTLs mean clients can hold stale addresses for a while, and DNS doesn't carry health or load information. See DNS.
Registry implementations
| Registry | How it works |
|---|---|
| Consul | A standalone registry with health checking and a key-value store for configuration. Services register through an HTTP API, Consul runs TCP or HTTP health checks, and clients query it over DNS or HTTP. |
| ZooKeeper | Keeps a tree of nodes. Each instance creates an ephemeral node when it starts; if the instance crashes, its session ends and ZooKeeper deletes the node automatically. Clients watch for changes and update their routing tables. |
| etcd | A consistent key-value store (used by Kubernetes) where instances write entries with leases that expire if not renewed. |
| Netflix Eureka | A self-registration registry used with client-side load balancing and local caching. |
| Managed cloud | AWS Cloud Map (works with EC2 and ECS) and Google Cloud Service Directory (works with GKE and Compute Engine) handle registration and health checks for platform services automatically. |
Health checks
Health checks make sure the registry only returns working instances. There are three kinds, and good systems use all of them:
- Basic (liveness) checks ping an HTTP endpoint:
200 OKmeans healthy; a timeout or error means unhealthy. - Deep checks also verify the instance's own dependencies. The inventory service checks its database connection and returns
503 Service Unavailableif it can't reach it, so the registry marks it unhealthy even though the process is still running. (Be careful: if a shared dependency fails, deep checks can mark every instance unhealthy at once. See health checks and failover.) - Passive checks watch real traffic. A load balancer tracks success rates and marks an instance unhealthy if, say, 10% of its requests fail. This catches problems that periodic probes miss.
Practical concerns
- Stale entries: a crashed instance stays registered until its heartbeat or TTL expires, so clients still need timeouts and retries against another instance.
- Registry outages: clients should cache the last known instance list and keep working if the registry is briefly unavailable.
- Zone awareness: prefer instances in the same availability zone to cut latency and cross-zone data costs, falling back to other zones.
- Graceful shutdown: deregister (or fail readiness) before stopping, so no new requests arrive.
Service discovery is essential infrastructure for microservices. Together with health checks and load balancing, it sends traffic only to healthy instances and adapts automatically as services scale and deploy.
Where it’s used
Services and EndpointSlices track ready pods; kube-proxy and CoreDNS give every service a stable name and virtual IP.
A registry with health checks, DNS and HTTP interfaces, and multi-data-center support, often paired with Envoy.
A self-registration registry used with client-side load balancing in Netflix's service architecture.
Register tasks automatically and expose them via DNS or API lookups.
In an interview
- Mention how services find each other: Kubernetes DNS, a registry like Consul, or internal load balancers.
- Explain client-side vs server-side discovery and when a service mesh fits.
- Tie registry entries to health checks (basic, deep, passive) and handle stale entries with timeouts and retries.
- Note caching the instance list so discovery outages don't stop traffic.