Kubernetes, visually

Container โ†’ Pod โ†’ ReplicaSet โ†’ Deployment โ†’ Service. Plus rolling updates, probes, and what each kubectl command actually does behind the scenes.

Color legend (used throughout)

Container Pod ReplicaSet Deployment Service Namespace

1 ยท What is a container?

A container is just a normal Linux process running on your host kernel โ€” but the kernel lies to it about the world. It thinks it has its own filesystem, its own network interfaces, its own process tree. It sees almost nothing of the host.

The lies are produced by three kernel features: namespaces (what you can see), cgroups (how much you can use), and chroot/overlay filesystems (which files exist).

Virtual Machine (heavy)

App
Guest OS ยท libs ยท kernel
Hypervisor (VMware, KVM)
Host OS
Physical hardware

Every VM ships its own kernel โ†’ GBs of RAM, minutes to boot.

Container (light)

App + libs only
Container runtime (containerd)
Host OS + shared kernel
Physical hardware

All containers share one kernel โ†’ MBs of RAM, milliseconds to boot.

Mental shortcut: a VM virtualises hardware. A container virtualises the OS view. That's why containers are 100ร— lighter โ€” they don't carry an OS, they borrow one.

The container in our lab

YAML we wrote

containers:
  - name: proddetail
    image: python:3.12-alpine
    command: ["/bin/sh", "-c"]
    args:
      - touch /tmp/healthy /tmp/ready
        python3 -m http.server 3000
    ports:
      - containerPort: 3000

What actually runs on the node

CONTAINER
proddetail
๐Ÿ–ผ๏ธ image: python:3.12-alpine
โš™๏ธ PID 1: python3 -m http.server 3000
๐ŸŒ listening on :3000
๐Ÿ“ own /tmp, own /proc, own network interface

2 ยท What is a Pod?

A Pod is a thin wrapper around one or more containers that share a network and a small set of disks. From outside the pod looks like one machine: one IP, one localhost, one DNS name.

99% of pods have exactly one container. The multi-container pattern is for tightly-coupled "sidecars" (log shipper, proxy) that need to share localhost with the main app.

POD
proddetail-784d8f4b99-7lr5g
IP: 10.244.0.14 ยท node: minikube
Shared network namespace ยท shared volumes
CONTAINER
proddetail
python3 :3000

Pod โ‰  Container โ€” three differences that matter

ContainerPod
Scheduling unitNo โ€” runtimes don't move containers between hosts.Yes โ€” Kubernetes schedules pods onto nodes.
IP addressNo own IP โ€” would share the host's.Gets its own cluster-internal IP (e.g. 10.244.0.14).
LifecycleJust a process โ€” when it crashes, it's gone.Can hold multiple containers, restart them, share storage.
Why the abstraction exists: Kubernetes wanted a unit bigger than a container (so you can colocate sidecars) but smaller than a VM. That unit is the Pod.

3 ยท The full hierarchy

You almost never create a Pod directly. You write a Deployment, and three nested controllers do the work.

DEPLOYMENT
proddetail
desired state: "I want 3 replicas, image python:3.12-alpine"
REPLICASET
proddetail-784d8f4b99
job: keep exactly 3 pods matching app=proddetail alive
POD
โ€ฆ7lr5g
10.244.0.14
CTR
proddetail
POD
โ€ฆk2gn2
10.244.0.15
CTR
proddetail
POD
โ€ฆtcktg
10.244.0.16
CTR
proddetail

Who's responsible for what?

LevelJobYou write?
DeploymentPod template + update strategy (rolling, recreate). Manages ReplicaSets.Yes
ReplicaSetKeep N pods of a specific template alive. Spawns replacements when pods die.Auto-created
PodThe actual unit that gets scheduled to a node. Hosts container(s).Auto-created
ContainerThe running process.Defined in pod template

4 ยท Service โ€” the stable address

Pods are mortal. They die, restart, get new IPs constantly. So you can't just save a pod's IP and call it.

A Service is a permanent virtual IP and DNS name that load-balances to whichever pods match its selector right now.

SERVICE
proddetail
type: ClusterIP
virtual IP: 10.109.233.228:3000 ยท DNS: proddetail.workshop.svc.cluster.local
selector: app=proddetail
โ–ผ routes to any pod with matching label โ–ผ
POD
10.244.0.14
label app=proddetail โœ“
POD
10.244.0.15
label app=proddetail โœ“
POD
10.244.0.16
label app=proddetail โœ“

Labels and selectors โ€” the glue of Kubernetes

Almost every "X connects to Y" relationship in Kubernetes is implemented with labels. The Service has a selector; pods have labels; they match โ†’ wired.

Service selector

spec:
  selector:
    app: proddetail

Pod labels

metadata:
  labels:
    app: proddetail
    version: v2
#1 source of bugs: the Service's selector doesn't match any pod's labels. Result: Endpoints: <none> and no traffic flows. Always check with kubectl get endpoints <svc>.

5 ยท Versioning & rolling updates โ€” the magic moment

Here's what your eyes saw earlier when we went from v1 โ†’ v2 with zero downtime.

The setup

Before the change, the Deployment is pointing at ReplicaSet A (v1) with 3 pods. We change the pod template โ†’ Deployment creates ReplicaSet B (v2) and orchestrates a controlled handoff.

The strategy knobs

What we wrote

strategy:
  type: RollingUpdate
  rollingUpdate:
    maxSurge: 1
    maxUnavailable: 0

What it means

  • maxSurge: 1 โ€” at most 4 pods total during the rollout (3 desired + 1 extra).
  • maxUnavailable: 0 โ€” never fewer than 3 Ready pods. Capacity never dips.

Frame-by-frame timeline

t = 0s
v1โ—
v1โ—
v1โ—
3 Ready ยท Service routes to all 3
t = 1s
v1โ—
v1โ—
v1โ—
v2โ—Œ
apply v2 โ†’ new RS spawns pod #1 (not Ready yet)
t = 4s
v1โ—
v1โ—
v1โ—
v2โ—
v2 passes readiness โ†’ one v1 starts draining
t = 8s
v1โ—
v1โ—
v2โ—
v2โ—
v2 pod #2 Ready ยท v1 pod #2 draining
t = 14s
v2โ—
v2โ—
v2โ—
All v2 ยท old ReplicaSet scaled to 0 (kept for rollback)
The key invariant: Service Endpoints only ever contain Ready pods. New pods don't get traffic until they pass readiness; old pods stop getting traffic the moment Kubernetes decides to terminate them. That's how 0 requests fail across the rollover.

ReplicaSets after the rollout

ReplicaSetPodsRole
proddetail-6bb476c4b60Ancient (no probes). Kept for rollback.
proddetail-784d8f4b990v1 with probes. Kept for rollback.
proddetail-74f9b4b4b93Active. v2.

One-command rollback

$ kubectl rollout undo deployment/proddetail -n workshop
# Same algorithm โ€” just scales the OLD RS back up and NEW RS back down.
# Service Endpoints update continuously. Zero downtime, both directions.

6 ยท Probes โ€” self-healing in two flavors

Liveness vs readiness โ€” the one-sentence distinction

Liveness probe failsReadiness probe fails
What happens to the pod๐Ÿ”ช Container killed and restarted๐Ÿšง Pod stays alive, removed from Service Endpoints
RESTARTS counter+10
Pod namesame (container restarts inside same pod)same
Use it forDetecting deadlocks, stuck processesWarming up, draining, dep outages

Readiness probe โ€” visual state machine

โณ NotReady (warming up)
โ†’
โœ… Ready ยท in Endpoints
โ‡„
โ›” NotReady ยท removed from Endpoints

Pod can flip back and forth indefinitely. Never gets killed.

Liveness probe โ€” visual state machine

โœ… Running
โ†’
โš ๏ธ Failing probes
โ†’
๐Ÿ”ช SIGKILL ยท exit code 137
โ†’
โœ… Restarted (RESTARTS++)

After failureThreshold consecutive failures, kubelet kills and restarts the container.

What we actually demonstrated

Readiness fail

$ kubectl exec $POD -- rm /tmp/ready
# 8s later:
$ kubectl get endpoints proddetail
NAME         ENDPOINTS
proddetail                โ† empty!

$ kubectl get pod
NAME           READY   STATUS    RESTARTS
proddetail-x   0/1     Running   0
  โ–ฒ not Ready    โ–ฒ but alive    โ–ฒ never killed

Liveness fail

$ kubectl exec $POD -- rm /tmp/healthy
# ~12s later:
$ kubectl get pod
NAME           READY  STATUS   RESTARTS
proddetail-x   1/1    Running  1 (18s ago)
                                  โ–ฒ killed and restarted

$ kubectl get events ...
Warning  Unhealthy  Liveness probe failed
Normal   Killing    Container will be restarted

7 ยท What each kubectl command actually does

kubectl is just an HTTP client for the Kubernetes API server. Every command becomes a REST call to /api/v1/....

kubectl apply -f file.yaml

What you type

$ kubectl apply -f deployment.yaml

Behind the scenes

  1. YAML โ†’ JSON
  2. HTTP PUT/PATCH to API server
  3. API server validates & writes to etcd
  4. Deployment controller wakes up, sees new desired state
  5. Creates/updates ReplicaSet
  6. ReplicaSet controller creates Pod objects
  7. Scheduler picks a node for each pod
  8. kubelet on that node pulls the image and starts the container

kubectl get / describe / exec โ€” what they really are

CommandREST equivalentWhat it returns
kubectl get podsGET /api/v1/namespaces/.../podsTable view of pod list
kubectl describe pod xGET pod x + GET events?involvedObject=xVerbose pod state + recent events
kubectl exec -it pod -- bashPOST /pods/x/exec?command=bash (WebSocket)Bidirectional tunnel to a process inside the container
kubectl logs podGET /pods/x/logThe container's stdout/stderr stream
kubectl rollout undo deploy/xPATCH deployment with old pod-template-hashReverses the spec back to a previous revision

The control loop โ€” why this all keeps working

Every Kubernetes controller is the same shape:

1. Observe current state
โ†’
2. Compare to desired state (etcd)
โ†’
3. Act to close the gap
โ†ป

Forever. This is the loop. Pods die โ†’ ReplicaSet notices โ†’ spawns new pod. You change image โ†’ Deployment notices โ†’ rolling update. Everything in Kubernetes is one of these loops.

8 ยท The full picture (what we built today)

NODE (minikube)
10.244.0.0/16 pod network
NAMESPACE: workshop
SERVICE frontend
NodePort :30616
selector: app=frontend
SERVICE prodcatalog
ClusterIP :5000
selector: app=prodcatalog
SERVICE proddetail
ClusterIP :3000
selector: app=proddetail
DEPLOYMENT frontend
replicas: 1
POD
CTR
nginx
DEPLOYMENT prodcatalog
replicas: 1
POD
CTR
http-echo
DEPLOYMENT proddetail
replicas: 3
POD
CTR
python
POD
CTR
python
POD
CTR
python
If you can explain this diagram out loud, you understand 80% of practical Kubernetes. The remaining 20% is volumes, ingress, RBAC, and statefulsets โ€” all variations on the same patterns you see here.