CKA Workloads Master Reference Link to heading
Domain 2 — Workloads and Scheduling Link to heading
Deployment Rollouts Link to heading
# View history
k rollout history deploy <name> -n <ns>
# Inspect specific revision
k rollout history deploy <name> -n <ns> --revision=2
# Rollback to previous
k rollout undo deploy <name> -n <ns>
# Rollback to specific revision
k rollout undo deploy <name> -n <ns> --to-revision=2
# Force restart (picks up CM/Secret changes)
k rollout restart deploy <name> -n <ns>
StatefulSet Update Strategies Link to heading
| Strategy | Behavior | Action Required |
|---|---|---|
RollingUpdate | Auto-updates pods | Nothing extra |
OnDelete | Pods NOT updated automatically | Must delete pods manually |
StatefulSet default = OnDelete Deployment default = RollingUpdate
Force StatefulSet Update (OnDelete) Link to heading
k edit sts <name> -n <ns> # make change
k delete pod db-0 db-1 db-2 -n <ns> # delete all pods — they recreate with new spec
StatefulSet Rollback (OnDelete) Link to heading
k rollout undo sts <name> -n <ns>
k delete pod db-0 db-1 db-2 -n <ns> # still need manual delete
rollout restart bypasses OnDelete — good habit, always use it.
Init Containers Link to heading
Troubleshoot Link to heading
# Check init container logs (use -c flag)
k logs -n <ns> -l app=<label> -c <init-container-name>
# Common issue: command path vs volumeMount path mismatch
# Command: cp /credentials/* /config/
# VolumeMount mountPath: /tmp ← wrong, fix to /credentials
Pattern Link to heading
initContainers:
- name: init-config
image: bash:5
command:
- sh
- -c
- cp /credentials/* /config/
volumeMounts:
- name: secret
mountPath: /credentials
- name: config
mountPath: /config
Secrets Link to heading
Add Key to Existing Secret Link to heading
# Encode value (MUST use -n and -w 0)
echo -n "db.internal" | base64 -w 0
# Edit secret
k edit secret <name> -n <ns>
# Add under data:
# host: <base64-value>
# Restart deployment to pick up new key
k rollout restart deploy <name> -n <ns>
# Verify
k exec deploy/<name> -n <ns> -- cat /etc/app-config/host
Trap: echo "value" | base64 encodes trailing newline — always use -n
Trap: -w 0 prevents line wrapping on long values
HPA Link to heading
# Imperative
k -n <ns> autoscale deploy <name> --min=2 --max=5 --cpu-percent=50
# YAML
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: landing
namespace: landing-app
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: landing
minReplicas: 2
maxReplicas: 5
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 50
<unknown> on HPA metrics = no metrics-server installed. Expected, not a bug.
Node Scheduling Link to heading
NodeSelector Link to heading
spec:
nodeSelector:
node-type: configurator
Taints and Tolerations Link to heading
# Get taint key (never guess or type from memory)
k describe node <name> | grep Taint
# Common controlplane taint
# node-role.kubernetes.io/control-plane:NoSchedule
# Taint a node
k taint nodes <node> <key>=<value>:<effect>
# Remove a taint (trailing -)
k taint nodes <node> <key>=<value>:<effect>-
tolerations:
- key: node-role.kubernetes.io/control-plane
effect: NoSchedule
operator: Exists
NoSchedule vs NoExecute:
NoSchedule— blocks NEW pods only; pods already running stay putNoExecute— evicts ALL running pods on the node immediately unless they tolerate it
Multiple taints = multiple toleration entries. A toleration for taint A does NOT cover taint B — each taint needs its own entry in the tolerations array:
tolerations:
- key: my-cool-key
value: my-awesome-value
effect: NoSchedule
- key: upgrade
value: in-progress
effect: NoExecute
Trap: Adding a NoExecute taint to a node immediately evicts pods that only tolerate the node’s OTHER (NoSchedule) taint — add a second tolerations entry for the new taint specifically, don’t replace the first.
Label Node Link to heading
k label node <name> <key>=<value>
k label node node01 node-type=configurator
Pod Affinity / Anti-Affinity Link to heading
Mental model:
podAffinity= attract — co-locate with pods matching the label selectorpodAntiAffinity= repel — avoid nodes where pods matching the label selector runtopologyKey: kubernetes.io/hostname= “node” is the unit of comparison
Required vs Preferred:
| Type | Behavior |
|---|---|
requiredDuringSchedulingIgnoredDuringExecution | Hard rule — Pod stays Pending if no node satisfies it |
preferredDuringSchedulingIgnoredDuringExecution | Soft rule — scheduler tries, but schedules anyway if no match |
Pod Affinity (preferred — co-locate) Link to heading
spec:
affinity:
podAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchLabels:
level: restricted
topologyKey: kubernetes.io/hostname
Schedules onto the same node as pods labeled level=restricted. If the anchor pod moves, recreating this pod will follow it — but it’s preferred, not guaranteed.
Pod Anti-Affinity (required — never co-locate) Link to heading
spec:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
level: restricted
topologyKey: kubernetes.io/hostname
Pod can NEVER land on a node that has a level=restricted pod. If level=restricted pods are running on EVERY node, this pod stays Pending — that’s correct, not a bug.
Trap: weight only exists under preferred... — required... is a flat list of labelSelector + topologyKey, no weight field.
Trap: Affinity re-evaluates on each (re)scheduling, not just once — moving the anchor pod and recreating the affinity pod tests this dynamically.
PriorityClass / Preemption Link to heading
# List PriorityClasses + find what an existing pod uses
k get priorityclass
k -n <ns> get pod -o yaml | grep -i priority
Preemption pattern: create a new Pod with a HIGHER priorityClassName than an existing Pod. If the cluster can’t fit both, the scheduler evicts (preempts) the lower-priority Pod to make room.
apiVersion: v1
kind: Pod
metadata:
name: important
namespace: lion
spec:
priorityClassName: level3
containers:
- name: important
image: nginx:alpine
resources:
requests:
memory: 1Gi
Verify preemption happened:
k -n lion get pod # old pod gone or Pending, new one Running
k get events -A --sort-by='{.metadata.creationTimestamp}' | tail
# Look for "Preempted" in the event reason
Trap: Higher number = higher priority (opposite of process nice values). Custom PriorityClasses default globalDefault: false unless explicitly told to set the cluster default.
DaemonSet Link to heading
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: configurator
namespace: configurator
spec:
selector:
matchLabels:
name: configurator
template:
metadata:
labels:
name: configurator
spec:
nodeSelector:
node-type: configurator
containers:
- name: configurator
image: bash:5
command:
- bash
- -c
- echo <value> > /mount/config && sleep infinity
volumeMounts:
- name: vol
mountPath: /mount
volumes:
- name: vol
hostPath:
path: /configurator
Jobs Link to heading
Trigger CronJob Manually Link to heading
k create job <name> --from=cronjob/<cronjob-name> -n <ns>
k -n ops create job cleanup-manual --from=cronjob/cleanup
Check Job Completed Link to heading
k get jobs -n <ns>
k logs -l job-name=<name> -n <ns>
Drain and Uncordon Link to heading
k drain <node> --ignore-daemonsets # evict pods
k drain <node> --ignore-daemonsets --delete-emptydir-data # if emptyDir pods
k uncordon <node> # allow scheduling again
k get nodes # verify Ready
base64 Encoding Reference Link to heading
echo -n "value" | base64 -w 0 # encode (no newline, no wrap)
echo "dmFsdWU=" | base64 -d # decode