Lesson 03 of 32 · Level 1 — Foundations
Pods, Deployments & workload types
Pods are the atoms of Kubernetes; Deployments, StatefulSets, DaemonSets, Jobs and CronJobs are the molecules. Learn when to use each, and how rollouts and rollbacks really work.
Pods: the smallest thing you can run
A Pod is one or more containers that are always scheduled together, share one IP address, and can share storage. Most pods have one container. Extra ones are helpers (sidecars), such as a log shipper or a proxy.
A container is a sandwich. A Pod is the lunchbox. Usually one sandwich per box, but sometimes you add an apple (a helper container). Everything in the same lunchbox goes to the same table (node), and they share the same label with your name on it (the IP address).
Save this as pod.yaml:
apiVersion: v1 # which API group/version this object belongs to
kind: Pod # what kind of object
metadata:
name: hello
labels:
app: hello # labels are how other objects find this pod
spec:
containers:
- name: web
image: nginx:1.27
ports:
- containerPort: 80
$ kubectl apply -f pod.yaml
pod/hello created
$ kubectl get pod hello -o wide
NAME READY STATUS RESTARTS AGE IP NODE
hello 1/1 Running 0 8s 10.244.1.5 lab-worker
$ kubectl exec -it hello -- nginx -v
nginx version: nginx/1.27.x
Every Kubernetes object has the same four top-level fields: apiVersion, kind, metadata, spec. Learn those once and you can read any manifest.
Bare pods are not self-healing
Delete hello and it's gone for good. Nothing recreates it. In real life you almost never create bare pods. You create a controller (usually a Deployment) that creates pods for you and keeps them alive.
Pod statuses you'll meet
| Status | What it means | First thing to check |
|---|---|---|
Pending |
Accepted, not yet running | kubectl describe pod: no node fits? PVC not bound? |
ContainerCreating |
Scheduled; pulling image, mounting volumes | Events: slow pull? volume attach? |
Running |
At least one container running | Is it Ready too? (READY 1/1) |
Completed |
All containers exited 0 (normal for Jobs) | Nothing, if it's a Job |
ImagePullBackOff |
Can't pull the image | Typo in image name/tag? Private registry credentials? |
CrashLoopBackOff |
Starts, crashes, restarts with growing delays | kubectl logs <pod> --previous |
OOMKilled (in Last State) |
Used more memory than its limit | Raise the limit or fix the leak |
Deployments: keep N copies alive, and update them safely
A Deployment says: "keep this many identical pods running, and when I change the template, replace them gradually."
apiVersion: apps/v1
kind: Deployment
metadata:
name: web
spec:
replicas: 3
selector:
matchLabels:
app: web # manage pods with this label…
template: # …created from this pod template
metadata:
labels:
app: web # must match the selector
spec:
containers:
- name: nginx
image: nginx:1.27
ports:
- containerPort: 80
resources:
requests: { cpu: 50m, memory: 64Mi } # what the scheduler reserves
limits: { memory: 128Mi } # hard ceiling
readinessProbe: # only send traffic when this passes
httpGet: { path: /, port: 80 }
$ kubectl apply -f web.yaml
deployment.apps/web created
$ kubectl get deploy,rs,pods -l app=web
NAME READY UP-TO-DATE AVAILABLE
deployment.apps/web 3/3 3 3
NAME DESIRED CURRENT READY
replicaset.apps/web-6d4b9c8f7b 3 3 3
NAME READY STATUS RESTARTS
pod/web-6d4b9c8f7b-4hx9t 1/1 Running 0
pod/web-6d4b9c8f7b-9qm2c 1/1 Running 0
pod/web-6d4b9c8f7b-tz7lw 1/1 Running 0
See the chain: Deployment → ReplicaSet → Pods. The random suffix on the ReplicaSet is a hash of the pod template. Change the template and you get a new ReplicaSet.
Rolling update and rollback
$ kubectl set image deployment/web nginx=nginx:1.27-alpine
deployment.apps/web image updated
$ kubectl rollout status deployment/web
Waiting for deployment "web" rollout to finish: 1 out of 3 new replicas have been updated...
deployment "web" successfully rolled out
$ kubectl get rs -l app=web
NAME DESIRED CURRENT READY
web-6d4b9c8f7b 0 0 0 ← old, kept for rollback
web-7f9c5d6b8d 3 3 3 ← new
Made a mistake? Roll back in one line:
$ kubectl set image deployment/web nginx=nginx:does-not-exist
$ kubectl get pods -l app=web
NAME READY STATUS RESTARTS
web-5c8d7b6f4-kx2vw 0/1 ImagePullBackOff 0
web-7f9c5d6b8d-4hx9t 1/1 Running 0
web-7f9c5d6b8d-9qm2c 1/1 Running 0
web-7f9c5d6b8d-tz7lw 1/1 Running 0
$ kubectl rollout undo deployment/web
deployment.apps/web rolled back
Notice the broken rollout never took down the running pods. The new pod never became Ready, so the Deployment stopped replacing old ones. That's the readiness probe and rolling strategy protecting you.
A Deployment is a team captain with a rule: "we always have 3 players on the field". To swap in players with new shirts, the captain brings one new player on, waits until they're ready to play, then takes one old player off. If a new player can't even find the pitch (bad image), the old players just keep playing.
The five workload types, and when to use each
| Workload | Use it for | Everyday analogy |
|---|---|---|
| Deployment | Stateless apps: web, APIs, workers | Identical delivery riders: any one can be replaced by any other |
| StatefulSet | Databases, queues, anything with an identity and its own disk | Numbered football players, each with their own locker (db-0, db-1) |
| DaemonSet | One pod per node: log agents, monitoring, CNI, kube-proxy | One security guard on every floor, including new floors |
| Job | Run to completion once: migrations, batch tasks | Homework: done when it's finished |
| CronJob | Jobs on a schedule: backups, reports | An alarm clock that starts the homework every night |
A Job that computes π to 200 digits:
apiVersion: batch/v1
kind: Job
metadata:
name: pi
spec:
backoffLimit: 4 # retry up to 4 times on failure
template:
spec:
restartPolicy: Never
containers:
- name: pi
image: perl:5.34
command: ["perl", "-Mbignum=bpi", "-wle", "print bpi(200)"]
A CronJob that runs every minute:
apiVersion: batch/v1
kind: CronJob
metadata:
name: hello
spec:
schedule: "*/1 * * * *" # standard cron syntax
jobTemplate:
spec:
template:
spec:
restartPolicy: OnFailure
containers:
- name: hello
image: busybox:1.36
command: ["sh", "-c", "date; echo Hello from Kubernetes"]
$ kubectl get cronjob,jobs,pods
$ kubectl logs job/pi
3.1415926535897932384626433832795028841971693993751058209749445923078164062862...
Try it: self-healing and rollbacks
- Apply the
webDeployment above with 3 replicas. - Delete one pod and immediately run
kubectl get pods -l app=web -w. Watch the replacement appear. - Scale to 6, then back to 2. Which pods get removed first?
- Break the image on purpose, confirm the old pods keep serving, then
rollout undo. - Run
kubectl rollout history deployment/weband explain each revision.
Going deeper: rollout tuning and labels
- The rolling strategy is controlled by
maxSurge(extra pods allowed abovereplicas) andmaxUnavailable(pods allowed below). Both default to 25%. For a 3-replica app that's effectively "one extra, none down". Explore withkubectl explain deployment.spec.strategy.rollingUpdate. - Readiness gates traffic; liveness restarts stuck containers; startup probes protect slow starters from liveness. Misconfigured liveness probes are a classic cause of self-inflicted outages.
spec.selectoris immutable after creation. Plan labels up front (app,component,version); they're how Services, NetworkPolicies and monitoring find your pods.kubectl rollout restartjust sets an annotation with a timestamp on the pod template. That's enough to trigger a new ReplicaSet.
Recap
- A Pod is the smallest unit: containers that share an IP and are scheduled together. Bare pods are not self-healing.
- A Deployment manages ReplicaSets, which manage Pods by label. It gives you scaling, self-healing, rolling updates and rollbacks.
- Pick the workload by the job: Deployment (stateless), StatefulSet (identity + disk), DaemonSet (per node), Job / CronJob (run to completion).
- When something fails:
describefor events,logs --previousfor crashes.
This site is a public version of my personal engineering knowledge hub. It intentionally excludes confidential company information and internal operational details.