Service Discovery (Consul, etcd, DNS-based)
How services find each other in a dynamic fleet — because IPs change, nodes come and go, and hardcoding is a losing game.
Why it exists
In a static environment, service-A calls service-B at 10.0.0.42:8080 — configured once. In a dynamic environment (containers, autoscaling, rolling deploys), IPs change constantly. A service discovery layer solves the question: 'what are the current healthy instances of service-B?' — with automatic registration on startup, deregistration on shutdown, and health checking. It's the plumbing that makes microservices possible.
How it works
Services self-register on startup by writing (name, IP, port) into the discovery store. Clients query the store to find current instances. A health check either polls each instance or receives heartbeats; unhealthy instances are removed. Some systems (Kubernetes) use DNS as the front — you look up 'service-b.default.svc.cluster.local' and get a list of current IPs. Others (Consul, etcd) expose a richer API for service metadata + tags.
Scaling characteristics
Discovery stores are read-heavy — millions of queries/sec is common. Write load (register/deregister/heartbeat) is bounded by fleet size. DNS-based discovery scales trivially via caching (TTL trade-off vs freshness). Consul/etcd scale to thousands of services; beyond that you shard by domain.
When to use it
- Microservices architectures with autoscaling
- Kubernetes-based deployments (service discovery is baked in via DNS + endpoints)
- Service mesh setups (Consul + Envoy)
- Multi-region deployments where routing needs to know regional availability
- Any environment where instance IPs change (containers, spot instances)
When NOT to use it
- Fully static, single-region, small fleet — a load balancer with hardcoded targets is simpler
- When you can piggyback on the cloud provider's LB/DNS — often good enough
Failure modes
- Stale registrations: a dead service is still returned to clients. Mitigate with aggressive health checks + short TTLs.
- Split-brain in Consul during partition — some clients see one set of services, others another
- Cascading heartbeat failures: discovery store overload causes it to mark healthy nodes as dead
- DNS TTL mismatch: clients cache old IPs for minutes, seeing 'dead' services after failover
- Client-side caching without invalidation causes routing to gone instances
Alternatives
- Kubernetes Service + DNS — batteries-included for K8s workloads
- Client-side load balancing with a service registry (Ribbon, gRPC name resolvers)
- Envoy service mesh with xDS — dynamic config push from a control plane
- Hardcoded LB + rolling deploys — works for stable environments; falls apart with autoscale
Interview questions
- Compare DNS-based service discovery with a purpose-built registry (Consul/etcd). Pros and cons?
- Your service dies but stays registered for 30s. What's the client-side impact and mitigation?
- How does Kubernetes service discovery work under the hood (Endpoints, kube-proxy)?
- Design a multi-region service discovery that keeps regional latency low.
- What happens if the service discovery cluster itself goes down?
- How would you route traffic to services during a rolling deployment without dropping requests?
Systems that use this component
See how the real designs on this platform put service-discovery to work — concrete usage context per system.
Where did my service go? The problem microservices didn't know they had
For 30 years, service discovery was solved by DNS: your app hardcoded api.example.com, DNS returned an IP, done. Then microservices arrived. Suddenly you had 50 services × 20 instances each × ephemeral containers coming and going. IPs changed constantly. TTLs made DNS too slow. Static config broke overnight. The old solution stopped working.
The industry response was service registries — a live database of "who's alive where." Netflix Eureka (2012) let services register themselves + query for peers. HashiCorp Consul (2014) added health checks, KV, DNS interface. etcd and ZooKeeper served the same role for other stacks. Then Kubernetes (2015) baked service discovery into the platform — everything is a Service resource, backed by an in-cluster DNS + kube-proxy magic. You stopped even thinking about it.
Modern discovery is three layers: (1) DNS-based (still the most common, now with SRV records + short TTLs), (2) registry-based (Consul, Eureka, etcd — apps ask the registry directly), (3) service mesh (Istio, Linkerd — a sidecar proxy handles discovery + load balancing invisibly). Which you pick shapes everything else — the sidecars, the deployment tooling, the debugging.
The core insight: service discovery is the seam where infrastructure meets application code. Get it right, and adding a new service instance is invisible — traffic just starts flowing. Get it wrong, and you learn about a deployment failure only when 10% of requests start 500ing because DNS still points at the dead pod. The primitives are simple: health checks + registry + routing. Choosing the right composition is where design taste matters.
Historical timeline
- 1983DNS invented (RFC 882/883)Paul Mockapetris designs DNS. Solves 30 years of hostname discovery in one protocol.
- 1996DNS SRV records (RFC 2052)SRV records add port + priority discovery. Foundation for many service discovery schemes.
- 2007mDNS / BonjourApple standardizes zero-config service discovery on local networks. AirPrint, AirPlay use this.
- 2008Apache ZooKeeperYahoo builds coordination service. Used for both locks + service registry in Hadoop stacks.
- 2012Netflix EurekaNetflix open-sources Eureka — service registry + client discovery. Companion to Ribbon (client-side load balancing).
- 2013Consul + etcd emergeHashiCorp Consul (Sept 2013) + CoreOS etcd (Aug 2013). Both aim at coordinated service discovery + KV.
- 2015Kubernetes 1.0K8s ships with in-cluster DNS + Service abstraction. Service discovery becomes a platform primitive.
- 2016Envoy proxy by LyftEnvoy's xDS APIs enable dynamic service discovery for sidecars. Foundation for service meshes.
- 2017Istio + Linkerd 2.0Service mesh becomes mainstream. Discovery, routing, retries, mTLS — all in the sidecar.
- 2018AWS App Mesh + Cloud MapAWS enters service discovery. Cloud Map for registry. App Mesh for mesh routing.
- 2019Consul Connect adds mesh featuresHashiCorp evolves Consul into a full mesh with mTLS + policy. Bridge between registry + mesh worlds.
- 2020Kubernetes EndpointSlicesK8s scales endpoint discovery. Old Endpoints objects broke at 1000+ pods; slices scale linearly.
- 2022Ambient mesh (Istio)Sidecar-less service mesh emerges. Discovery + routing without a proxy per pod.
- 2024Discovery gets AI-awareService registries add metadata for AI-model versioning, routing, capacity — LLM inference nodes register per model.
Four discovery patterns — which shape fits your architecture?
Choosing between these shapes what your infrastructure looks like. There is no universal winner — it's about where you want to put the complexity:
Client-side discovery
Client Registry │ │ ├─── who is api? ──────────►│ │◄── [ip1, ip2, ip3] ──────┤ │ ├─── request → ip2 (client picks)
Products: Netflix Eureka + Ribbon, Consul client SDKs.
Health checks — what "alive" actually means
A service is alive when it can serve requests correctly. This is harder than it sounds. "Ping succeeds" is not the same as "DB queries work." Modern discovery systems support multiple health check types:
Two ways for services to register themselves
Once you have a registry, the next question is: who tells the registry about new instances?Two philosophies, both viable:
Self-registration
Service registers itself on startup, deregisters on shutdown. Sends heartbeats.
on_start: registry.register(self, tags=[v2, primary]) every 10s: registry.heartbeat() on_shutdown: registry.deregister(self)
Third-party registration
A separate registrar (Kubernetes, ECS, Nomad) watches for new instances and populates the registry.
K8s Pod created → controller sees → populates Endpoints/EndpointSlices → CoreDNS updates → clients discover Pod dies → Endpoints cleaned → discovery converges in seconds
The Kubernetes model — discovery baked into the platform
Kubernetes made service discovery so smooth that many devs never think about it. Understanding the layers helps when something breaks:
Product comparison
| Product | Model | Strength | Weakness |
|---|---|---|---|
| Kubernetes Service + CoreDNS | OSS | Baked into K8s. Zero-config for pods in-cluster. Standard. | Only works in K8s. External clients need Ingress or LB. |
| HashiCorp Consul | OSS + Enterprise | Multi-DC. Rich health checks. KV + DNS interface. Now also mesh. | License changed (2023). Extra cluster to run outside K8s. |
| Netflix Eureka | OSS (Netflix) | Battle-tested at Netflix. Client-side LB with Ribbon. | Java-centric. Eureka 2.x abandoned. Netflix moved to K8s. |
| etcd | OSS (CNCF) | Raft-based. Powers K8s control plane. Reliable. | Low-level API. Requires you to build discovery on top. |
| Apache ZooKeeper | OSS | Mature. Used by Kafka, HBase, Hadoop. Rich primitives. | JVM ops. Session complexity. Older API. |
| AWS Cloud Map | Managed | AWS-native. Integrates with ECS, EKS, Lambda. DNS interface. | AWS-only. Costs add up at scale. |
| Istio | OSS (CNCF) | Full mesh: discovery + mTLS + traffic mgmt + observability. | Complex. Envoy sidecars add resource overhead. Steep learning curve. |
| Linkerd | OSS (CNCF) | Simpler than Istio. Rust-based sidecar. Fast + light. | Fewer features than Istio. Smaller community. |
| AWS App Mesh | Managed | AWS-managed Envoy mesh. Deep AWS integration. | AWS-only. Recently declared legacy (VMware Tanzu status). |
| SkyDNS + others (legacy) | OSS | Predecessor to CoreDNS in K8s. Historical value. | Obsolete. |
| Google Cloud Service Directory | Managed | GCP-native. Deep GCP integration. | GCP-only. |
| Envoy xDS ecosystem | OSS | Config protocol that many meshes speak. Standardizes discovery integration. | Protocol, not a full product. Requires a control plane. |
How to choose: K8s shop → CoreDNS + Services. Need multi-DC → Consul. AWS-managed → Cloud Map. Full mesh → Istio (rich features) or Linkerd (simplicity). Java + old stack → Eureka. Kafka-adjacent → ZooKeeper.
12 real-world service discovery deployments
Eureka + Ribbon for a decade
For 10+ years, Netflix used Eureka (registry) + Ribbon (client-side LB) to discover services across thousands of instances. Client-side LB meant no central bottleneck. Recently migrated much of this to K8s + service mesh.
CoreDNS at >5M queries/sec
Google Cloud runs Kubernetes clusters with CoreDNS serving hundreds of thousands of pods. Every intra-cluster call resolves through CoreDNS. Zero manual config: pod → Service → EndpointSlices → CoreDNS → pod IP.
Custom RPC discovery via TChannel
Uber's TChannel RPC framework (predecessor to gRPC) included baked-in service discovery via Ringpop consistent hashing. Every service instance participated in a ring. Now migrating to gRPC + mesh.
Consul at Roblox scale
Roblox uses Consul for service discovery across ~13K servers. Every game server, matchmaker, chat service registers. Consul's multi-DC support key for regional deployments.
SmartStack (Nerve + Synapse) legacy
Before K8s, Airbnb ran SmartStack — Nerve registered services in ZK, Synapse (a local HAProxy) proxied to them. Predecessor to modern service mesh, retired for K8s + Envoy.
Finagle + ZooKeeper
Twitter's Scala RPC framework Finagle uses ZooKeeper for service discovery. Every service registers in ZK; clients query ZK for peers. Now blending with mesh approaches.
Envoy + xDS
Lyft built Envoy and pioneered the xDS discovery protocol — sidecars dynamically discover clusters + endpoints from a control plane. Today the standard mesh control-plane interface.
Cloud Map + App Mesh + ECS
AWS integrates Cloud Map (registry) with ECS (auto-registration) and App Mesh (mesh routing). One consistent story across compute types.
Massive mesh deployment
Salesforce runs Istio across many production clusters. Mesh handles discovery, mTLS, retry, canary rollouts. Multi-cluster mesh for cross-region traffic.
Simpler mesh choice
HP chose Linkerd over Istio for their internal platform. Simpler ops, smaller sidecar footprint. Trade: fewer features.
Consul + Cassandra + K8s hybrid
SkyScanner runs a mix: Consul for legacy VMs, K8s Services for new microservices. Consul-K8s connector bridges them. Migration paths matter for real orgs.
Custom internal discovery
GitHub Codespaces (which spin up VMs on demand) built custom service discovery on Envoy + Redis. Every codespace VM registers itself with its assigned user + capacity. Serves millions of ephemeral instances.
Key takeaways
- 1Service discovery has three main models: DNS-based, registry-based, or service mesh. Each has a place.
- 2Modern default: Kubernetes + CoreDNS + Services. Zero app config for in-cluster discovery.
- 3Health checks matter: separate liveness ("process alive") from readiness ("ready to serve") — DON'T check downstream DB from readiness.
- 4Two registration models: self-registration (Eureka style) vs third-party (K8s style). K8s is now dominant.
- 5Service mesh (Istio, Linkerd, Consul Connect) offloads discovery + retry + mTLS to sidecars. Adds ops overhead but reduces app-code complexity.
- 6For multi-DC: Consul. For K8s: built-in Services. For AWS: Cloud Map. Java monolith legacy: Eureka.
- 7Watch out for DNS caching. Language-level caches (JVM by default) can hold IPs for minutes — cause outages after failovers.
- 8Discovery is the seam between infra and code. Get the abstraction right and adding services is trivial. Get it wrong and every deploy is a debugging session.
References & further reading
- • Mockapetris, P. (1983). RFC 882/883. The DNS specification.
- • Gulbrandsen, A. et al. (2000). RFC 2782. DNS SRV records.
- • Netflix Tech Blog: "Netflix Shares Cloud Load Balancing And Failover Tool: Eureka!" (2012). The introduction.
- • HashiCorp Consul Docs: "Service Discovery." Excellent conceptual overview.
- • Kubernetes Docs: "Service" + "EndpointSlices." The canonical explanation of K8s discovery.
- • Klein, M. (2017). "Service mesh data plane vs. control plane." Envoy founder's explanation.
- • Istio Docs: "Traffic Management" section. The definitive service mesh reference.
- • Newman, S. (2015). Building Microservices. Chapter on service discovery is excellent.
- • Kleppmann, M. (2017). DDIA, chapters on networking + partial failure.
- • CNCF Landscape: filter for "service proxy" and "service mesh" — best overview of the ecosystem.