Service Discovery, Naming, and Routing Control

LESSON

Networking and Failure Models

006 30 min intermediate

Service Discovery, Naming, and Routing Control

By the end of this lesson, you will be able to...

  • Explain why a service name is not the same thing as a network endpoint.

  • Trace how discovery, routing, health, and identity combine before a request is sent.

  • Identify stale discovery as a normal failure mode, not a rare control-plane bug.

Idea in one sentence: Discovery gives a client candidate destinations, routing chooses one for this request, and identity proves the chosen endpoint is allowed to answer for the stable service name.

Core Insight

The previous lesson looked at health checks and traffic steering. A router cannot choose a healthy target until it has a list of possible targets. That list usually comes from service discovery.

Imagine the learning platform has split into services:

frontend
  -> progress-service
  -> catalog-service
  -> certificate-service
  -> recommendation-service

The frontend and gateway should not need to know that progress-service currently runs on 10.0.1.7:8080, 10.0.2.4:8080, and 10.0.3.9:8080. Those addresses are temporary. Replicas restart, deployments roll, zones partition, nodes drain, certificates rotate, and endpoint lists change.

The naive model says:

service discovery = lookup table
progress-service -> 10.0.1.7:8080

That model is useful for the first minute of learning, then it starts lying.

In a live distributed system, service discovery is not merely a phone book. It is part of the control plane that connects stable intent to temporary destinations. A client asks for progress-service because it wants "the service allowed to handle progress reads and writes." The discovery system returns candidates that might be able to serve that intent right now. Routing policy then chooses a destination for this particular request. Identity checks make sure the endpoint is actually allowed to answer as that service.

The important distinction is:

service name: stable intent
endpoint: temporary destination
discovery: candidate list
routing: request-specific choice
identity: proof that the endpoint is allowed to answer
health: evidence about whether the endpoint should receive traffic

Once you separate those meanings, several confusing network failures become easier to explain. A request can fail because the name resolved to stale endpoints. It can fail because routing chose a candidate that was healthy for reads but unsafe for writes. It can fail because an endpoint answered from the wrong rollout group. It can fail because the endpoint existed, but could not prove the right workload identity.

The useful design shift is to treat naming as a control surface, not a string replacement trick. Names, endpoint freshness, routing policy, health state, and identity all shape where traffic goes.

Stable Names, Temporary Endpoints

A service name should express what the caller means, not where the process happens to run today.

For the learning platform:

progress-service = the API that owns lesson completion state
catalog-service = the API that exposes lesson and track metadata
certificate-service = the API that issues durable completion evidence

Those names should survive normal operational change. The progress service may run three replicas this morning, six replicas during peak traffic, and two replicas during a quiet deploy. The name stays stable while the destination set changes.

An endpoint is different:

progress-service endpoints
  P1 10.0.1.7:8080  zone=A  version=v2  write_ready=true
  P2 10.0.2.4:8080  zone=B  version=v2  write_ready=false read_ready=true
  P3 10.0.3.9:8080  zone=C  version=v1  draining=true

Each endpoint is a current option, not a promise forever. P3 may be removed because it is draining. P2 may stay in the pool for reads but not writes. P1 may be the only write-ready target for a short period. A minute later the table may look different.

That difference matters because application code often wants a stable name, while infrastructure has to deal with unstable destinations. If application code hard-codes endpoints, every deployment becomes a coordination problem. If infrastructure hides too much, callers may assume that a successful lookup means "safe to use for anything," which is also false.

Plain meaning:

The name says what you want. The endpoint says where you might send this request. They are related, but they are not the same fact.

Technical name:

This is indirection. A stable service identity is mapped to a changing set of concrete network destinations.

Discovery Is Not Routing

Discovery answers:

What endpoints could currently receive traffic for this service name?

Routing answers:

Which endpoint should receive this request now?

Those are different decisions.

Suppose a gateway receives:

POST /complete-lesson
target service: progress-service
operation: authoritative write
deadline remaining: 700 ms
idempotency key: req-3017-a

Discovery might return three candidates:

P1: zone A, version v2, write_ready=true, identity=progress-service
P2: zone B, version v2, read_only=true, identity=progress-service
P3: zone C, version v1, draining=true, identity=progress-service

Routing should not randomly pick one of the three just because discovery returned them. The request is a write, so P2 is not safe even if it can serve reads. P3 is draining, so it should not receive new work. P1 is the only candidate that matches the request class.

For a different request, the right decision may change:

GET /progress-summary

That read might be acceptable on P2 if the product allows bounded staleness. The same endpoint can be a good destination for one request class and a bad destination for another.

This is the bridge to the previous lesson. Health and load balancing act on the candidates that discovery provides. If discovery gives stale or incomplete candidates, routing starts from bad evidence. If routing ignores request meaning, a good candidate list still produces bad traffic decisions.

The trade-off is where to place the intelligence. A smart gateway or service mesh can centralize routing policy. A simple client can be easier to reason about. But no design escapes the semantic question: "safe for which request?"

Check: Discovery returns endpoints P1, P2, and P3 for progress-service. P2 is read-only. Should the gateway send POST /complete-lesson to P2 because it appeared in discovery?

Think first, then reveal.

Answer: No. Discovery only says P2 is a candidate for the service name. Routing still has to check whether P2 is safe for this request class.

A Worked Discovery And Routing Trace

Follow one request through the mechanism.

Input:
  POST /complete-lesson
  service name = progress-service
  request class = authoritative write
  idempotency key = req-3017-b

Transition:
  gateway resolves progress-service
  discovery returns endpoint metadata
  gateway evaluates health, version, zone, request class, and identity

Intermediate state:
  P1 = write-ready, version v2, identity valid
  P2 = read-only, version v2, identity valid
  P3 = draining, version v1, identity valid but stale in one client cache

Output or decision:
  route the write to P1
  do not route the write to P2
  do not start new work on P3

Naive failure contrast:
  cached address sends the write to P3 because P3 was valid earlier
  round-robin sends the write to P2 because P2 is alive
  lookup-table thinking treats all discovered endpoints as equivalent

Here is the same mechanism as a table:

Step Question Example answer Decision impact
Name What service does the caller intend? progress-service Use the stable service identity
Discovery What endpoints are candidates? P1, P2, P3 Build the candidate set
Health Are candidates responsible destinations? P3 is draining Remove P3 from new work
Request class What kind of operation is this? authoritative write Reject read-only candidates
Routing Which endpoint should receive it? P1 Send the request or fail closed
Identity Is the endpoint allowed to answer? P1 proves progress identity Continue only if verification succeeds

The output is not "we found an IP." The output is a controlled decision:

send POST /complete-lesson to P1
because P1 is write-ready, current, and identity-valid

If no endpoint satisfies those conditions, the right answer may be a fast 503 Service Unavailable, a retry after backoff, or a degraded response depending on the operation. For a progress write, failing closed is usually safer than sending the request to a destination that cannot responsibly commit it.

This is why service discovery belongs in failure-model thinking. Discovery data is evidence. Routing policy interprets that evidence. Identity constrains trust. None of them is perfect truth.

Stale Discovery Is A Normal Failure Mode

Discovery systems are distributed systems too. They have caches, watches, registries, DNS TTLs, control-plane delays, resolver behavior, dropped updates, and clients that continue using old data.

Stale discovery is therefore not strange. It is one of the everyday ways a networked system fails.

Imagine this deploy:

t0: P3 is serving progress-service traffic
t1: P3 starts draining for a deploy
t2: orchestrator marks P3 not ready
t3: registry removes P3 from the ready endpoint list
t4: one gateway has a cached endpoint list that still includes P3
t5: gateway sends POST /complete-lesson to P3

The discovery system may be behaving reasonably and still produce this situation. The cache existed to reduce control-plane load and survive brief registry problems. The stale entry is the cost of that resilience.

Now P3 needs defensive behavior. It cannot assume "if traffic arrived, I must be a good target." A draining endpoint can reject new writes, finish in-flight work, or redirect only if that is safe. The gateway can refresh discovery after a failed attempt. The application can require idempotency so a retry does not create duplicate completion state.

The key lesson is that discovery should reduce bad routing, not become the only safety barrier.

There is a real trade-off:

Choice Benefit Risk
Long cache / high TTL fewer lookups, less control-plane dependency clients keep stale endpoints longer
Short cache / low TTL faster reaction to endpoint changes more lookup traffic and more sensitivity to control-plane issues
Push updates / watches fast convergence when healthy watch streams can lag, reconnect, or miss events
Proxy-managed discovery consistent policy in one layer proxy/control-plane bugs affect many services

No setting eliminates failure. The goal is to know what kind of failure each setting makes more likely.

Check: A team lowers DNS TTL to one second and claims stale discovery is now solved. What is missing from that claim?

Think first, then reveal.

Answer: TTL helps, but it does not remove client caches, resolver behavior, delayed health updates, watch lag, partitions, or endpoints that must still defend themselves when stale traffic arrives.

Identity Prevents Blind Trust

Finding an endpoint is not the same as trusting it.

Suppose discovery returns:

progress-service -> 10.0.2.4:8080

The client still needs to know whether the workload at that address is allowed to answer as progress-service. In modern service fleets, that proof may come from mTLS certificates, SPIFFE identities, service accounts, signed tokens, or platform workload identity.

Without identity, discovery can accidentally become trust:

I found an address, therefore I trust whatever answers there.

That is dangerous. A stale registry entry, misconfigured DNS record, reused IP, compromised side path, or accidental port exposure can send traffic to the wrong workload. Strong identity gives the client or proxy a way to reject the connection even when the address was reachable.

The better model is:

name requested:
  progress-service

endpoint reached:
  10.0.2.4:8080

identity verified:
  workload is allowed to represent progress-service

route allowed:
  only if health and request policy also match

Identity does not replace discovery. It also does not replace health checks. It answers a different question: "is this endpoint who it needs to be?"

The trade-off is operational complexity. Certificates expire. Identities need policy. Rotation can break traffic. Debugging a failed handshake is harder than debugging a plain TCP connection. But without identity, a discovery mistake can turn into an authorization mistake.

Design Pressure Points

A good service discovery design makes several choices explicit.

First, decide what a service name means. If progress-service means "any process with a progress HTTP port," the name is too loose. If it means "the workload identity and API surface allowed to handle progress state," the name carries useful intent.

Second, decide how clients learn endpoint changes. DNS, a registry API, Kubernetes Services, sidecar proxies, and service meshes all draw the boundary differently. The right answer depends on operational maturity, traffic volume, latency needs, and how much policy you want outside application code.

Third, decide what metadata routing needs. A bare IP list may be enough for simple read traffic. A platform that routes writes, canaries, regional traffic, and degraded reads probably needs metadata such as version, zone, readiness class, weight, and identity.

Fourth, decide what happens when discovery is unavailable. Some clients can continue with cached endpoints for a short time. Some operations should fail closed. A certificate issuance path should not blindly use old discovery data if it cannot verify authority.

Fifth, decide what the endpoint does when it receives traffic it should not have received. The defensive answer should live in the service too, not only in the router.

Observability Needs The Decision Trail

The next lesson focuses on observability because discovery and routing bugs are painful when the decision trail disappears.

For this lesson's worked trace, useful telemetry would include:

requested service name = progress-service
discovery source = gateway cache
endpoint candidates = P1, P2, P3
selected endpoint = P1
route reason = write_ready=true and identity_valid=true
rejected candidates = P2 read_only, P3 draining
endpoint version = v2
identity verification = success
cache age = 4 seconds

Without those facts, engineers may only see "one progress request was slow" or "P3 received traffic during deploy." They will not know whether the problem was stale discovery, bad routing policy, an identity mismatch, a health signal delay, or a client ignoring the gateway.

This connects the track arc:

Practice

Take a service in a system you know and write five lines:

stable service name:
request classes:
endpoint metadata needed:
identity proof:
what stale discovery would break:

For example:

stable service name: progress-service
request classes: progress reads, completion writes, admin repair jobs
endpoint metadata needed: zone, version, read_ready, write_ready, draining
identity proof: workload certificate for progress-service
what stale discovery would break: writes may go to a draining or old-version replica

Then ask one review question:

If discovery returns this endpoint, what still has to be true before routing a write there?

If the answer is only "it was in discovery," the design is under-specified.

Resources

Key Takeaways

PREVIOUS Health Checks, Load Balancing, and Traffic Steering NEXT Observability Across Network Boundaries