Files
rootnode-academy/content/root/root-07-kubernetes-networking-config/m03-services-and-service-discovery/02-demonstration-zero-endpoints.md
T
ced4568andClaude Sonnet 5 dbcc210889 feat(root): author networking and configuration curriculum
root-07-m03-services-and-service-discovery — a new, full gold-standard-
pattern module: the Service/Endpoints/DNS request path, choosing the
right Service type. Lesson, demonstration (a Service with zero
Endpoints — the single fastest diagnostic check for this failure
class), knowledge check, guided exercise (ClusterIP vs. NodePort vs.
LoadBalancer for three real components), a real Lab
(expose-service-basics), and a reflection.

root-07-m04-ingress-networkpolicy-foundations — a new PARTIAL module:
Ingress and NetworkPolicy at the architectural level (the security-
control framing of NetworkPolicy is deliberately deferred to ROOT 09).
Lesson + knowledge check + real Labs (ingress-task, network-policy).

Co-Authored-By: Claude Sonnet 5 <[email protected]>
2026-08-26 09:06:19 -05:00

3.3 KiB

Demonstration: A Service With Zero Endpoints

This is one of the most common "it just doesn't work" incidents in real Kubernetes operation — and it produces no error message at all at the Service level.

The setup

A Service api-svc selects Pods labeled app: api. A Deployment was recently updated and its Pods now carry the label app: api-v2 instead (a rename during a refactor, deliberately or accidentally).

What you'd see from the client side

$ curl http://api-svc:8080/health
curl: (7) Failed to connect to api-svc port 8080: Connection refused

Just a generic connection failure — nothing here says "your Service's selector doesn't match any Pods."

What the Service object itself shows

$ kubectl get service api-svc
NAME      TYPE        CLUSTER-IP     PORT(S)    AGE
api-svc   ClusterIP   10.96.34.201   8080/TCP   2h

Completely normal-looking. The Service exists, has a stable IP, is not in any kind of "error" state — because from the Service's own perspective, nothing is wrong. It's doing exactly what it's configured to do: route to whatever matches its selector, which currently happens to be nothing.

Where the real evidence is: Endpoints

$ kubectl get endpoints api-svc
NAME      ENDPOINTS   AGE
api-svc   <none>      2h

This is the actual signal. <none> means the Service's selector currently matches zero Pods — this is the single fastest check for "is this a Service-to-Pod mismatch" any time something reachable-in-principle looks like a connection failure.

Confirming the mismatch

$ kubectl get pods --show-labels
NAME                     READY   STATUS    LABELS
api-abc123                1/1    Running   app=api-v2

The Pod is healthy and running — app=api-v2, not app=api. The Service's selector (app: api) matches nothing, and nothing about the Pod itself is broken.

The general diagnostic shape

curl/application-level connection failure tells you that something's wrong, but nothing about where. kubectl get endpoints <service> is the fastest way to distinguish "the Service has no matching Pods" from "the Pods exist and are ready but something else is wrong" — reach for it early whenever a Service-fronted connection fails for no obvious reason.

Instructor Notes

Demo script: Live-create this exact mismatch (a Service with one selector, a Deployment with a slightly different label) and walk through kubectl get service -> kubectl get endpoints -> kubectl get pods --show-labels in front of the class, narrating what each command is and isn't telling you.

Approximate timing: 8-10 minutes live; 5-7 minutes self-paced.

Common misconception to preempt: students often check the Service object first and, seeing nothing obviously wrong, assume the problem must be elsewhere entirely (DNS, firewall, the application itself) — reinforce that kubectl get endpoints is the specific, fast check for this exact failure mode, before reaching for anything more complex.

Optional extension: ask what kubectl describe service api-svc would additionally show compared to kubectl get service (answer: the same selector the Service is using, which lets you directly compare it against kubectl get pods --show-labels's actual Pod labels side by side, rather than inferring the mismatch).