Chapter 56 — Services and how traffic finds a pod
In one sentence
Pods are ephemeral — they're created, destroyed, and rescheduled with new IP addresses constantly — so a Service provides a STABLE address and name that automatically, continuously routes traffic to whichever healthy pods currently match a label selector.
Why this matters
"How does anything reliably talk to my constantly-changing pods" is one of the first genuinely confusing questions for anyone new to Kubernetes, and Services are the entire, elegant answer.
How it actually works
apiVersion: v1
kind: Service
metadata:
name: web-app-svc
spec:
selector:
app: web-app # matches the SAME label from Chapter
# 55's Deployment's pod template
ports:
- port: 80 # the Service's OWN port
targetPort: 80 # the port on the PODS to send
# traffic to
type: ClusterIP # the default -- internal-only
# (Chapter 37's networking, one layer
# up)
THE CORE MECHANISM: a Service doesn't point at specific PODS by
name or IP -- it uses a LABEL SELECTOR, continuously, live. Whatever
pods CURRENTLY have matching labels are automatically included as
valid destinations -- as pods are created, destroyed, or rescheduled
(each getting a NEW IP, Chapter 55), the Service's routing updates
automatically, with no manual reconfiguration ever needed. This is
EXACTLY Docker Compose's container-name DNS resolution (Chapter 50),
but far more dynamic -- resolving to a live, continuously-updated SET
of healthy pods, not one fixed container.
SERVICE TYPES, briefly:
ClusterIP (default) reachable only FROM WITHIN the cluster --
the normal way services talk to EACH OTHER.
NodePort additionally exposes the service on a
specific port on EVERY node's own IP --
simple, but rarely the final answer for real
external traffic.
LoadBalancer (on a cloud provider) provisions an
actual, external cloud load balancer pointing
at the service -- the standard way to expose
something to the real internet.
EVERY SERVICE also gets a DNS NAME, automatically, resolvable from
anywhere in the cluster: `web-app-svc.default.svc.cluster.local`
(or just `web-app-svc` from within the same namespace) -- Chapter
37's DNS resolution, provided automatically by Kubernetes' own
internal DNS (CoreDNS, typically).
Real-world example
The PROBLEM Services solve is explicit, foundational, and explained
directly in Kubernetes' own official documentation: pods are, by
design, EPHEMERAL. A pod can be deleted and recreated (a
Deployment's rolling update, Chapter 60; a node failure and
reschedule; a manual scale-down-then-up) and the REPLACEMENT pod
gets a COMPLETELY NEW IP ADDRESS every time. Any system that tried
to talk to "the web server" by remembering a specific pod's IP
address would break constantly, unpredictably, every single time a
pod was replaced for ANY reason -- a genuinely unworkable model at
any real scale, and precisely the problem Kubernetes' designers
(with the Borg lineage's years of real operational experience behind
them, Chapter 53) built the Service abstraction specifically to
solve from day one, not as a later addition.
This is worth sitting with as a GENERAL PRINCIPLE that recurs
throughout distributed systems, not just Kubernetes specifically:
whenever INDIVIDUAL INSTANCES of something are expected to come and
go, you need a STABLE LAYER OF INDIRECTION (a name, a label selector,
a load balancer) in front of them -- pointing directly at an
individual, ephemeral instance is a reliability bug waiting to
happen, in Kubernetes or in any other real distributed system.
Try it yourself
# Continue from Chapter 55's deployment (recreate if needed)
kubectl apply -f deployment.yaml
cat > service.yaml << 'EOF'
apiVersion: v1
kind: Service
metadata:
name: web-app-svc
spec:
selector:
app: web-app
ports:
- port: 80
targetPort: 80
EOF
kubectl apply -f service.yaml
kubectl get svc web-app-svc
# See the Service resolve to MULTIPLE pod IPs, live
kubectl get endpoints web-app-svc # every pod IP currently backing
# this service
# Prove it survives a pod being replaced
OLDPOD=$(kubectl get pods -l app=web-app -o jsonpath='{.items[0].metadata.name}')
test -n "$OLDPOD" && kubectl delete pod "$OLDPOD"
sleep 3
kubectl get endpoints web-app-svc # a DIFFERENT set of IPs now --
# the OLD pod's IP is gone, a
# NEW pod's IP is present -- but
# the SERVICE's own address/name
# never changed at all
# Reach it from inside the cluster (DNS resolution in action)
kubectl run testcurl --image=curlimages/curl --rm -it --restart=Never -- \
curl -s http://web-app-svc
kubectl delete -f service.yaml -f deployment.yaml
Common mistakes
- Hardcoding a specific pod's IP anywhere. It WILL change — always go through a Service (or its DNS name) instead.
- Forgetting the Service's
selectormust actually match the pod template's labels. A typo here silently results in a Service with ZERO healthy endpoints — always checkkubectl get endpointswhen traffic isn't reaching pods as expected. - Using
NodePortas the default choice for external traffic. It works, butLoadBalancer(on a cloud provider) or an Ingress controller (Chapter 59-adjacent territory) is almost always the better real-world answer for actual internet-facing traffic.
Check yourself
- What problem does a Service solve, specifically?
- How does a Service know which pods to route traffic to, and does that update automatically?
- What's the difference between
ClusterIP,NodePort, andLoadBalancer?
Further reading
- Docs: Kubernetes' own official "Service" documentation — thorough, and the authoritative reference for every service type and field.
- Reference:
kubectl explain service.spec,kubectl get endpoints. - Concept: search "stable indirection ephemeral instances distributed systems" for the broader pattern this chapter's lesson generalises into.