S066 · Kubernetes I — Pods, Deployments, Services
Kubernetes without the mysticism — Pods run containers, Deployments keep N of them alive, Services give them a stable IP. Ship a real app to a local cluster in 25 minutes.
🎯 Understand Pod, Deployment, Service, and Ingress; deploy a 2-replica app to kind and hit it through a Service.
Why this session exists
Kubernetes is the operating system of the cloud in 2026 — every major cloud, most fintechs, and half the startups run on it. The reputation for complexity is deserved, but 80% of what you'll do sits on four objects: Pod, Deployment, Service, Ingress. Learn those four crisply and everything else (HPA, ConfigMap, StatefulSet) is decoration.
- Explain Pod vs Deployment vs Service vs Ingress to a friend in under two minutes.
- Write valid YAML for a 2-replica web app with a ClusterIP service and read it with kubectl.
- Deploy to a local kind cluster, kill a pod, and watch the Deployment respawn it.
- Diagnose the top-3 K8s beginner traps: label mismatches, unbounded resources, and CrashLoopBackOff loops.
Prerequisites
- S065 · Docker — you can build and run container images.
- S062 · Load balancers — reverse-proxy + health-check mental model.
- S060 · Linux fundamentals — processes, network namespaces.
(a) Intuition · 5 min
A big hotel has rooms (single occupants), suites (a few people who share a bathroom), a housekeeping supervisor who guarantees ‘this floor always has 20 rooms clean and ready’, and a concierge at the desk who tells guests ‘Alice is in room 412’ without the guest needing to know Alice's room number in advance.
Rooms change. Guests come and go. But the hotel's public interface — call the concierge, get connected — never changes. That's the magic that lets a hotel with 500 rooms present a single phone number.
A Pod is a suite: 1 or 2 tightly-coupled containers that share network + volumes. A Deployment is the housekeeping supervisor: it says ‘keep 3 pods of this shape alive at all times’, and rolls out new versions gradually. A Service is the concierge: a stable IP and DNS name that routes to whichever pods are healthy right now, even as pods churn.
An Ingress is the hotel's front door — the single public HTTPS entry that fans out to internal Services based on hostname and path.
The four objects that carry 80% of the mental load
- Pod — the smallest deployable unit. 1+ containers sharing a network namespace + volumes. Almost never created directly; you create Deployments that create Pods.
- Deployment — declares ‘N replicas of this pod template’. Handles rolling updates, rollbacks, and self-healing (kills unhealthy, spawns replacements).
- Service — a stable virtual IP + DNS name in the cluster. Uses label selectors to find backing pods. Types: ClusterIP (internal), NodePort (a fixed port on every node), LoadBalancer (asks the cloud for an external LB).
- Ingress — the L7 HTTP router at the cluster edge. Maps hostnames + paths to Services. Requires an Ingress Controller (nginx, Traefik, Istio) doing the actual work.
A quick history so the ecosystem makes sense
- 2003Google Borg (internal)Google's cluster manager runs everything at Google for a decade. Kubernetes is its open-source spiritual successor.
- 2014Kubernetes 0.1 released · GoogleJoe Beda + Brendan Burns + Craig McLuckie open-source ‘Seven of Nine’ — codenamed Kubernetes.
- 2015Kubernetes 1.0 · CNCF foundedK8s donated to the CNCF. Docker Swarm and Apache Mesos are still contenders.
- 2017K8s wins the orchestrator warAWS launches EKS, DockerCon adopts K8s, Mesos fades. K8s is the standard.
- 2020Dockershim removal announcedKubernetes 1.24 (2022) drops Docker as the container runtime; containerd takes over. Docker the CLI still works, kubelet no longer needs it.
(b) Visual walkthrough · 15 min
The object hierarchy — how the four pieces connect
Reading the diagram: the Service is not a proxy in the pod path — it's a virtual IP that kube-proxy installs as iptables/IPVS rules. When you curl app-svc, the packet gets DNAT'd to one of the backing pods. Zero application-level indirection.
The lifecycle of a Deployment rollout
You send desired state (image: myapp:v2) to the API server, which stores it in etcd.
Creates a new ReplicaSet for v2, sets desired replicas gradually (maxSurge/maxUnavailable rules).
Assigns each new pod to a node with capacity + matching affinity/taints.
Registers pod status back to the API server.
Pod becomes Ready. Service selector picks it up automatically.
One old pod terminated per new pod ready. Old ReplicaSet kept around for instant rollback.
The four Service types you'll actually use
Internal only
- Virtual IP reachable only inside the cluster
- Perfect for microservice-to-microservice
- kube-dns gives it a DNS name: `svc.namespace.svc.cluster.local`
- The 95% default
Fixed port on every node
- Opens the same port (30000-32767) on every node in the cluster
- Cheap way to expose a service without a cloud LB
- Mostly used for local dev + demos
- Awkward at scale — you need to know node IPs
Ask the cloud for an external LB
- Cloud controller provisions AWS ELB / Azure LB / GCP TCP LB
- One external IP per Service — gets expensive fast
- Good for a small number of top-level entry points
- In practice you often prefer one Ingress over many LoadBalancer Services
L7 router in front of many ClusterIP services
- Host/path routing, TLS termination, one external IP
- Requires an Ingress Controller (nginx-ingress, Traefik, Istio)
- Standard pattern: N ClusterIP services + 1 Ingress + 1 LB
- The production-grade pattern
YAML anatomy — the shape you'll write a hundred times
Every K8s manifest has these four top-level keys
"Kubernetes runs my containers. I give it a Deployment and it starts them, like a fancier Docker Compose."
Kubernetes does not run anything imperatively. You declare a desired state, and a set of independent controllers continuously compare desired to actual and act to reduce the difference. Nothing "starts" your pod in a single step — the Deployment controller creates a ReplicaSet, the ReplicaSet controller creates Pods, the scheduler binds them to nodes, and the kubelet on that node asks the container runtime to run them. Each is an independent loop that could run in any order.
The myth is sticky because kubectl apply feels imperative — you run a command and pods appear, so cause and effect look direct. It matters the moment something does not happen: with an imperative model your only question is "did the command fail?", whereas the reconciliation model tells you exactly where to look. Some controller is either not running, blocked, or continuously trying and failing, and it will keep trying forever without ever reporting a top-level error.
Watch reconciliation rather than execution — delete a pod and observe that nothing "restarts" it in the imperative sense:
kubectl delete pod my-app-abc123
kubectl get pods -w
# a NEW pod appears with a different name.
# nothing restarted it: the ReplicaSet controller noticed
# actual(2) != desired(3) and created one.
kubectl describe pod my-app-xyz | tail -20
# the Events section is the reconciliation log - read it FIRSTWhy does Kubernetes schedule Pods rather than containers, and why does a Pod have that shared-network-namespace design?
- 1Some containers are genuinely co-dependent: a log shipper reading a file the app writes, a proxy handling the app's traffic, an adapter translating its metrics.forced by · these helpers exist only to serve one specific application instance and are useless without it
- 2If such containers were scheduled independently, they could land on different nodes, and their entire purpose — sharing a filesystem or a network endpoint — would be impossible.forced by · a local file or a localhost connection cannot cross a machine boundary
- 3Therefore the scheduling unit must be a group that is guaranteed co-located and scheduled atomically.forced by · partial placement of a co-dependent group is never a useful state
- 4Co-location alone is insufficient: they must also share a network identity, so the proxy can intercept traffic on localhost and the app needs no awareness of it.forced by · transparent sidecars require the app to be unable to tell the difference, which means one network namespace
- 5So the Pod is defined as a set of containers sharing a network namespace and optionally volumes, with a single IP address, scheduled as one unit.forced by · one IP per Pod is what makes localhost communication and transparent interception work
Therefore the Pod is not an arbitrary wrapper — it is the smallest unit that can express "these processes must be together", which containers alone cannot express.
And note what this predicts: the entire service mesh pattern becomes possible without modifying application code, because a sidecar proxy in the same network namespace can transparently intercept all traffic. It also predicts the constraint that catches everyone out: containers in a Pod cannot bind the same port, since they share one network namespace. And it explains why a Pod is never "updated" in place — changing the image means a new Pod, because the unit of scheduling is the whole group.
You do not press buttons; you set a target. Controllers watch the gap between what you declared and what exists, and act continuously to close it. If you delete something, it comes back. If a node dies, its pods are recreated elsewhere. The system is always converging, never finished.
The API server plus etcd holds the declared and observed state; everything else is a loop reading from it and writing back. There is no orchestrator in the middle — just many small loops agreeing on one database.
- Every object type has a controller, and diagnosis means finding which loop is stuck.
kubectl describeEvents is the controller's own account of what it tried and why it failed — read it before logs, before metrics, before anything. - Services are stable virtual IPs with label-selector membership; Pods are cattle with changing IPs. Never address a Pod IP directly — the indirection through Services is what makes pods disposable.
- Probes have distinct jobs and confusing them causes outages: readiness removes a pod from Service endpoints (temporary, no restart), liveness kills and restarts the container (permanent-failure assumption), startup suspends the other two while a slow application boots. A liveness probe pointed at a dependency turns that dependency's slowness into a restart loop across your entire fleet.
- Requests drive scheduling; limits drive enforcement. Requests are what the scheduler reserves; a CPU limit throttles rather than kills, while a memory limit triggers an OOM kill. Setting requests too high wastes cluster capacity, and setting them too low gets you evicted under pressure.
Fire this model when you see: a pod stuck Pending · a CrashLoopBackOff · a deleted resource reappearing · traffic reaching a pod that is not ready · a pod that runs fine locally and gets OOM-killed in the cluster.
How do you set CPU and memory requests and limits for a service?
Always set a memory limit — memory is incompressible, so exceeding it has no graceful degradation, only an OOM kill. Set the memory request equal to the limit for production services so the QoS class is predictable. Be more relaxed with CPU: exceeding a CPU limit only throttles, so a request well below the limit is a reasonable trade for utilisation.
Set the numbers from observed usage, not from guesses. Take the p99 of actual consumption over a representative period and add headroom. The failure mode of guessing is bimodal and both ends are bad: guess high and you quietly waste a large fraction of your cluster spend, guess low and you get evictions and OOM kills under exactly the load conditions where you needed the service most.
(c) Hands-on · 25 min
Spin up a real Kubernetes cluster on your laptop with kind, deploy a 2-replica web app, expose it via a Service, and prove self-healing by killing a pod.
What each block does
Anatomy of the manifest
Run:
kubectl set image deployment/web nginx=nginx:doesnotexist
kubectl get pods -l app=web -wYou'll see pods flip through ImagePullBackOff and (if you use a real image with a bad command) CrashLoopBackOff. The restart backoff grows exponentially — 10s, 20s, 40s, 80s, capped at 5 min. Roll back:
kubectl rollout undo deployment/web
kubectl rollout status deployment/webRollback is one command because the old ReplicaSet is still around. This is why K8s deploys feel bulletproof after a while.
(d) Production reality · 15 min
Reddit upgraded Kubernetes from 1.23 to 1.24. Post-upgrade, a niche default changed — the way pods discovered nodes via the cluster's internal DNS. Route lookups started failing intermittently.
Cascading effect: Redis clients couldn't reach Redis, cache misses stormed the DB, every API endpoint slowed to seconds. Rolling back the K8s version isn't a one-liner; it took hours to isolate the specific change and patch DNS config.
resources.requests AND resources.limits set. Requests tell the scheduler how much to reserve. Limits give the kernel a ceiling. Adopt a policy: no requests = pod gets rejected by the admission controller (Gatekeeper, Kyverno).app: my-app. Service selector is app: myapp (no dash). kubectl get pods shows healthy pods. kubectl get svc shows the Service. kubectl get endpoints my-svc shows NONE — zero backing pods. Every request 503s and nobody knows why.kubectl get endpoints <service>. If it's empty, your selector doesn't match. Adopt Kustomize / Helm to generate labels from a single source, so mismatch becomes impossible.Common footguns to internalise
- CrashLoopBackOff without logs — the app crashes so fast that logs are empty. Try
kubectl logs <pod> --previousto get the last dying breath. - imagePullPolicy: Always with :latest — every restart re-pulls the tag. Combined with mutable :latest, every restart is a Russian roulette deploy.
- Missing PodDisruptionBudget — cluster maintenance drains a node, all your replicas get evicted simultaneously, zero downtime turns into an outage. Set
minAvailable: 1(at least). - No graceful shutdown — SIGTERM arrives, your app doesn't handle it, in-flight requests die. Set
terminationGracePeriodSeconds: 30and handle SIGTERM in your app.
Where this shows up in the rest of the plan
(e) Recall + stretch · 10 min
Explain-out-loud test
- What's the difference between a Pod and a Deployment?
- How does a Service find the pods it routes to? (label selector + endpoints)
- Name two things you must set on every production pod. (resources · readinessProbe · non-root user · terminationGracePeriodSeconds)
What comes next
Hub: The 6-Month Learning Plan
Part of a 130-session evergreen learning series. Session structure: intuition → visual → hands-on → production war stories → recall. Duration: 90 minutes.