S066 · Kubernetes I — Pods, Deployments, Services
Orchestrating containers at scale.
Module M07: Systems & Infrastructure · Session 66 of 130 · Track: SYS ⚓
What you'll be able to do after this session
- Explain Kubernetes I to a friend in one minute, out loud, no notes.
- Recognise it when you see it in real code / systems / papers.
- Complete the hands-on exercise at the end without looking up help.
Prerequisites
If you can't answer these in 30 seconds each, redo the linked session first:
Pre-read (skim before the session)
- 🎥 Intuition (5–15 min) · Kubernetes Explained in 100 Seconds (Fireship) — then jump to the 10-min tutorial in the same channel.
- 🎥 Deep dive (30–60 min) · Kubernetes Tutorial for Beginners (TechWorld with Nana) — 4h masterclass, first hour = pods/deployments/services.
- 🎥 Hands-on demo (10–20 min) · Kubernetes with kind in 15 min (Kubesimplify) — real cluster on your laptop, deploy a pod, expose a service.
- 📖 Canonical article · Kubernetes Docs — Concepts — the reference; start with "Overview", "Cluster architecture", "Workloads".
- 📖 Book chapter / tutorial · Kubernetes the Hard Way (Kelsey Hightower) — bootstrap a cluster from scratch to see every moving part.
- 📖 Engineering blog · Google — Borg, Omega, and Kubernetes (ACM Queue) — the paper that explains K8s' design lineage from Google's internal Borg.
(a) Intuition · 5 min
The one-paragraph mental model. Why this concept exists, what problem it solves, and the analogy that makes it stick.
In one sentence: Orchestrating containers at scale.
You've got 50 servers and 200 Docker containers to run across them. Which server runs which container? What if one crashes at 3 AM? What if traffic spikes and you need 5 more copies? Who wires up the load balancer? Doing this by hand is a full-time job and nobody sleeps. Kubernetes (K8s) is the robot that does that job for you: give it a desired state ("I want 3 copies of this container running with these resource limits") and K8s makes it so — and keeps it so — reconciling reality with desire every few seconds.
The key mental shift from Docker to Kubernetes: with Docker you think in commands ("run this container now"); with K8s you think in declarations ("this is what should exist"). You write a YAML file. The API server stores it. The scheduler decides which node has capacity. The kubelet on each node pulls the image and runs the container. If a container dies, the controller notices "reality ≠ desired" and spawns a new one. You never manually restart anything again.
The unit of scheduling isn't a container — it's a Pod, which is usually one container but can be a small group of tightly-coupled ones that share network + storage. You almost never create Pods directly; you create a Deployment ("keep N pods of this template running, roll out updates safely") and let K8s manage the pods for you. To expose a stable network endpoint over ever-changing pods, you create a Service — think of it as a virtual IP + DNS name that load-balances across whichever pods currently match its selector.
Forever-gotcha: K8s is a control loop, not a script. Every object has a spec (what you want) and a status (what is). Controllers continuously nudge status toward spec. If you kubectl delete pod, the Deployment's controller immediately creates a replacement — because your desired state is still "3 pods". To actually remove it, you have to change the desire (delete the Deployment or scale to 0).
(b) Visual walkthrough · 15 min
Diagrams, worked examples, step-by-step visuals.
Core objects — the ones you meet in week one:
| Object | Job | Analogy |
|---|---|---|
| Pod | One (or few) container(s) sharing net + storage | A single running instance of your app |
| Deployment | Manages a set of identical Pods, handles rollouts | "Keep 3 copies of this, upgrade them safely" |
| ReplicaSet | The thing Deployments create under the hood | The actual "keep N running" controller |
| Service | Stable IP + DNS name in front of Pods | An internal load balancer |
| Ingress | HTTP router from outside → Services | The reverse-proxy layer (nginx/traefik) |
| Namespace | Logical partition of the cluster | A folder for grouping objects |
| ConfigMap / Secret | Inject config / creds into Pods | env vars & files, but centrally managed |
Worked example — deploy nginx three times, expose it:
# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata: { name: web }
spec:
replicas: 3
selector: { matchLabels: { app: web } }
template:
metadata: { labels: { app: web } }
spec:
containers:
- name: nginx
image: nginx:1.27-alpine
ports: [{ containerPort: 80 }]
resources:
requests: { cpu: "50m", memory: "64Mi" }
limits: { cpu: "200m", memory: "128Mi" }
---
apiVersion: v1
kind: Service
metadata: { name: web }
spec:
selector: { app: web }
ports: [{ port: 80, targetPort: 80 }]
type: ClusterIPkubectl apply -f deployment.yaml→ API server writes to etcd.- Deployment controller creates a ReplicaSet.
- ReplicaSet controller creates 3 Pods (each in
Pending). - Scheduler picks nodes for each Pod based on resource requests.
- Kubelet on each chosen node pulls
nginx:1.27-alpineand starts the container. - Pods report
Running; kube-proxy programs iptables soweb.default.svc.cluster.local:80round-robins across them. - Delete one pod: Deployment sees 2/3, spawns one more within seconds. You did nothing.
(c) Hands-on · 20 min
Code you type and run. Not pseudo-code — real, runnable, copy-pasteable.
We'll use kind (Kubernetes in Docker) to run a real cluster on your laptop.
# Install prerequisites (Linux / macOS via brew)
# brew install kind kubectl OR apt install kubectl + go install kind
# Spin up a 1-node cluster
kind create cluster --name learn
kubectl cluster-info
kubectl get nodes
# Deploy nginx x3 + expose it
cat > web.yaml <<'EOF'
apiVersion: apps/v1
kind: Deployment
metadata: { name: web }
spec:
replicas: 3
selector: { matchLabels: { app: web } }
template:
metadata: { labels: { app: web } }
spec:
containers:
- name: nginx
image: nginx:1.27-alpine
ports: [{ containerPort: 80 }]
---
apiVersion: v1
kind: Service
metadata: { name: web }
spec:
selector: { app: web }
ports: [{ port: 80 }]
EOF
kubectl apply -f web.yaml
kubectl rollout status deployment/web
kubectl get pods -o wide
kubectl get svc web
# Talk to the service from inside the cluster
kubectl run tmp --rm -it --image=curlimages/curl --restart=Never -- curl -s http://web.default.svc.cluster.local/
# Watch self-healing: kill a pod
POD=$(kubectl get pod -l app=web -o jsonpath='{.items[0].metadata.name}')
kubectl delete pod $POD
kubectl get pods -w # Ctrl-C after you see the new one appear
# Rolling update: change image tag
kubectl set image deployment/web nginx=nginx:1.26-alpine
kubectl rollout status deployment/web
# Scale up/down
kubectl scale deployment/web --replicas=5
kubectl get pods
# Read logs
kubectl logs deployment/web --tail=20
# Cleanup
kubectl delete -f web.yaml
kind delete cluster --name learnChecklist — what to observe:
kubectl get podsshows three pods with different suffixes but the same prefix — that's the ReplicaSet naming.- Deleting a pod → within ~5 s a new pod appears with a new name. The Deployment controller reconciled.
kubectl rollout statusshows K8s ramping up the new version and terminating old pods one at a time — this is a rolling update, zero downtime by default.kubectl scale --replicas=5→ two new pods appear immediately (no image pull because the image is cached on the node).kubectl describe pod <name>shows the full event history:Scheduled→Pulling→Pulled→Created→Started. This is your #1 debugging tool.
Try this modification: add livenessProbe: { httpGet: { path: /nonexistent, port: 80 }, periodSeconds: 5 } to the container. Watch pods enter CrashLoopBackOff as K8s repeatedly kills and restarts them because the probe fails. Then change path to / and watch them stabilise. This is how K8s learns "the container is running but broken".
(d) Production reality · 10 min
How this shows up in real systems, gotchas, war stories, common bugs.
War story #1 — the CrashLoopBackOff mystery. A team's pods kept restarting every 30 s. kubectl logs showed nothing. Turns out the container was OOMKilled because their memory limit was 128 Mi but the JVM defaulted to using half of host memory (16 GB). Kernel killed it, K8s restarted it, forever. Rule: set JVM -Xmx (or Node's --max-old-space-size) inside the container to match the K8s limit.
War story #2 — the 3 AM node drain. Cloud provider announced maintenance: they cordoned the node and drained it, meaning K8s evicted all pods to move them to other nodes. But the team hadn't set podDisruptionBudget: { minAvailable: 1 }. All 3 replicas got evicted at once; the service was down for 90 s. Rule: for anything with replicas > 1, set a PDB so K8s knows how many pods can be safely evicted concurrently.
War story #3 — the wrong secret. Someone updated a Secret. Existing pods still had the old value mounted as env vars — env vars are set at pod start and never re-read. Only when the deployment rolled did new pods pick up the new secret. Fix: trigger a rollout after Secret changes (kubectl rollout restart deployment/foo) or mount secrets as files (files can be re-read).
Common gotchas & how top teams handle them:
kubectl editin production. Edit lives in etcd, not in your git repo. Two weeks later nobody remembers what changed. GitOps (ArgoCD, Flux) — cluster state derived from a git repo, PR-reviewed — is the modern norm.- Resource requests/limits. Without requests, the scheduler can't make good decisions. Without limits, one pod can hog a node and starve others. Real teams enforce them via admission controllers (Gatekeeper, Kyverno).
- Image pull policies.
imagePullPolicy: Alwayson every pod = every restart pulls from the registry = deploy slowdowns and registry rate limits. Pin tags + useIfNotPresent. - Namespace hygiene. Everything in
defaultis a smell. Namespaces are your unit of quota, RBAC, and network policy. - Logs & metrics.
kubectl logsdoesn't persist across pod restarts. Real setups run Fluent Bit / Vector shipping to Loki / Elasticsearch, and Prometheus scraping metrics. - etcd is the whole cluster. Back it up. Lose etcd → lose cluster state. Managed K8s (EKS, GKE, AKS) hides this but you still need to think about it.
Kubernetes is an operating system for your cluster. It rewards teams who invest in the platform once and reuse it across every service; it punishes teams who treat it like "docker but bigger" without learning the primitives.
(e) Quiz + exercise · 10 min
- Explain the difference between a Pod, a ReplicaSet, and a Deployment.
- Why does
kubectl delete pod xnot actually remove the pod from your cluster (long-term)? - What is a Service and what problem does it solve that Pods alone don't?
- Name 3 things you'd put in a container's
resources.requestsand.limitsand what happens if you skip them. - What does
kubectl rollout statustell you and what would you check next if it hangs?
Stretch problem (connects to S062 — Load Balancers): a K8s Service of type ClusterIP provides an in-cluster virtual IP. A LoadBalancer service provisions an external LB (e.g. AWS NLB). Ingress adds a shared L7 router in front of many Services. For a public API with 5 microservices, sketch the packet path from an internet user's browser to a pod's process — naming each hop (DNS, external LB, node port / iptables, kube-proxy, pod IP). ~10 lines.
Explain-out-loud test
If you can't teach these 3 things to a friend without notes, redo the session:
- What is Kubernetes I? (one sentence, no jargon)
- When would you use it? (one concrete example)
- When would you NOT use it? (one anti-pattern)
What comes next
- Next session (S067): Kubernetes II — ConfigMaps, Secrets, HPA, Network Policies
- Previous (S065): Docker — Images, Layers, Dockerfile, Networking
- Hub: The 6-Month Learning Plan
Part of a 130-session evergreen learning series. Session structure: (a) intuition · (b) visual walkthrough · (c) hands-on · (d) production reality · (e) quiz + exercise. Duration: 60 minutes.
More from M07 · Systems & Infrastructure
all modules →