Module 7 — Pods & Workloads
Overview
Pods are the smallest deployable unit in Kubernetes. Workload resources (Deployments, ReplicaSets, DaemonSets, StatefulSets, Jobs, CronJobs) manage pods at scale. This module covers the pod lifecycle, every workload type, and rolling update/rollback mechanics — all heavily tested on the CKA exam.
1. Pod Lifecycle and Phases
1.1 Pod Phases
A pod moves through these phases during its lifetime:
| ┌──────────┐
┌─────────│ Pending │
│ └────┬─────┘
│ │ scheduled + images pulled
│ ▼
│ ┌──────────┐
│ │ Running │
│ └────┬─────┘
│ │
│ ┌─────────┼──────────┐
│ ▼ ▼ ▼
│ ┌─────────┐ ┌────────┐ ┌────────┐
│ │Succeeded│ │ Failed │ │Unknown │
│ └─────────┘ └────────┘ └────────┘
│
▼
┌──────────┐
│ Failed │ (image pull error, insufficient resources, etc.)
└──────────┘
|
| Phase |
Meaning |
Pending |
Pod accepted but not yet running — waiting for scheduling, image pull, or volume mount |
Running |
At least one container is running, starting, or restarting |
Succeeded |
All containers terminated successfully (exit code 0) — typical for Jobs |
Failed |
All containers terminated, at least one with a non-zero exit code |
Unknown |
Pod status can't be determined — usually a node communication failure |
| # Check pod phase
kubectl get pod <name> -o jsonpath='{.status.phase}'
|
1.2 Container States
Within a running pod, each container has its own state:
| State |
Description |
Waiting |
Container not yet running (pulling image, applying config) |
Running |
Container executing normally |
Terminated |
Container finished execution (success or failure) |
| # Check container state details
kubectl get pod <name> -o jsonpath='{.status.containerStatuses[0].state}'
# Human-readable
kubectl describe pod <name> | grep -A5 State:
|
1.3 Common Status Conditions
What you see in kubectl get pods STATUS column:
| Status |
Phase |
Meaning |
Pending |
Pending |
Waiting to be scheduled |
ContainerCreating |
Pending |
Scheduled, pulling images or mounting volumes |
Running |
Running |
All containers started |
Completed |
Succeeded |
All containers exited with code 0 |
Error |
Failed |
Container exited with non-zero code |
CrashLoopBackOff |
Running |
Container keeps crashing, kubelet backs off restart |
ImagePullBackOff |
Pending |
Can't pull the container image |
ErrImagePull |
Pending |
First image pull failure |
OOMKilled |
Running/Failed |
Container exceeded memory limit |
Terminating |
Running |
Pod is being deleted (graceful shutdown) |
1.4 Restart Policies
| Policy |
Behavior |
Used by |
Always (default) |
Restart container on any exit |
Deployments, ReplicaSets, DaemonSets, StatefulSets |
OnFailure |
Restart only on non-zero exit code |
Jobs |
Never |
Never restart |
Jobs (when you want to inspect failures) |
| spec:
restartPolicy: OnFailure
|
1.5 Pod Lifecycle Hooks and Probes
| Container Start
│
├── postStart hook (runs immediately after container starts)
│
├── startupProbe (runs until success, then stops)
│ │
│ ▼ (once startup passes)
│
├── livenessProbe (runs periodically — restarts container on failure)
│
├── readinessProbe (runs periodically — removes from Service endpoints on failure)
│
▼
Container Stop
│
└── preStop hook (runs before SIGTERM is sent)
|
| Probe |
Purpose |
On failure |
startupProbe |
Detect slow-starting containers |
Container killed + restarted |
livenessProbe |
Detect deadlocked/hung containers |
Container killed + restarted |
readinessProbe |
Detect containers not ready to serve traffic |
Removed from Service endpoints (not killed) |
| spec:
containers:
- name: app
image: nginx
startupProbe:
httpGet:
path: /healthz
port: 8080
failureThreshold: 30
periodSeconds: 10
livenessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: 8080
periodSeconds: 5
|
Probe types:
| Type |
Example |
httpGet |
httpGet: { path: /healthz, port: 8080 } |
tcpSocket |
tcpSocket: { port: 3306 } |
exec |
exec: { command: ["cat", "/tmp/healthy"] } |
grpc |
grpc: { port: 50051 } |
2. Creating Pods
2.1 Imperative Commands
| # Create a pod
kubectl run nginx --image=nginx
# Create a pod with labels
kubectl run nginx --image=nginx --labels="app=web,tier=frontend"
# Create a pod and expose a port
kubectl run nginx --image=nginx --port=80
# Create a pod with a command
kubectl run busybox --image=busybox --command -- sleep 3600
# Create a pod with environment variables
kubectl run myapp --image=nginx --env="DB_HOST=mysql" --env="DB_PORT=3306"
# Dry-run to generate YAML (very useful on the exam)
kubectl run nginx --image=nginx --dry-run=client -o yaml > pod.yaml
# Create a temporary pod for debugging
kubectl run debug --image=busybox -it --rm --restart=Never -- sh
|
2.2 Minimal Pod YAML
| apiVersion: v1
kind: Pod
metadata:
name: nginx
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.25
ports:
- containerPort: 80
|
2.3 Multi-Container Pods
Containers in the same pod share the network namespace (localhost) and can share volumes:
| apiVersion: v1
kind: Pod
metadata:
name: multi-container
spec:
containers:
- name: app
image: nginx
ports:
- containerPort: 80
volumeMounts:
- name: shared-logs
mountPath: /var/log/nginx
- name: sidecar
image: busybox
command: ["sh", "-c", "tail -f /var/log/nginx/access.log"]
volumeMounts:
- name: shared-logs
mountPath: /var/log/nginx
volumes:
- name: shared-logs
emptyDir: {}
|
Common multi-container patterns:
| Pattern |
Description |
Example |
| Sidecar |
Enhances the main container |
Log shipper, proxy |
| Ambassador |
Proxies network connections |
Local DB proxy |
| Adapter |
Transforms output |
Log format converter |
2.4 Init Containers
Run before app containers start. All init containers must complete successfully before the main containers begin:
| spec:
initContainers:
- name: wait-for-db
image: busybox
command: ["sh", "-c", "until nc -z mysql 3306; do sleep 2; done"]
containers:
- name: app
image: myapp:latest
|
3. Workload Resources
3.1 ReplicaSet
Ensures a specified number of pod replicas are running at all times.
| apiVersion: apps/v1
kind: ReplicaSet
metadata:
name: nginx-rs
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.25
|
CKA Tip: You rarely create ReplicaSets directly. Deployments manage ReplicaSets for you. But you must understand how they work.
| # List ReplicaSets
kubectl get rs
# Scale a ReplicaSet
kubectl scale rs nginx-rs --replicas=5
|
3.2 Deployment
The most common workload resource. Manages ReplicaSets and provides rolling updates and rollbacks.
| Deployment
│
├── ReplicaSet (current) ──▶ Pod, Pod, Pod
│
└── ReplicaSet (old) ──▶ (scaled to 0 after rollout)
|
Imperative Creation
| # Create a deployment
kubectl create deployment nginx --image=nginx --replicas=3
# Generate YAML
kubectl create deployment nginx --image=nginx --replicas=3 --dry-run=client -o yaml > deploy.yaml
# Scale
kubectl scale deployment nginx --replicas=5
# Update image
kubectl set image deployment/nginx nginx=nginx:1.26
# Expose as a service
kubectl expose deployment nginx --port=80 --target-port=80 --type=ClusterIP
|
Declarative YAML
| apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
spec:
replicas: 3
selector:
matchLabels:
app: nginx
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1 # max pods above desired during update
maxUnavailable: 0 # max pods unavailable during update
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.25
ports:
- containerPort: 80
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 250m
memory: 256Mi
|
3.3 DaemonSet
Runs exactly one pod per node (or per matching node). Pods are automatically added/removed as nodes join/leave.
| ┌──────────┐ ┌──────────┐ ┌──────────┐
│ node-1 │ │ node-2 │ │ node-3 │
│ │ │ │ │ │
│ [ds-pod] │ │ [ds-pod] │ │ [ds-pod] │
└──────────┘ └──────────┘ └──────────┘
|
Common use cases:
- Log collectors (Fluentd, Filebeat)
- Monitoring agents (Prometheus node-exporter)
- Network plugins (kube-proxy, Calico, Cilium)
- Storage daemons (CSI node plugins)
| apiVersion: apps/v1
kind: DaemonSet
metadata:
name: log-collector
namespace: kube-system
spec:
selector:
matchLabels:
app: log-collector
template:
metadata:
labels:
app: log-collector
spec:
tolerations:
- key: node-role.kubernetes.io/control-plane
effect: NoSchedule # run on control plane nodes too
containers:
- name: fluentd
image: fluentd:latest
volumeMounts:
- name: varlog
mountPath: /var/log
volumes:
- name: varlog
hostPath:
path: /var/log
|
| # List DaemonSets
kubectl get ds -n kube-system
# Check which nodes have the DaemonSet pod
kubectl get pods -l app=log-collector -o wide -n kube-system
|
CKA Tip: There's no kubectl create daemonset imperative command. Create a Deployment YAML with --dry-run, then change kind: Deployment to kind: DaemonSet and remove the replicas and strategy fields.
3.4 StatefulSet
For stateful applications that need stable network identities and persistent storage.
| StatefulSet: mysql (replicas: 3)
│
├── mysql-0 ──▶ PVC: data-mysql-0 ──▶ PV
├── mysql-1 ──▶ PVC: data-mysql-1 ──▶ PV
└── mysql-2 ──▶ PVC: data-mysql-2 ──▶ PV
|
Key differences from Deployments:
| Feature |
Deployment |
StatefulSet |
| Pod names |
Random suffix (nginx-7d4f8b) |
Ordered index (mysql-0, mysql-1) |
| Creation order |
All at once |
Sequential (0, then 1, then 2) |
| Deletion order |
All at once |
Reverse sequential (2, then 1, then 0) |
| Stable network ID |
No |
Yes — <pod-name>.<headless-service> |
| Persistent storage |
Shared or none |
Per-pod PVC via volumeClaimTemplates |
| apiVersion: apps/v1
kind: StatefulSet
metadata:
name: mysql
spec:
serviceName: mysql-headless # required — headless Service name
replicas: 3
selector:
matchLabels:
app: mysql
template:
metadata:
labels:
app: mysql
spec:
containers:
- name: mysql
image: mysql:8.0
ports:
- containerPort: 3306
env:
- name: MYSQL_ROOT_PASSWORD
value: "rootpass"
volumeMounts:
- name: data
mountPath: /var/lib/mysql
volumeClaimTemplates: # creates a PVC per pod
- metadata:
name: data
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 10Gi
|
Requires a headless Service:
| apiVersion: v1
kind: Service
metadata:
name: mysql-headless
spec:
clusterIP: None # headless
selector:
app: mysql
ports:
- port: 3306
|
DNS records created:
| mysql-0.mysql-headless.default.svc.cluster.local
mysql-1.mysql-headless.default.svc.cluster.local
mysql-2.mysql-headless.default.svc.cluster.local
|
3.5 Job
Runs a pod to completion — for batch processing, one-time tasks.
| # Imperative
kubectl create job pi --image=perl -- perl -Mbignum=bpi -wle 'print bpi(2000)'
# Generate YAML
kubectl create job pi --image=perl --dry-run=client -o yaml > job.yaml
|
| apiVersion: batch/v1
kind: Job
metadata:
name: pi
spec:
completions: 1 # how many pods must complete successfully
parallelism: 1 # how many pods run in parallel
backoffLimit: 4 # max retries before marking as Failed
activeDeadlineSeconds: 120 # timeout for the entire Job
template:
spec:
restartPolicy: Never # or OnFailure
containers:
- name: pi
image: perl
command: ["perl", "-Mbignum=bpi", "-wle", "print bpi(2000)"]
|
| Field |
Purpose |
completions |
Total successful completions needed |
parallelism |
Max pods running simultaneously |
backoffLimit |
Max retries (default: 6) |
activeDeadlineSeconds |
Hard timeout for the Job |
| # Check Job status
kubectl get jobs
kubectl describe job pi
# View completed pod logs
kubectl logs job/pi
|
3.6 CronJob
Runs Jobs on a schedule (cron syntax).
| # Imperative
kubectl create cronjob backup --image=busybox --schedule="0 2 * * *" -- /bin/sh -c "echo backup"
# Generate YAML
kubectl create cronjob backup --image=busybox --schedule="0 2 * * *" --dry-run=client -o yaml > cj.yaml
|
| apiVersion: batch/v1
kind: CronJob
metadata:
name: backup
spec:
schedule: "0 2 * * *" # every day at 2:00 AM
concurrencyPolicy: Forbid # don't run if previous is still running
successfulJobsHistoryLimit: 3
failedJobsHistoryLimit: 1
startingDeadlineSeconds: 200 # skip if missed by this many seconds
jobTemplate:
spec:
template:
spec:
restartPolicy: OnFailure
containers:
- name: backup
image: busybox
command: ["/bin/sh", "-c", "echo backup completed"]
|
Cron schedule format:
| ┌───────────── minute (0-59)
│ ┌───────────── hour (0-23)
│ │ ┌───────────── day of month (1-31)
│ │ │ ┌───────────── month (1-12)
│ │ │ │ ┌───────────── day of week (0-6, Sun=0)
│ │ │ │ │
* * * * *
|
concurrencyPolicy |
Behavior |
Allow (default) |
Multiple Jobs can run concurrently |
Forbid |
Skip new Job if previous is still running |
Replace |
Kill the running Job and start a new one |
4. Rolling Updates and Rollbacks
4.1 Update Strategies
| Strategy |
Behavior |
RollingUpdate (default) |
Gradually replaces old pods with new ones |
Recreate |
Kills all old pods first, then creates new ones (downtime) |
4.2 Rolling Update Mechanics
| Deployment: nginx (replicas: 3, maxSurge: 1, maxUnavailable: 0)
Image update: nginx:1.25 → nginx:1.26
Step 1: Create new RS, scale up 1 new pod
Old RS: ●●● (3 pods) New RS: ○ (1 pod starting)
Step 2: New pod ready → scale down 1 old pod
Old RS: ●● (2 pods) New RS: ● (1 pod ready)
Step 3: Scale up 1 more new pod
Old RS: ●● (2 pods) New RS: ●○ (1 ready, 1 starting)
Step 4: New pod ready → scale down 1 old pod
Old RS: ● (1 pod) New RS: ●● (2 pods ready)
Step 5: Scale up 1 more new pod
Old RS: ● (1 pod) New RS: ●●○ (2 ready, 1 starting)
Step 6: New pod ready → scale down last old pod
Old RS: (0 pods) New RS: ●●● (3 pods ready) ✓
|
| Parameter |
Default |
Meaning |
maxSurge |
25% |
Max extra pods above desired count during update |
maxUnavailable |
25% |
Max pods that can be unavailable during update |
| spec:
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0 # zero-downtime: always have all replicas available
|
4.3 Triggering a Rolling Update
| # Method 1: Update the image
kubectl set image deployment/nginx nginx=nginx:1.26
# Method 2: Edit the deployment
kubectl edit deployment nginx
# Method 3: Apply an updated YAML
kubectl apply -f deployment.yaml
# Method 4: Patch
kubectl patch deployment nginx -p '{"spec":{"template":{"spec":{"containers":[{"name":"nginx","image":"nginx:1.26"}]}}}}'
|
4.4 Monitoring a Rollout
| # Watch rollout status (blocks until complete)
kubectl rollout status deployment/nginx
# Check rollout history
kubectl rollout history deployment/nginx
# REVISION CHANGE-CAUSE
# 1 <none>
# 2 <none>
# See details of a specific revision
kubectl rollout history deployment/nginx --revision=2
# Record the change cause (add --record or annotate)
kubectl annotate deployment/nginx kubernetes.io/change-cause="Updated to nginx:1.26"
|
4.5 Rollback
| # Rollback to the previous revision
kubectl rollout undo deployment/nginx
# Rollback to a specific revision
kubectl rollout undo deployment/nginx --to-revision=1
# Verify
kubectl rollout status deployment/nginx
kubectl describe deployment nginx | grep Image
|
4.6 Pause and Resume
For making multiple changes without triggering multiple rollouts:
| # Pause the deployment
kubectl rollout pause deployment/nginx
# Make multiple changes
kubectl set image deployment/nginx nginx=nginx:1.26
kubectl set resources deployment/nginx -c nginx --limits=cpu=200m,memory=256Mi
# Resume — triggers a single rollout with all changes
kubectl rollout resume deployment/nginx
|
4.7 Revision History Limit
| spec:
revisionHistoryLimit: 10 # default: 10 — how many old ReplicaSets to keep
|
Old ReplicaSets (scaled to 0) are kept for rollback. Setting this to 0 means you can't rollback.
5. Useful Commands Reference
| # --- Pods ---
kubectl get pods -o wide # show node, IP
kubectl get pods --show-labels # show labels
kubectl get pods -l app=nginx # filter by label
kubectl get pods --field-selector=status.phase=Running
kubectl describe pod <name> # detailed info
kubectl logs <pod> # container logs
kubectl logs <pod> -c <container> # specific container in multi-container pod
kubectl logs <pod> --previous # logs from previous crashed container
kubectl exec -it <pod> -- sh # shell into pod
kubectl delete pod <pod> --grace-period=0 --force # force delete
# --- Deployments ---
kubectl get deployments
kubectl describe deployment <name>
kubectl scale deployment <name> --replicas=5
kubectl autoscale deployment <name> --min=2 --max=10 --cpu-percent=80
kubectl rollout status deployment/<name>
kubectl rollout history deployment/<name>
kubectl rollout undo deployment/<name>
# --- DaemonSets ---
kubectl get ds -A # all namespaces
kubectl describe ds <name> -n kube-system
# --- StatefulSets ---
kubectl get sts
kubectl scale sts <name> --replicas=5
kubectl rollout status sts/<name>
# --- Jobs / CronJobs ---
kubectl get jobs
kubectl get cronjobs
kubectl describe job <name>
kubectl logs job/<name>
kubectl delete job <name> # also deletes completed pods
kubectl create job test-run --from=cronjob/backup # manual trigger
|
6. Practice Exercises
Exercise 1 — Pod Lifecycle
| # 1. Create a pod that exits successfully
kubectl run success --image=busybox --restart=Never -- echo "done"
kubectl get pod success # STATUS: Completed
# 2. Create a pod that fails
kubectl run fail --image=busybox --restart=Never -- sh -c "exit 1"
kubectl get pod fail # STATUS: Error
# 3. Create a pod with CrashLoopBackOff
kubectl run crash --image=busybox -- sh -c "exit 1"
# Wait 30 seconds
kubectl get pod crash # STATUS: CrashLoopBackOff
# 4. Inspect the restart count
kubectl describe pod crash | grep "Restart Count"
# 5. Clean up
kubectl delete pod success fail crash
|
Exercise 2 — Deployments and Scaling
| # 1. Create a deployment with 3 replicas
kubectl create deployment web --image=nginx:1.24 --replicas=3
# 2. Verify pods and ReplicaSet
kubectl get deploy,rs,pods
# 3. Scale to 5
kubectl scale deployment web --replicas=5
# 4. Update the image
kubectl set image deployment/web nginx=nginx:1.25
# 5. Watch the rollout
kubectl rollout status deployment/web
# 6. Check history
kubectl rollout history deployment/web
# 7. Rollback
kubectl rollout undo deployment/web
# 8. Verify the image is back to 1.24
kubectl describe deployment web | grep Image
# 9. Clean up
kubectl delete deployment web
|
Exercise 3 — DaemonSet
| # 1. Generate a Deployment YAML and convert to DaemonSet
kubectl create deployment ds-test --image=busybox --dry-run=client -o yaml \
-- sh -c "while true; do echo running; sleep 60; done" > ds.yaml
# 2. Edit ds.yaml:
# - Change kind: Deployment → kind: DaemonSet
# - Remove spec.replicas
# - Remove spec.strategy
# 3. Apply
kubectl apply -f ds.yaml
# 4. Verify — one pod per node
kubectl get pods -o wide
kubectl get ds
# 5. Clean up
kubectl delete -f ds.yaml
|
Exercise 4 — Jobs and CronJobs
| # 1. Create a Job that runs 5 completions with parallelism of 2
cat <<EOF | kubectl apply -f -
apiVersion: batch/v1
kind: Job
metadata:
name: counter
spec:
completions: 5
parallelism: 2
template:
spec:
restartPolicy: Never
containers:
- name: counter
image: busybox
command: ["sh", "-c", "echo Processing item; sleep 5"]
EOF
# 2. Watch pods being created
kubectl get pods -w
# 3. Check Job status
kubectl get job counter
# 4. Create a CronJob that runs every minute
kubectl create cronjob minute-job --image=busybox --schedule="*/1 * * * *" \
-- echo "tick"
# 5. Wait 2 minutes and check
kubectl get cronjobs
kubectl get jobs
kubectl get pods
# 6. Manually trigger a Job from the CronJob
kubectl create job manual-run --from=cronjob/minute-job
# 7. Clean up
kubectl delete job counter
kubectl delete cronjob minute-job
kubectl delete job manual-run
|
Exercise 5 — Rolling Update with Zero Downtime
| # 1. Create a deployment
kubectl create deployment rolling --image=nginx:1.24 --replicas=4
# 2. Set strategy to zero-downtime
kubectl patch deployment rolling -p '{"spec":{"strategy":{"rollingUpdate":{"maxSurge":1,"maxUnavailable":0}}}}'
# 3. Update image and watch
kubectl set image deployment/rolling nginx=nginx:1.25
kubectl rollout status deployment/rolling
# 4. Verify all pods are on the new version
kubectl get pods -o jsonpath='{range .items[*]}{.spec.containers[0].image}{"\n"}{end}'
# 5. Rollback to revision 1
kubectl rollout undo deployment/rolling --to-revision=1
# 6. Verify
kubectl describe deployment rolling | grep Image
# 7. Clean up
kubectl delete deployment rolling
|
7. Key Takeaways for the CKA Exam
| Point |
Detail |
kubectl run creates a Pod |
Use kubectl create deployment for Deployments |
--dry-run=client -o yaml |
Generate YAML for any resource — fastest way to start |
| Deployments manage ReplicaSets |
Don't create ReplicaSets directly |
| DaemonSet = one pod per node |
No imperative create — convert from Deployment YAML |
| StatefulSet needs a headless Service |
serviceName field is required |
Jobs use restartPolicy: Never or OnFailure |
Never Always |
kubectl rollout undo |
Rollback to previous or specific revision |
maxSurge + maxUnavailable |
Control rolling update speed and availability |
kubectl create job --from=cronjob/ |
Manually trigger a CronJob |
| Know probe types |
httpGet, tcpSocket, exec — and the difference between liveness, readiness, startup |
| Init containers run first |
All must succeed before app containers start |
Previous: 06-kubeconfig-contexts.md — Kubeconfig & Contexts
Next: 08-scheduling.md — Scheduling