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

StrategyBehaviorAction Required
RollingUpdateAuto-updates podsNothing extra
OnDeletePods NOT updated automaticallyMust 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 put
  • NoExecute — 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 selector
  • podAntiAffinity = repel — avoid nodes where pods matching the label selector run
  • topologyKey: kubernetes.io/hostname = “node” is the unit of comparison

Required vs Preferred:

TypeBehavior
requiredDuringSchedulingIgnoredDuringExecutionHard rule — Pod stays Pending if no node satisfies it
preferredDuringSchedulingIgnoredDuringExecutionSoft 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