Last Updated: January 15, 2026
In a monolithic application, components call each other through function calls or local method invocations. The address is simple: it is the same process, the same memory space.
When you break a monolith into microservices, those components become separate processes running on different machines with different IP addresses. Suddenly, calling another component requires knowing where it lives on the network.
This sounds straightforward until you consider the reality of modern deployments. Services scale up and down dynamically. Containers spin up on different hosts with different IPs each time. Instances fail and get replaced.
In a Kubernetes cluster, a service might have dozens of pods, each with its own IP, changing every few minutes. Hardcoding addresses does not work when addresses are not stable.
Service discovery solves this problem. It provides a mechanism for services to find each other dynamically, regardless of where they are running or how often they move.
Consider a simple e-commerce application with three services: Order Service, Inventory Service, and Payment Service. The Order Service needs to call both Inventory and Payment to complete an order.
In a static deployment, you might configure Order Service with the addresses of its dependencies:
This works until it does not.
In the "After Scaling" scenario, new instances of Inventory and Payment were added to handle increased load. But Order Service does not know about them. It keeps sending all traffic to the original instances while the new ones sit idle.
The problems compound:
| Scenario | What Happens |
|---|---|
| Scaling out | New instances are invisible to clients |
| Scaling in | Clients still call removed instances, getting errors |
| Instance failure | Clients call dead instances until config is updated |
| Rolling deployment | New versions get different IPs, breaking clients |
| Container orchestration | IPs change on every container restart |
Static configuration requires manual updates every time topology changes. In a dynamic environment where changes happen constantly, this is unmanageable.
Service discovery provides three core capabilities:
With service discovery, adding a new Inventory instance is automatic. The new instance registers itself, and Order Service discovers it on the next lookup.
The service registry is the central database that tracks all service instances. It stores:
When a service starts, it registers with the registry:
There are two ways services can register:
Self-Registration: The service registers itself directly with the registry.
Third-Party Registration: A separate component (registrar) monitors services and registers them.
Kubernetes uses third-party registration. The kubelet monitors pods and updates the endpoints object, which acts as the service registry.
In client-side discovery, the client queries the registry directly and chooses which instance to call.
Since the client chooses which instance to call, it implements load balancing:
| Algorithm | Description | Best For |
|---|---|---|
| Round Robin | Cycle through instances in order | Equal capacity instances |
| Random | Pick randomly | Simple, good distribution |
| Least Connections | Pick instance with fewest active connections | Varying request durations |
| Weighted | Assign weights based on capacity | Mixed instance sizes |
| Consistent Hashing | Same key goes to same instance | Caching, session affinity |
Netflix's Eureka is the canonical example of client-side discovery.
With Netflix Ribbon (client-side load balancer):
In server-side discovery, the client makes requests to a load balancer or router, which handles discovery and routing.
Kubernetes provides built-in server-side discovery through its Service abstraction.
In Kubernetes:
| Aspect | Client-Side | Server-Side |
|---|---|---|
| Client complexity | High (discovery + load balancing) | Low (just HTTP calls) |
| Network hops | 1 (direct to service) | 2 (through load balancer) |
| Infrastructure | Registry only | Registry + load balancer |
| Language support | Need library per language | Language agnostic |
| Flexibility | High (client controls routing) | Lower (central policy) |
| Failure modes | Stale cache, smart retries | Load balancer failure |
| Examples | Netflix Eureka, Consul | Kubernetes, AWS ELB |
Interview Insight: Know when to recommend each pattern. Client-side works well for internal microservices with sophisticated clients. Server-side is better for external clients or when you want to keep clients simple.
Service discovery is only useful if it returns healthy instances. Health checking ensures the registry knows which instances are actually working.
| Check Type | Purpose | Frequency | Action on Failure |
|---|---|---|---|
| Liveness | Detect crashed processes | Every 5-10s | Restart container |
| Readiness | Detect inability to serve | Every 5-10s | Remove from load balancer |
| Deep | Detect dependency issues | Every 30-60s | Alert, possibly remove |
A typical health endpoint:
Push-based (Heartbeat): Service periodically sends heartbeats to the registry.
Pull-based (Polling): Registry or load balancer periodically checks services.
Most systems use a combination: services send heartbeats AND the infrastructure performs active health checks.
HashiCorp Consul provides service discovery with additional features:
Key features:
etcd is a distributed key-value store often used for service discovery:
Key features:
Apache ZooKeeper provides coordination services including discovery:
Key features:
Kubernetes provides built-in discovery through DNS:
Choose names that are clear and consistent:
Register useful metadata with instances:
Clients can use metadata for intelligent routing, like sending crypto payments only to capable instances.
Deregister before shutting down to avoid routing to dying instances:
Registry being down should not bring down your services:
When registry returns after an outage, all clients might refresh simultaneously:
Add random jitter to refresh intervals.
Service discovery is the foundation of service-to-service communication in microservices:
Service discovery tells you where services are. But in a microservices architecture, you typically do not want every client talking to every service directly. You need a single entry point that handles cross-cutting concerns like authentication, rate limiting, and request routing.
This brings us to our next topic: the API Gateway pattern, which provides a unified interface for clients while managing the complexity of the underlying services.