Service Discovery Explained — How Microservices Find Each Other
Services need to find each other and receive safe runtime configuration in changing environments.
Imagine you have 50 microservices all running in a dynamic cloud environment where instances start, stop, and move constantly. You can't hardcode IP addresses — they change with every deploy, autoscale event, or failure. Service discovery solves this: it's a registry that services register themselves in and query to find each other.
There are two main patterns. Client-side discovery: the calling service queries the registry directly and picks which instance to call (used by Netflix Eureka). Server-side discovery: the calling service sends requests to a load balancer or proxy that consults the registry and routes to the right instance (used by Kubernetes — when you call 'payments-service', DNS resolves to a ClusterIP that proxies to healthy pods).
Beyond discovery, services need dynamic configuration: feature flags, rate limits, connection pool sizes, and API keys that change without a redeploy. A config service (Consul KV, AWS AppConfig, LaunchDarkly) allows you to push configuration changes to all running instances in seconds, with audit trails and gradual rollouts. This is how large companies do 'dark launches' — ship code with a feature off, then enable it for 1% of users, then 10%, then 100%, rolling back instantly if metrics degrade.
Key concepts
Step-by-step approach
- 1
Define the naming scheme for services: pick a convention and enforce it via code review before any services are deployed.
- 2
Choose a registry technology: Kubernetes DNS + CoreDNS for K8s-native; Consul for multi-cloud or VM-based; Eureka for Spring Cloud stacks.
- 3
Implement service registration: for K8s, it's automatic via Deployment and Service objects; for non-K8s, use startup/shutdown hooks to register and deregister.
- 4
Implement health checks: the registry should deregister services that fail health checks — this ensures only healthy instances receive traffic.
- 5
Separate service discovery from runtime configuration: use a dedicated config service (Consul KV, etcd, AWS Parameter Store) for runtime values that change without redeployment.
Key trade-offs
Client-side vs. server-side discovery
client-side gives the caller full control over load balancing and retry logic but requires a discovery client library in every service. Server-side (proxy-based) is language-agnostic but adds a network hop.
Centralized registry vs. service mesh
a registry is simpler to understand and operate. A service mesh (Istio, Linkerd) provides discovery plus mTLS, circuit breaking, and observability at the sidecar level — powerful but complex.
Static config (env vars) vs. dynamic config
environment variables are simple and reliable but require a restart to change. Dynamic config (feature flags, config service) enables runtime changes but requires all services to poll or subscribe for updates.
Common pitfalls
No deregistration on shutdown: if a service crashes without deregistering, the registry keeps listing it as healthy until TTL expires — causing requests to fail for the TTL duration.
Config drift between environments: hardcoding prod-specific config in service code while managing other config in a config service creates inconsistency. Manage all environment-specific config externally.
Discovery without circuit breaking: if service B is slow or failing, service A will keep discovering it and routing traffic to it. Combine discovery with a circuit breaker to stop routing to degraded services.
Interview questions on this topic
Practice answering these with AI feedback → Start on CrackLab
Continue learning — explore 207 more topics on CrackLab DesignHub
From Classic HLD designs (Twitter, YouTube, Uber) to LLD patterns, distributed systems, databases, and company case studies.
Related topics
Choose service boundaries carefully. Microservices solve team and scale problems but add distributed complexity.
Proxies mediate traffic. Gateways add routing, auth, throttling, protocol translation, and observability.
Distribute traffic across healthy backends using L4/L7 algorithms and health checks.