Service Discovery in Microservices: How It Works

Updated on
11 min read

Service discovery in microservices is the mechanism that lets one service find another when instances are created, removed, moved, or replaced. It matters because a distributed application cannot safely rely on fixed server addresses: deployments change the set of healthy instances, containers receive new network identities, and failures can remove a target without warning. This guide explains the lookup problem, the main discovery models, health checking, request routing, and a practical Kubernetes example.

What Is Service Discovery?

Service discovery is a directory and lookup process for network services. A caller asks for a logical name such as payments, and the discovery system returns one or more usable endpoints such as an IP address and port. The caller can then send a request without knowing which machine or container currently runs the service.

The name is intentionally more stable than the instance. A service may have three instances at 10.0.2.11, 10.0.2.12, and 10.0.2.13 today, then have a different set after a rollout. Discovery hides that churn behind a contract that callers can use consistently.

Kubernetes implements this model with a Service resource: it provides a stable virtual endpoint for a changing set of Pods. The Kubernetes Service documentation describes selectors, virtual IPs, DNS names, and headless Services. Kubernetes is also an example of a broader cloud-native platform whose project documentation treats networking and service identity as core cluster concerns.

Service discovery is not the same thing as authentication, authorization, encryption, or load balancing in general. It may provide endpoint selection and health information, but the application still needs identity controls and a protocol with explicit failure behavior.

The Problem Service Discovery Solves

In a monolith, a function call points to code in the same process. In a small two-tier application, a database hostname may remain stable for a long time. Microservices make the network boundary part of nearly every feature, and their instances are deliberately more dynamic:

  • A scheduler starts additional replicas when load increases.
  • A deployment replaces old instances with new ones.
  • A node failure makes several addresses unusable.
  • A cloud or container platform assigns addresses from a pool.
  • Multiple environments need the same logical service names with different endpoints.

Hard-coding an address couples the caller to deployment details. Manually maintained configuration can work for a small static system, but it becomes slow and error-prone when instances change frequently. A load balancer can provide a stable address, but it still needs a source of truth for which backends exist and which are healthy.

Without discovery, teams often create one of two failure modes. The first is a stale endpoint: the caller keeps trying an instance that was deleted or moved. The second is an incomplete endpoint list: a newly added instance receives no traffic because clients have not been updated. Discovery makes membership a runtime concern while leaving routing policy explicit.

How Service Discovery Works

A typical request path has four stages:

  1. Registration: A service instance announces its logical name, address, port, metadata, and expiration or health information.
  2. Lookup: A caller queries a registry or a naming system for instances of that service.
  3. Selection: The caller or a proxy chooses an endpoint using locality, health, load, version, or another policy.
  4. Refresh and failure handling: The result expires or is refreshed, and the caller retries discovery or removes failed endpoints when conditions change.

The registry is a control-plane component. The request itself is the data-plane operation between services. Keeping that distinction clear helps explain why a registry outage does not necessarily have to stop every request: clients may cache recently retrieved endpoints, while new instances may be slower to discover.

Client-side and server-side discovery

There are two common placement models:

Feature Client-side discovery Server-side discovery
Lookup location Application library or client code Proxy, gateway, or load balancer
Routing decision The caller selects an instance The intermediary selects an instance
Application complexity Higher; each language needs a compatible client Lower; clients use one stable endpoint
Policy visibility Fine-grained and close to the caller Centralized and easier to standardize
Failure surface Registry and client cache affect callers directly Proxy capacity and availability become important
Useful when Teams need custom routing or low-hop calls Many languages should share routing and security policy

Client-side discovery can avoid an extra network hop. A client library can select an endpoint using a round-robin, least-request, zone-aware, or weighted policy. The trade-off is that every client must implement timeouts, caching, health interpretation, and compatible updates correctly.

Server-side discovery puts that logic in a reverse proxy, sidecar, ingress controller, or load balancer. Applications call a stable virtual address, and the intermediary resolves the service and forwards the request. This centralizes policy but introduces another component that must be scaled, secured, and observed.

DNS-based discovery

DNS is a natural naming layer for services. A caller resolves a name such as catalog.internal, receives an address, and connects to it. DNS-based discovery is simple, language-neutral, and widely supported. It can work well when a platform owns records and clients respect the intended time-to-live (TTL).

The DNS concepts in RFC 1034 define the domain name system’s distributed naming model and resolver behavior. DNS answers are not a real-time health stream, however. Caches may retain an answer until its TTL expires, and a successful DNS lookup does not prove that the application is ready to handle a request. DNS should therefore be paired with endpoint health checks, connection timeouts, and retry limits.

Registry-based discovery

A registry stores service membership and metadata directly. Instances register when they start, renew a lease while alive, and are removed when their lease expires or an active health check fails. A registry can support richer queries than DNS, such as selecting a version, region, zone, or capability.

Tools such as HashiCorp Consul provide service catalog and discovery features alongside health checks and networking integrations. Registry-backed discovery is useful when a platform needs explicit registration, metadata, and operational inspection. It also adds a stateful dependency whose access, consistency, backups, and upgrade path must be designed.

Components and Key Concepts

Service identity and endpoint records

An endpoint record normally contains a logical service name, address, port, protocol, and status. Useful metadata can include version, region, availability zone, instance ID, or supported API capability. Keep identity separate from ephemeral infrastructure facts: inventory is a useful service identity, while a pod name is usually not.

Registration and leases

Registration can be explicit, where the service calls the registry, or platform-managed, where an orchestrator derives membership from workload state. A lease or heartbeat prevents an abandoned record from living forever. Lease duration is a trade-off: short leases remove failed instances quickly but create more control-plane traffic and can eject healthy instances during a temporary pause.

Readiness versus liveness

A process can be alive but unable to serve traffic. A liveness check asks whether it should be restarted; a readiness check asks whether it should receive requests. Discovery and routing should generally use readiness semantics. A service that is warming a cache, applying a migration, or losing a dependency may be alive while temporarily unready.

Health checks

Health checks may be performed by the registry, a proxy, or the caller. A useful check tests the condition required for traffic, not merely that a TCP port is open. Checks should have bounded timeouts and should avoid causing expensive side effects. Health status is an input to routing, not proof that every request will succeed.

Consistency and staleness

Discovery data is often eventually consistent. One client may briefly see an instance after another component has removed it. This is normal in distributed systems and must be handled with connection timeouts, bounded retries, idempotency, and clear error reporting. A discovery system should not promise stronger freshness than its architecture can deliver.

Naming scope

Names should have a clear scope. A short name might work inside one namespace, while a fully qualified name is safer across environments or clusters. Avoid using environment-specific addresses in application code; inject the environment’s naming configuration at deployment time.

Practical Kubernetes Example

The following Deployment runs two replicas and labels them as catalog. The Service selects those Pods and gives callers a stable DNS name. The Pods may be replaced without changing the name used by the caller.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: catalog
spec:
  replicas: 2
  selector:
    matchLabels:
      app: catalog
  template:
    metadata:
      labels:
        app: catalog
    spec:
      containers:
        - name: catalog
          image: example/catalog:1.4.0
          ports:
            - name: http
              containerPort: 8080
          readinessProbe:
            httpGet:
              path: /ready
              port: http
            periodSeconds: 5
---
apiVersion: v1
kind: Service
metadata:
  name: catalog
spec:
  selector:
    app: catalog
  ports:
    - name: http
      port: 80
      targetPort: http

From another workload in the same namespace, an application can use the catalog Service name as its endpoint. Kubernetes updates the Service’s backing endpoints as Pods become ready or unready. A cluster-aware client can also use the service’s DNS name, while a headless Service can expose individual Pod addresses when the application needs to perform its own selection.

Verify the object and its endpoint membership with:

kubectl apply -f catalog.yaml
kubectl get service catalog
kubectl get endpointslice -l kubernetes.io/service-name=catalog
kubectl run discovery-check --rm -it --image=curlimages/curl -- \
  curl --fail --max-time 3 http://catalog/health

Do not treat a successful kubectl get service as proof that requests will work. Confirm that the selector matches the Pod labels, that the readiness probe passes, that the target port is correct, and that a NetworkPolicy or firewall is not blocking the path. For the network boundary between containers, see the Docker networking drivers guide.

Reliability and Security Practices

Use a deadline on every service-to-service request. A discovery lookup should not be allowed to consume the entire request budget, and a failed endpoint should not trigger unlimited retries. Retry only operations that are safe to repeat, add backoff with jitter, and preserve a correlation ID so operators can trace the original request.

Cache discovery results for a bounded period, but do not cache them forever. A cache can keep an application operating during a short registry interruption, while an unbounded cache can route traffic to retired instances. When the connection fails, refresh discovery before retrying, and use circuit breaking to prevent a failing dependency from exhausting caller resources.

Protect the control plane as carefully as the data plane. Restrict who can register a service, authenticate registry clients, encrypt discovery traffic where it crosses trust boundaries, and audit changes to service records. Discovery metadata can reveal internal topology, so it should not be exposed to untrusted callers.

Service discovery also does not replace authorization. A caller that can resolve payments is not automatically allowed to invoke every operation on it. Use service identity, mutual TLS, signed credentials, or an equivalent authorization mechanism appropriate to the environment. The NIST container security guidance places orchestration, image, host, and runtime controls in a broader security model; naming is only one part of that model.

Common Misconceptions

“A service name guarantees a healthy response”

No. A name maps to an endpoint set, and an endpoint can fail between lookup and connection. Readiness checks reduce bad routing but cannot eliminate races. Clients still need timeouts and failure handling.

“DNS and a registry are interchangeable”

They solve related naming problems but expose different trade-offs. DNS is broadly compatible and cache-oriented; a registry can carry leases, health state, and metadata. Either can be appropriate when its freshness, failure, and operational model are understood.

“Service discovery is a load balancer”

Discovery answers “where can I find this service?” Load balancing answers “which available instance should receive this request?” A platform can combine them, but separating the concepts makes it easier to test policy, observe failures, and choose client-side or server-side routing.

TBO Editorial

About the Author

TBO Editorial writes about the latest updates about products and services related to Technology, Business, Finance & Lifestyle. Do get in touch if you want to share any useful article with our community.