Skip to main content

Workloads

Exam relevance: CKA ✅ (Workloads & Scheduling — 15%) | CKAD ✅ (Application Design & Build — 20%, Application Deployment — 20%)


Workload Resource Hierarchy​

Deployment
└── manages → ReplicaSet (v1)
└── manages → Pod, Pod, Pod
└── creates → ReplicaSet (v2) ← on update
└── manages → Pod, Pod, Pod

StatefulSet
└── manages → Pod-0, Pod-1, Pod-2 (ordered, stable names)

DaemonSet
└── manages → Pod (one per node)

Job
└── creates → Pod (runs to completion)

CronJob
└── creates → Job (on schedule)
└── creates → Pod

Deployments — The Primary Workload​

A Deployment manages a ReplicaSet, which manages Pods. You almost never create Pods or ReplicaSets directly.

Full Deployment YAML​

apiVersion: apps/v1
kind: Deployment
metadata:
name: webapp
namespace: default
labels:
app: webapp
spec:
replicas: 3 # Desired number of pods
revisionHistoryLimit: 10 # How many old ReplicaSets to keep
selector: # MUST match pod template labels
matchLabels:
app: webapp
strategy: # How to update pods
type: RollingUpdate # RollingUpdate or Recreate
rollingUpdate:
maxSurge: 1 # Max extra pods during update
maxUnavailable: 0 # Max pods that can be down during update
template: # Pod template — everything below is a Pod spec
metadata:
labels:
app: webapp # MUST match selector above
spec:
containers:
- name: webapp
image: nginx:1.25
ports:
- containerPort: 80
resources:
requests:
cpu: "100m"
memory: "128Mi"
limits:
cpu: "500m"
memory: "256Mi"

Critical Rule: selector.matchLabels MUST match template.metadata.labels​

If they don't match, the Deployment can't find its Pods. This is a common exam trap.

Create Deployment Fast (Exam)​

# Imperative
kubectl create deployment webapp --image=nginx:1.25 --replicas=3

# Generate YAML
kubectl create deployment webapp --image=nginx:1.25 --replicas=3 \
--dry-run=client -o yaml > deployment.yaml

Rolling Updates and Rollbacks​

Update Strategies​

StrategyBehaviorWhen to Use
RollingUpdateGradually replaces old pods with new onesDefault. Zero-downtime updates.
RecreateKills ALL old pods, then creates new onesWhen old and new versions can't coexist (DB schema changes)

How RollingUpdate Works​

Initial state:  ReplicaSet-v1 (3 pods running nginx:1.25)

kubectl set image deployment/webapp webapp=nginx:1.26

Step 1: Create ReplicaSet-v2 with 1 pod (nginx:1.26)
ReplicaSet-v1: 3 pods ReplicaSet-v2: 1 pod (maxSurge=1 → 4 total OK)

Step 2: Scale down ReplicaSet-v1 to 2
ReplicaSet-v1: 2 pods ReplicaSet-v2: 1 pod

Step 3: Scale up ReplicaSet-v2 to 2
ReplicaSet-v1: 2 pods ReplicaSet-v2: 2 pods

Step 4: Scale down ReplicaSet-v1 to 1
...and so on until...

Final: ReplicaSet-v1: 0 pods ReplicaSet-v2: 3 pods

Triggering Updates​

# Method 1: Set image directly
kubectl set image deployment/webapp webapp=nginx:1.26

# Method 2: Edit the deployment
kubectl edit deployment webapp

# Method 3: Apply updated YAML
kubectl apply -f deployment.yaml

# Record the change cause (useful for rollback history)
kubectl set image deployment/webapp webapp=nginx:1.26
kubectl annotate deployment/webapp kubernetes.io/change-cause="Update to nginx 1.26"

Checking Rollout Status​

# Watch rolling update progress
kubectl rollout status deployment/webapp

# View rollout history
kubectl rollout history deployment/webapp

# View a specific revision's details
kubectl rollout history deployment/webapp --revision=2

Rollback​

# Rollback to previous version
kubectl rollout undo deployment/webapp

# Rollback to a specific revision
kubectl rollout undo deployment/webapp --to-revision=2

# Pause/Resume rollout (for canary-like testing)
kubectl rollout pause deployment/webapp
# ... test the partial rollout ...
kubectl rollout resume deployment/webapp

ReplicaSets​

A ReplicaSet ensures a specified number of pod replicas are running. You rarely create them directly — Deployments manage them.

apiVersion: apps/v1
kind: ReplicaSet
metadata:
name: webapp-rs
spec:
replicas: 3
selector:
matchLabels:
app: webapp
template:
metadata:
labels:
app: webapp
spec:
containers:
- name: webapp
image: nginx:1.25

Key behavior: A ReplicaSet will adopt existing pods that match its selector, even if they weren't created by the ReplicaSet. This can cause unexpected behavior if labels overlap.

Scaling​

# Scale via kubectl
kubectl scale deployment webapp --replicas=5

# Scale via edit
kubectl edit deployment webapp
# Change spec.replicas

# Scale via patch
kubectl patch deployment webapp -p '{"spec":{"replicas":5}}'

DaemonSets​

A DaemonSet ensures one copy of a pod runs on every node (or a subset of nodes). When a new node joins the cluster, the DaemonSet automatically creates a pod on it.

Use cases:

  • Log collectors (fluentd, filebeat)
  • Monitoring agents (node-exporter, datadog-agent)
  • Network plugins (kube-proxy, calico-node, cilium)
  • Storage daemons (ceph, glusterd)
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: # Often needed to run on control plane
- key: node-role.kubernetes.io/control-plane
effect: NoSchedule
containers:
- name: fluentd
image: fluentd:v1.16
volumeMounts:
- name: varlog
mountPath: /var/log
volumes:
- name: varlog
hostPath:
path: /var/log

Targeting Specific Nodes​

Use nodeSelector or nodeAffinity to limit which nodes get the DaemonSet pod:

spec:
template:
spec:
nodeSelector:
disk: ssd # Only nodes with label disk=ssd

DaemonSet Updates​

DaemonSets support rolling updates (default) or OnDelete (manual):

spec:
updateStrategy:
type: RollingUpdate # or OnDelete
rollingUpdate:
maxUnavailable: 1 # Update one node at a time

StatefulSets​

StatefulSets are for stateful applications that need:

  • Stable network identity: Pod names are predictable (mysql-0, mysql-1, mysql-2)
  • Stable storage: Each pod gets its own PersistentVolumeClaim that survives restarts
  • Ordered deployment: Pods are created in order (0, 1, 2) and scaled down in reverse (2, 1, 0)

Use cases: Databases (MySQL, PostgreSQL), message queues (Kafka, RabbitMQ), distributed systems (Zookeeper, etcd)

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
volumeMounts:
- name: data
mountPath: /var/lib/mysql
volumeClaimTemplates: # Each pod gets its OWN PVC
- metadata:
name: data
spec:
accessModes: ["ReadWriteOnce"]
storageClassName: standard
resources:
requests:
storage: 10Gi

StatefulSet REQUIRES a Headless Service​

apiVersion: v1
kind: Service
metadata:
name: mysql-headless
spec:
clusterIP: None # Headless — no cluster IP
selector:
app: mysql
ports:
- port: 3306

With this, each pod gets a DNS record:

mysql-0.mysql-headless.default.svc.cluster.local
mysql-1.mysql-headless.default.svc.cluster.local
mysql-2.mysql-headless.default.svc.cluster.local

StatefulSet vs Deployment​

FeatureDeploymentStatefulSet
Pod namesRandom (webapp-5d78c-abc12)Ordered (mysql-0, mysql-1)
Pod creationParallelSequential (waits for previous to be Ready)
Pod deletionAny orderReverse order (2 → 1 → 0)
StorageAll pods share PVC or no PVCEach pod gets its own PVC
DNSVia Service onlyIndividual pod DNS records
Use caseStateless appsStateful apps (databases)

Scaling StatefulSets​

kubectl scale statefulset mysql --replicas=5
# Creates mysql-3, then mysql-4 (in order)

kubectl scale statefulset mysql --replicas=2
# Deletes mysql-4, then mysql-3, then mysql-2 (reverse order)
# PVCs are NOT deleted — they persist for reuse

Jobs​

A Job creates one or more pods and ensures they run to completion (exit code 0). Unlike Deployments, Jobs don't restart pods that succeed.

apiVersion: batch/v1
kind: Job
metadata:
name: data-processor
spec:
completions: 5 # Total successful completions needed
parallelism: 2 # How many pods run simultaneously
backoffLimit: 3 # Retries before marking job as failed
activeDeadlineSeconds: 300 # Kill the whole job after 5 minutes
template:
spec:
restartPolicy: Never # MUST be Never or OnFailure (not Always)
containers:
- name: processor
image: busybox
command: ["sh", "-c", "echo Processing batch $JOB_COMPLETION_INDEX && sleep 5"]

Job Patterns​

completionsparallelismBehavior
11Single pod runs to completion (default)
515 pods run sequentially
53Up to 3 pods run in parallel until 5 succeed
—3Work queue — 3 workers process until done

Quick Job Creation (Exam)​

kubectl create job my-job --image=busybox -- sh -c "echo hello && sleep 30"

# Generate YAML
kubectl create job my-job --image=busybox --dry-run=client -o yaml -- sh -c "echo hello"

Check Job Status​

kubectl get jobs
kubectl describe job data-processor
kubectl get pods -l job-name=data-processor
kubectl logs job/data-processor # Logs of the job's pod

CronJobs​

A CronJob creates Jobs on a schedule, using standard cron syntax.

apiVersion: batch/v1
kind: CronJob
metadata:
name: daily-cleanup
spec:
schedule: "0 2 * * *" # Every day at 2:00 AM
concurrencyPolicy: Forbid # Don't start new if previous still running
successfulJobsHistoryLimit: 3 # Keep last 3 successful jobs
failedJobsHistoryLimit: 1 # Keep last 1 failed job
startingDeadlineSeconds: 200 # Max seconds late a job can start
jobTemplate:
spec:
template:
spec:
restartPolicy: OnFailure
containers:
- name: cleanup
image: busybox
command: ["sh", "-c", "echo Cleaning up old data"]

Cron Schedule Syntax​

┌───────────── minute (0-59)
│ ┌───────────── hour (0-23)
│ │ ┌───────────── day of month (1-31)
│ │ │ ┌───────────── month (1-12)
│ │ │ │ ┌───────────── day of week (0-6, Sun=0)
│ │ │ │ │
* * * * *

Examples:
*/5 * * * * → every 5 minutes
0 * * * * → every hour
0 0 * * * → every day at midnight
0 0 * * 0 → every Sunday at midnight
30 8 1 * * → 8:30 AM on the 1st of every month

Concurrency Policies​

PolicyBehavior
AllowMultiple jobs can run simultaneously (default)
ForbidSkip new job if previous is still running
ReplaceKill the running job and start new one

Quick CronJob Creation (Exam)​

kubectl create cronjob daily-backup --image=busybox \
--schedule="0 2 * * *" \
-- sh -c "echo backup completed"

Pod Restart Policies​

PolicyBehaviorUsed By
AlwaysRestart container regardless of exit codeDeployments, ReplicaSets, DaemonSets, StatefulSets
OnFailureRestart only on non-zero exit codeJobs
NeverNever restartJobs (one-shot)

Important: This is set at the Pod spec level (spec.restartPolicy), not the container level.


Horizontal Pod Autoscaler (HPA)​

HPA automatically scales the number of pod replicas based on CPU, memory, or custom metrics.

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: webapp-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: webapp
minReplicas: 2
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70 # Scale when avg CPU > 70%
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 80

Quick HPA (Exam)​

# Requires metrics-server to be running
kubectl autoscale deployment webapp --min=2 --max=10 --cpu-percent=70

# Check HPA
kubectl get hpa
kubectl describe hpa webapp-hpa

Prerequisite: The Deployment's pods MUST have resources.requests set for CPU/memory. Without requests, HPA can't calculate utilization percentage.


Key Takeaways​

  1. Deployments are the default choice for stateless apps — they manage ReplicaSets and rolling updates
  2. RollingUpdate strategy gives zero-downtime deploys; Recreate kills all pods first
  3. kubectl rollout undo is how you rollback — know it cold
  4. StatefulSets give stable names, ordered ops, and per-pod storage — required for databases
  5. DaemonSets ensure one pod per node — used for monitoring/logging agents
  6. Jobs run to completion; CronJobs run Jobs on a schedule
  7. HPA needs metrics-server AND resource requests to work
  8. selector.matchLabels MUST match template.metadata.labels — this is a frequent exam gotcha