Part 10 · Architecture & Deployment
Service Discovery: How Services Find Each Other
A hard-coded IP address is out of date within minutes once containers auto-scale and redeploy all day. How services find the current, healthy addresses of each other: registries, health checks, client-side vs server-side discovery, DNS, service meshes, and shutting down without dropping requests.
On this page
The Problem
The order service needs to call the payment service. To call it, you need its address. An IP address is the number that identifies a computer on a network, like 10.0.4.17. In the old days, you would write the address in a config file (a file of settings):
PAYMENT_SERVICE_URL=http://10.0.4.17:8080But in a modern system:
- the payment service runs as 12 containers (small isolated boxes that each run one program), and auto-scaling adds and removes them all day (post 7). Auto-scaling means the platform changes the number of copies by itself,
- containers get new IP addresses every time they restart or move to another machine,
- a deploy replaces every instance with new ones,
- some instances are unhealthy and shouldn't get traffic.
An IP address written in a file is out of date within minutes. Service discovery is the way services find the current, healthy addresses of the other services they need.
The Core Idea
Think about finding a doctor in a large hospital.
- You do not memorise which room Dr. Mehta is in today. Doctors move between rooms, shifts change, and some are on leave.
- Instead, you ask the reception desk, which keeps an up-to-date directory: "Cardiology? Dr. Mehta is in room 214 today; Dr. Rao is in room 216."
- When a doctor starts their shift, they check in with reception. When they leave, they check out. If a doctor does not answer the pager, reception stops sending patients to that doctor.
Service discovery works the same way:
- A service registry is a database that lists which instances of each service exist, where they are, and whether they are healthy. An instance is one running copy of a service.
- Services register when they start and deregister when they stop.
- Health checks are small tests that remove instances that stop answering.
- Callers look up the service by name ("payment-service") instead of by IP.
How It Works
The service registry
Service registry
┌──────────────────┬────────────────────┬──────────┐
│ service │ instance address │ health │
├──────────────────┼────────────────────┼──────────┤
│ payment-service │ 10.0.4.17:8080 │ ✅ │
│ payment-service │ 10.0.7.23:8080 │ ✅ │
│ payment-service │ 10.0.2.91:8080 │ ❌ failing│
│ order-service │ 10.0.5.44:8080 │ ✅ │
└──────────────────┴────────────────────┴──────────┘The registry must be highly available, which means it must almost always be up. If it is down, nobody can find anybody. So registries usually run as a cluster (a group of servers that keep copies of the same data). They often use consensus, a way for servers to agree on one answer. Raft is a common consensus algorithm (post 24).
Popular registries:
- Consul (HashiCorp): service registry, health checks, DNS interface and key-value store.
- etcd: a key-value store (a simple database of names and values) that always gives correct data. Kubernetes keeps its state in it.
- Apache ZooKeeper: an older coordination service, used by many big-data systems.
- Netflix Eureka: built for AWS. It favours availability over consistency, which means it keeps answering even if its data may be slightly old.
- Kubernetes' built-in discovery (Services + DNS), the most common today. Kubernetes is a system that runs and manages containers. DNS is the system that turns names into IP addresses.
- Cloud services: AWS Cloud Map, and similar.
How instances get registered
Self-registration. Each instance adds itself to the registry when it starts. It sends heartbeats (a small "I am alive" message every few seconds) and removes itself when it stops.
- ✅ Simple.
- ❌ Every service needs registry code, and a crashed instance cannot remove itself. A heartbeat timeout handles that. A TTL (time to live) is the time after which an entry with no heartbeat is deleted.
Third-party registration. The platform registers instances for you. For example, Kubernetes knows exactly which pods are running and ready. A pod is the unit that runs your containers. Kubernetes updates the service's list of addresses (its endpoints) by itself.
- ✅ No code in the services, and always accurate.
- This is how most modern platforms work.
Health checks
Only healthy instances should receive traffic:
- the registry or platform polls a health endpoint. This means it calls a URL like
/healthor/readyagain and again to ask "are you OK?", - or instances send heartbeats within a TTL,
- instances that fail are removed from the list until they recover.
As in post 12, keep health checks focused on the instance itself. A deep check also tests shared dependencies, like the database. If that dependency fails for a moment, such a check can remove every instance at once.
Client-side discovery
The caller asks the registry for the list of instances and picks one itself. Load balancing means spreading requests across instances, and here the client does it:
- where is payment-service?
- [10.0.4.17, 10.0.7.23] (healthy only)
- POST /chargepicked by round robin (take turns) or least-loaded
- 200 OK
- ✅ No extra hop (no extra server in the path), and the client can use smart balancing, like choosing the instance with the fewest requests or the one in the same zone.
- ✅ Works well with gRPC (a fast way for services to call each other, post 31). gRPC keeps one connection open for a long time, so it needs balancing for each request.
- ❌ Every client needs discovery and balancing code, in every programming language you use.
- Examples: Netflix's Eureka + Ribbon setup (Ribbon is now in maintenance mode), and the name resolvers and balancers built into gRPC.
Server-side discovery
The caller sends requests to a load balancer or router that has a fixed address. The router looks up the registry and passes the request on:
- Order service
- Load balancer / routerstable address
- Registrylooked up by the router
- Payment instance
- ✅ Clients stay simple. They only call one address.
- ✅ Centralised control of balancing and routing.
- ❌ An extra network hop, and the router must itself be highly available.
- Examples: AWS load balancers with target groups (lists of instances), and Kubernetes Services. In Kubernetes, kube-proxy sends traffic for a virtual IP (an address that belongs to no single machine) to the pods.
DNS-based discovery
This is the simplest way: you look up the service by name through DNS and get back IP addresses. DNS (Domain Name System) is the system that turns names into IP addresses.
payment-service.payments.svc.cluster.local → 10.96.12.40 (Kubernetes Service IP)In Kubernetes:
- a Service gives a group of pods a name and a virtual IP that do not change,
- cluster DNS (CoreDNS, the DNS server inside Kubernetes) turns
payment-serviceinto an address when you are in the same namespace (a named group of resources). It also understands the full namepayment-service.payments.svc.cluster.local, - traffic to the Service IP is spread across the pods that are ready,
- "headless" Services (Services without a virtual IP) return the IP of each pod. They are used for client-side balancing or for stateful apps (apps that keep data, like databases).
DNS pitfalls (post 1):
- Caching: clients and language runtimes may keep (cache) DNS answers longer than you expect, and keep calling old instances that are already gone. Keep TTLs low, and check how your runtime caches DNS.
- DNS gives addresses, not health or load. It relies on the platform keeping records up to date.
- SRV records are a kind of DNS record that can also include a port number, but not every client supports them.
Service mesh discovery
A service mesh (Istio, Linkerd, Consul) is a layer that manages the traffic between services. In a mesh, a sidecar proxy runs next to each service. A sidecar is a small helper program, and it is often Envoy, a popular proxy. The mesh's control plane is the part that manages all the sidecars. It watches the registry (usually Kubernetes) and sends up-to-date address lists to every sidecar. Envoy calls these APIs xDS.
- 1Order servicecalls
http://payment-service - 2Envoy sidecarknows every healthy endpoint, balances, retries, encrypts
- 3Envoy sidecarverifies the caller's identity
- 4Payment service
- 5Control plane (Istio)pushes endpoints, routes and certificates to every sidecar via xDS
Your application code just calls http://payment-service. The sidecar handles discovery, load balancing, retries, timeouts, circuit breaking (stopping calls to a failing service), mTLS (both sides prove their identity, post 51) and metrics (post 46).
Making deploys and shutdowns safe
- t = 0mark not-ready — the readiness probe fails
- 0–5 spreStop sleep while discovery and load balancers stop routing to it
- 5–30 sdrain in-flight requests; finish open connections
- t = 30 sthe process exits cleanly (terminationGracePeriodSeconds)
Sometimes an instance must be removed, for example during a deploy, when scaling down, or when a node (a machine) is drained. Do these steps:
- Mark it not-ready, so it's removed from discovery and load balancers first,
- wait for discovery to update everywhere (a few seconds),
- drain the instance. This means you let the requests that are still running (in-flight requests) and the open connections finish,
- then stop the process.
In Kubernetes, you use readiness probes (a check that tells Kubernetes if a pod can take traffic), a preStop hook (a command that runs just before the pod stops) with a short sleep, and a sensible terminationGracePeriodSeconds (the maximum time a pod gets to stop). Without these, callers keep sending requests to a pod that is already shutting down, and users see errors on every deploy.
Trade-offs
- Client-side discovery: no extra hop and smart balancing, but logic in every client and every language.
- Server-side discovery: simple clients, but an extra hop and another component to keep available.
- DNS-based: universal and simple, but caching delays, and no health or load information.
- Service mesh: powerful, consistent and language-independent, but significant complexity, resource overhead (a sidecar per pod) and a learning curve.
- Strongly consistent registries (etcd, Consul, ZooKeeper) are accurate, but they may refuse updates during a partition (when the network splits the servers into groups that cannot talk). Availability-first registries (Eureka) keep serving lists that may be out of date (CAP, post 27).
In the Real World
Kubernetes Services and DNS. For most teams today, service discovery is simply Kubernetes: every Service gets a DNS name, and traffic is routed to ready pods. Most developers use it every day without thinking of it as "service discovery".
Netflix Eureka. Netflix built Eureka as its service registry on AWS, designed to stay available during network problems even if its data is slightly stale. Netflix decided that calling an instance that might be gone (and trying again) was better than having no directory at all. This is a deliberate AP choice (post 27). AP means "available and partition-tolerant" and gives up strict consistency.
Airbnb's SmartStack. Airbnb built and open-sourced SmartStack (around 2013), which used ZooKeeper plus a local HAProxy (a load balancer program) on each machine. It was an early version of the sidecar idea that service meshes later made common.
HashiCorp Consul is widely used for service discovery across mixed environments (VMs, containers and multiple data centres), with health checks and a DNS interface so even legacy apps can discover services by name.
Envoy's xDS APIs have become a common standard in practice for sending discovery and routing settings to proxies. They're used by Istio and many other control planes.
Interview Questions
Q1Why do microservices need service discovery?
Instances come and go constantly with auto-scaling, deploys and rescheduling, each with a new IP, and some are unhealthy. Callers need an up-to-date list of healthy instances addressed by service name, not hard-coded IPs.
Q2Client-side vs server-side discovery?
Client-side: the caller queries the registry and load-balances itself — no extra hop and smarter balancing, but logic in every client library. Server-side: the caller hits a stable load balancer or router that does the lookup — simple clients, at the cost of an extra hop and a component to keep available.
Q3What are the limitations of DNS-based discovery?
Clients and resolvers cache answers, so changes and failed instances take time to disappear; DNS carries no health or load information; and many clients resolve once and reuse the connection. Kubernetes avoids some of this with stable virtual Service IPs.
Q4How does a service mesh handle discovery?
A control plane watches the platform's endpoints and pushes them, plus routing rules and certificates, to a sidecar proxy next to every service. The app calls a name; the sidecar handles discovery, balancing, retries, timeouts and mTLS consistently in every language.
Q5How do you avoid errors during deploys with discovery?
Fail readiness first, wait for discovery and balancers to stop routing to the instance (a preStop delay), drain in-flight requests, then exit — with a termination grace period long enough for all of that.
Q6Should a service registry be CP or AP?
It depends on what's worse. Netflix's Eureka is AP — a possibly stale list beats no list, and clients retry. Consul, etcd and ZooKeeper are CP — accurate, but they may refuse updates during a partition.
Key Takeaways
- In dynamic environments, IPs change constantly. Service discovery lets services find current, healthy instances by name.
- A service registry (Kubernetes, Consul, etcd, Eureka) tracks instances. Registration is self (with heartbeats and TTLs) or third-party (the platform does it).
- Client-side discovery means the client picks an instance. Server-side discovery means a router or LB picks. DNS is the simplest interface, and service meshes push endpoints to sidecars.
- Health checks keep bad instances out. Keep them shallow, and beware DNS caching.
- Deregister before shutdown: readiness → wait → drain → stop, so deploys don't cause errors.
Further Reading
- microservices.io patterns "Client-side discovery", "Server-side discovery", "Service registry" and "Self registration"
- The Kubernetes documentation "Service" and "DNS for Services and Pods"
- The HashiCorp Consul documentation on service discovery
- The etcd documentation
- The Envoy documentation on service discovery and the xDS protocol
- The System Design Primer on GitHub (section "Service discovery")
Published by Jainik · Last updated · How these lessons are made
Keep learning
Related series