Search Tech Journey

Find topics, journeys and posts

6-month learning plan66 / 130
back to blog
systemsintermediate 15m read

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)


(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:

ObjectJobAnalogy
PodOne (or few) container(s) sharing net + storageA single running instance of your app
DeploymentManages a set of identical Pods, handles rollouts"Keep 3 copies of this, upgrade them safely"
ReplicaSetThe thing Deployments create under the hoodThe actual "keep N running" controller
ServiceStable IP + DNS name in front of PodsAn internal load balancer
IngressHTTP router from outside → ServicesThe reverse-proxy layer (nginx/traefik)
NamespaceLogical partition of the clusterA folder for grouping objects
ConfigMap / SecretInject config / creds into Podsenv 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: ClusterIP
  1. kubectl apply -f deployment.yaml → API server writes to etcd.
  2. Deployment controller creates a ReplicaSet.
  3. ReplicaSet controller creates 3 Pods (each in Pending).
  4. Scheduler picks nodes for each Pod based on resource requests.
  5. Kubelet on each chosen node pulls nginx:1.27-alpine and starts the container.
  6. Pods report Running; kube-proxy programs iptables so web.default.svc.cluster.local:80 round-robins across them.
  7. 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 learn

Checklist — what to observe:

  1. kubectl get pods shows three pods with different suffixes but the same prefix — that's the ReplicaSet naming.
  2. Deleting a pod → within ~5 s a new pod appears with a new name. The Deployment controller reconciled.
  3. kubectl rollout status shows K8s ramping up the new version and terminating old pods one at a time — this is a rolling update, zero downtime by default.
  4. kubectl scale --replicas=5 → two new pods appear immediately (no image pull because the image is cached on the node).
  5. kubectl describe pod <name> shows the full event history: ScheduledPullingPulledCreatedStarted. 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 edit in 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: Always on every pod = every restart pulls from the registry = deploy slowdowns and registry rate limits. Pin tags + use IfNotPresent.
  • Namespace hygiene. Everything in default is a smell. Namespaces are your unit of quota, RBAC, and network policy.
  • Logs & metrics. kubectl logs doesn'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

  1. Explain the difference between a Pod, a ReplicaSet, and a Deployment.
  2. Why does kubectl delete pod x not actually remove the pod from your cluster (long-term)?
  3. What is a Service and what problem does it solve that Pods alone don't?
  4. Name 3 things you'd put in a container's resources.requests and .limits and what happens if you skip them.
  5. What does kubectl rollout status tell 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:

  1. What is Kubernetes I? (one sentence, no jargon)
  2. When would you use it? (one concrete example)
  3. When would you NOT use it? (one anti-pattern)

What comes next


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 →
  1. 060OS Basics — Processes, Threads, Memory, FDs
  2. 061Networking I — TCP/IP, DNS, Sockets
  3. 062Networking II — Load Balancers L4 vs L7, Reverse Proxies
  4. 063Caching — Cache-Aside, Write-Through, TTLs, Invalidation
  5. 064CDN — Edge, Cache Hierarchies, Cache-Control
  6. 065Docker — Images, Layers, Dockerfile, Networking