Kubernetes Services and Networking — the stable name over churning pods
Pods are ephemeral. The Service is what makes their reachability not care.
A Service is a stable name and a virtual IP the cluster keeps pointing at the current healthy pods. The pod might change between two consecutive calls, and the client will never notice.
§ IFrame
Pods are ephemeral. A ReplicaSet may destroy any pod at any moment and create another to take its place. A rolling deployment cycles every pod in the app three times a day. A node drains and thirty pods evict at once. Every one of those events changes the IP a client would have used, and if the client cared about pod IPs directly, every one of those events would break the client.
The Service object exists to make that not happen. A Service is a stable name and a stable virtual IP that fronts a churning set of pods, selected by label. Clients call the service; the cluster's data-plane component, kube-proxy, translates each call to the IP of one healthy pod behind the label. The pod might change between two consecutive calls, and the client will never notice, which is the whole point.
The reconciliation lesson taught how the control plane keeps pods matching a desired count. Admission taught what mutates and validates each pod before it lands. This lesson teaches what makes those pods reachable once they are running: the four Service types the exam names, the DNS layer built on top of them, the kube-proxy modes that carry the packets, and the failure shapes each mode produces when things go wrong.
§ IIFoundations — Four Service Types and What Each Reaches
Every Service is one of four types, and the type answers the question "who can call this service from where."
ClusterIP. The default. The Service gets a virtual IP from a cluster-internal range, reachable only from inside the cluster. Pod-to-pod communication uses ClusterIP; a frontend calls its backend at http://backend.default.svc.cluster.local and the request is routed to one of the backend pods. Burns opens the service-discovery chapter that grounds this lesson at exactly this altitude: ClusterIP is the shape most services in most clusters actually take, because most traffic in a cluster is between pods, not from the outside world.
NodePort. Layered on top of ClusterIP. The Service still has a ClusterIP for internal traffic, and additionally opens the same port on every node in the cluster in the range 30000-32767. A caller can reach the service at http://<any-node-ip>:30080. NodePort is what you reach for when you need external traffic and there is no cloud load balancer available, and it is what the exam expects you to describe when asked how to expose a service in a bare-metal cluster.
LoadBalancer. Layered on top of NodePort. In addition to a ClusterIP and a NodePort on every node, the Service asks the cloud provider (via a cloud-controller-manager plugin) to provision an external L4 load balancer that forwards traffic to the NodePort on every node. On AWS, this creates an NLB or classic ELB; on GCP, a network load balancer; on Azure, a public load balancer. The Service's external IP is populated by the cloud controller once the LB is ready. This is the type production internet-facing traffic uses, one Service per public entry point.
ExternalName. The odd one out. It has no selector, no ClusterIP, and no kube-proxy involvement. It is a CNAME record in the cluster's DNS pointing at an external hostname like my-db.rds.amazonaws.com. Callers inside the cluster reach an external service by the internal-DNS name, which makes the external dependency swappable without changing the caller's code. The exam names it because it exists, and because a question sometimes turns on knowing it is a DNS aliasing feature, not a routing feature.
§ IIIMechanism — Selectors, Endpoints, and kube-proxy
Three surfaces sit between a Service definition and a packet reaching a pod. Read them in order.
The selector filters pods. A Service's selector matches labels on pods. When a pod matching the selector is Ready, the endpoints controller adds its IP to the Service's EndpointSlice objects (the modern EndpointSlices object supersedes the old singular Endpoints object; both exist for compatibility). When a pod goes NotReady or is deleted, its IP is removed. The list of pod IPs behind a Service is therefore not static; it is a live view maintained by the endpoints controller, and the freshness of that view is what keeps a rolling deploy from breaking clients.
EndpointSlices are the modern shape of the data. A single Endpoints object in a very large service (thousands of pods) becomes a hot spot for every kube-proxy that watches it. EndpointSlices split the same information across multiple objects (100 endpoints per slice by default), so a kube-proxy watching a large service only re-reads slices that actually changed, not the whole set. The exam knows both terms; know that EndpointSlices are the current API and Endpoints is legacy but still populated.
kube-proxy programs the data path. kube-proxy runs on every node and watches Services and EndpointSlices from the API server. When it sees a new Service or a changed endpoint list, it programs the node's kernel to route traffic addressed to the Service's ClusterIP to one of the endpoint pod IPs. There are three modes: userspace (legacy, slow, essentially unused today), iptables (default in most clusters; kube-proxy writes iptables rules that DNAT the ClusterIP to a randomly selected pod IP), and ipvs (available where the IPVS kernel module is loaded; kube-proxy writes IPVS rules which scale better than iptables at thousands of services with hash-based lookup instead of linear rule matching). Know the tradeoff: iptables is universal and simple, ipvs is faster at scale but adds a kernel dependency, and the mode is a kube-proxy startup flag rather than a per-service setting.
§ IVWorked Example — A Backend Deployment and Its Service
The specimen is a two-object pair, a Deployment and the Service that fronts it. Read the selector match on both sides, then read kube-proxy's job.
apiVersion: apps/v1
kind: Deployment
metadata:
name: backend
spec:
replicas: 3
selector:
matchLabels:
app: backend
template:
metadata:
labels:
app: backend
spec:
containers:
- name: server
image: hedronite/backend:v1.4.0
ports:
- containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: backend
spec:
type: ClusterIP
selector:
app: backend
ports:
- port: 80
targetPort: 8080
The Deployment creates three pods, each labeled app: backend. The Service's selector: app: backend matches those three, so the endpoints controller populates the Service's EndpointSlice with three pod IPs. kube-proxy on every node writes iptables rules so that any packet whose destination IP is the Service's ClusterIP and whose destination port is 80 is DNAT'd to one of the three pod IPs on port 8080, chosen at random per new connection.
Two ports appear in the Service. port: 80 is what clients dial (http://backend/); targetPort: 8080 is the port the container listens on. The rename lets you present a public-facing port shape to callers without forcing the app to listen on port 80 in the container. The exam asks the distinction directly.
For DNS: the cluster's CoreDNS runs a Service that clients resolve backend (short name), backend.default (namespace-qualified), or backend.default.svc.cluster.local (fully qualified) against. All three resolve to the Service's ClusterIP. A pod's /etc/resolv.conf is populated at start-time with the namespace's search domain, which is why the short name works from within the namespace.
When the deploy rolls to v1.5.0, the three pods are replaced one at a time. Each new pod arrives with the same app: backend label, so the endpoints controller adds its IP as soon as it is Ready and removes the old pod's IP as soon as it goes NotReady. kube-proxy re-programs iptables on each event. A caller mid-request against the old pod completes; the next connection routes to a new pod. The Service's ClusterIP never changed. That non-change is what "stable name" means.
§ VConnection to Prior Lessons
The reconciliation lesson taught that the control plane converges pods toward desired state. This lesson names what makes those pods reachable during the convergence, because a rolling deploy is exactly the convergence step where every pod IP changes. Without kube-proxy re-programming the data path on every endpoint event, a rolling deploy would drop every client mid-flight. With it, the client sees one stable name and never learns anything happened.
The admission lesson taught what mutates and validates each pod on the way in. Services are one common target of admission: a policy might refuse a Service of type LoadBalancer without a required annotation, or mutate a Service to inject a default value for externalTrafficPolicy. Services also flow through admission, and the same failure postures the admission lesson named (audit, warn, enforce) apply here.
§ VIConnection to Today's Dev and Cert Lessons
The Dev lesson goes to the client side of this same picture from Python. Instead of asking the cluster to route your traffic to a pod, the Dev lesson watches the EndpointSlices API for a Service and maintains a client-side registry of pod IPs, which is what a client-side load balancer or a service mesh's control plane does under the covers. The Cert lesson lifts the same surface into the CKA blueprint, where the Services and Networking domain is 20% of the exam. Read the three together and you have one narrative: the Service the cluster owns, the client that watches it evolve, and the exam-tier vocabulary that lets you defend either at speed.
§ VIIClosing
A Service is a selector, a virtual IP, and an EndpointSlice the cluster keeps in sync as pods come and go. Behind every Service, kube-proxy programs the node's kernel to route traffic to one of the current healthy endpoints. Choose ClusterIP for pod-to-pod, NodePort when there is no cloud LB, LoadBalancer when there is, and ExternalName when the target is outside the cluster and you want the internal DNS to make that fact swappable.
Pick a Service in a cluster you operate. Run kubectl describe service <name> and read the endpoints list. Run kubectl get endpointslices -l kubernetes.io/service-name=<name> and count them. Delete one of the backing pods and run both again while a client is calling the service. If the client never noticed, the machinery in this lesson did its job.
Filed 2026-07-30 backfill (Wed 2026-07-29 Fajr slot) · Ops lesson · K8s deep-mastery track (day 7, visit 3)