The Exam Blueprint · CKA · D2 · Workloads & Scheduling · 15%

Workloads & Scheduling

Domain 2 of the CKA blueprint is the domain closest to what most people picture when they think "Kubernetes": writing a Deployment, rolling out a new image without downtime, and getting the right Pod onto the right node. It is worth 15% of the exam — smaller than Troubleshooting or Cluster Architecture, but every task in it is something you will do again in the other four domains, because a broken rollout or a mis-scheduled Pod is exactly what Domain 5's troubleshooting tasks hand you to fix. This page covers the five competency groups the CNCF curriculum lists under it: Deployments and their rolling-update and rollback mechanics; ConfigMaps and Secrets for separating configuration from image; workload autoscaling; the self-healing primitives that make Kubernetes reach for a fix before a human does; and Pod admission and scheduling, including affinity, taints and tolerations, and resource requests and limits.

☺ Explain it like I'm 10

Picture a moving company that guarantees your furniture always arrives, no matter what. A foreman decides how many movers a job needs, and when the company switches to a newer, faster truck, it swaps trucks one at a time so a job in progress never stops mid-move — that's a rolling update, and if the new truck turns out to have a flat tire, the foreman just swaps back. Every mover carries the same instruction sheet (a ConfigMap) but keeps the alarm codes in a separate, locked pouch (a Secret) instead of writing them on the sheet. If a mover collapses on the job, dispatch sends a replacement automatically — nobody has to notice and phone it in. During the holiday rush, the company hires extra movers by itself when calls spike, and lets them go once things quiet down. And the dispatcher won't send a mover without a forklift license to a forklift job, and won't book two heavy jobs onto a truck that doesn't have room for both. That's the whole domain, one delivery company.

🦫🦥Your hosts for this topic: Benny the Beaver & Sol the Sloth — Benny turns the manifests into running, self-healing workloads; Sol does the slow, honest arithmetic on requests and limits before the scheduler ever gets asked to place anything.

The domain, and where it sits in the exam

☺ Like you're 10: This is one of five graded sections of the CKA, and it's worth about one in every seven questions — smaller than two of its neighbors, but everything it teaches shows up again inside them.

The CKA curriculum (version 1.35) publishes five domains that sum to exactly 100%, and Domain 2 sits in the middle of the list by weight, though not by importance — a rollout you can't reason about, or a Pod that won't schedule, is a routine source of the failures Domain 5 asks you to diagnose.

#DomainWeight
D1Cluster Architecture, Installation and Configuration25%
D2Workloads and Scheduling15%
D3Servicing and Networking20%
D4Storage10%
D5Troubleshooting30%

Across the five domains there are 27 published competencies in total; Domain 2 accounts for five of them — Deployments and rolling updates/rollbacks, ConfigMaps and Secrets, workload autoscaling, self-healing primitives, and Pod scheduling. The exam itself is performance-based: two hours, a browser terminal, several live pre-provisioned clusters, and a set of tasks graded purely on the end state of the cluster once time is up — not on the commands you typed to get there. Nothing on this page substitutes for typing every command yourself; see the CKA overview on the Platform Engineering course for the full picture of what sitting the exam is actually like, and this course's own CKA study plan and practice tasks for a structured build-up. If Kubernetes operations is the floor you want to build a platform-engineering career on top of, the CNCF's Golden Kubestronaut ladder — covered on this platform's Golden Astronaut course — is where that goes next, once all five Kubestronaut certifications (CKA, CKAD, CKS, KCNA, KCSA) are behind you.

⚠ Verify this before you book

This is an independent, unofficial study resource, not affiliated with the CNCF or the Linux Foundation. Domain weights, the curriculum version, the exam duration, the pass mark, and the price all change between curriculum releases — treat everything above as a planning signal. Confirm the current numbers on the official Linux Foundation CKA page and CNCF's certification page, and check that the curriculum version you're studying matches the one you'll sit at github.com/cncf/curriculum before you pay for anything.

Deployments: rolling updates and rollbacks

☺ Like you're 10: A Deployment is the thing that owns "how many, and which version" — you tell it the new picture you want, and it swaps the old one out gradually instead of all at once.

A Deployment doesn't manage Pods directly — it manages a ReplicaSet, and the ReplicaSet manages the Pods. Every time you change a Deployment's Pod template (a new image tag, a new environment variable), it creates a new ReplicaSet and hands the transition to a rollout strategy, keeping the old ReplicaSet around at zero replicas so a rollback has something to scale straight back up.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: checkout-api
  labels:
    app: checkout-api
spec:
  replicas: 3
  revisionHistoryLimit: 5          # how many old ReplicaSets to keep for rollback
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1                  # at most 1 extra Pod above desired, mid-rollout
      maxUnavailable: 0            # never drop below desired while rolling
  selector:
    matchLabels:
      app: checkout-api
  template:
    metadata:
      labels:
        app: checkout-api
    spec:
      containers:
        - name: checkout-api
          image: registry.example.com/checkout-api:1.25.0
          ports:
            - containerPort: 8080
          resources:
            requests:
              cpu: 250m
              memory: 256Mi
            limits:
              cpu: 500m
              memory: 512Mi

maxSurge and maxUnavailable can each be an absolute number or a percentage, and together they set the shape of the rollout: maxSurge:1, maxUnavailable:0 never drops below the desired replica count but briefly runs one Pod above it; maxSurge:0, maxUnavailable:1 never exceeds the desired count but briefly runs one below it. A new Pod only counts as available once it passes its readiness probe — see the self-healing section below — so a rollout with a broken readiness check simply stalls rather than replacing healthy Pods with broken ones.

Rolling update: checkout-api v1 → v2 Before Mid-rollout (maxSurge 1, maxUnavailable 0) After RS checkout-api-v1 desired: 3 Pod v1 Pod v1 Pod v1 RS v1 · desired: 2 Pod v1 Pod v1 RS v2 · desired: 2 Pod v2 Pod v2 4 Pods running (3 desired + 1 surge) RS checkout-api-v2 desired: 3 Pod v2 Pod v2 Pod v2

Two commands cover most exam rollout tasks: kubectl rollout status deployment/checkout-api blocks until the rollout finishes (or reports why it's stuck), and kubectl rollout undo deployment/checkout-api rolls back to the previous revision. kubectl rollout history deployment/checkout-api lists revisions, and --to-revision=N on undo targets a specific one instead of just "one back" — a detail worth knowing cold, because the default undo only goes one step.

kubectl set image deployment/checkout-api checkout-api=registry.example.com/checkout-api:1.25.1
kubectl rollout status deployment/checkout-api
kubectl rollout history deployment/checkout-api
kubectl rollout history deployment/checkout-api --revision=2
kubectl rollout undo deployment/checkout-api                  # back one revision
kubectl rollout undo deployment/checkout-api --to-revision=1  # back to a specific one

ConfigMaps & Secrets: configuration out of the image

☺ Like you're 10: ConfigMaps and Secrets are how you hand a container its instructions without baking them into the image — Secrets just keep the sensitive lines in a separate pouch.

A ConfigMap holds non-sensitive key/value configuration; a Secret holds the same shape of data but is base64-encoded in the API and intended for values like credentials or tokens. Both can be consumed the same three ways: as individual environment variables, as a whole set of environment variables via envFrom, or as files mounted into the container's filesystem via a volume.

apiVersion: v1
kind: ConfigMap
metadata:
  name: checkout-api-config
data:
  LOG_LEVEL: "info"
  CACHE_TTL_SECONDS: "30"
---
apiVersion: v1
kind: Secret
metadata:
  name: checkout-api-secrets
type: Opaque
stringData:                        # plaintext in; the API server base64-encodes it for you
  DB_PASSWORD: "correct-horse-battery-staple"
    spec:
      containers:
        - name: checkout-api
          image: registry.example.com/checkout-api:1.25.0
          envFrom:
            - configMapRef:
                name: checkout-api-config
            - secretRef:
                name: checkout-api-secrets
          volumeMounts:
            - name: config-volume
              mountPath: /etc/checkout-api
              readOnly: true
      volumes:
        - name: config-volume
          configMap:
            name: checkout-api-config
⚠ Watch out — a Secret is encoded, not encrypted

Base64 is an encoding, not encryption — anyone with get access to the Secret object (or read access to etcd, if it isn't configured with encryption at rest) can trivially decode it. Treat RBAC on Secrets, not the base64 layer, as the actual security boundary: kubectl get secret checkout-api-secrets -o jsonpath='{.data.DB_PASSWORD}' | base64 -d recovers the plaintext in one line. And updating a ConfigMap or Secret does not restart Pods that reference it — env-var-mounted values are frozen at Pod start, and even volume-mounted ConfigMaps only refresh on the kubelet's sync interval, not instantly. A common pattern to force a rollout on config change is a checksum annotation on the Pod template that changes whenever the ConfigMap's content does.

This exam blueprint stops at using Secrets, not securing the cluster's secret-handling posture end to end — encryption providers, external secret stores, and admission-time policy on who may read which Secret are covered on the security side of the certification ladder; see RBAC & admission control in this course and the CKS blueprint if that's the depth you need next.

Workload autoscaling

☺ Like you're 10: The HorizontalPodAutoscaler is a thermostat for replica count — it watches a metric and turns the number of Pods up or down to keep that metric near a target.

The HorizontalPodAutoscaler (HPA) is the autoscaler named explicitly on the CKA curriculum. It polls a metric — most commonly CPU utilization — against a target you set, and adjusts replicas on a Deployment, ReplicaSet, or StatefulSet up or down to keep the metric near that target, between a minReplicas and maxReplicas floor and ceiling. It depends on the metrics-server add-on being installed in the cluster; without it, kubectl get hpa shows <unknown> where the current metric value should be, and the HPA controller can't make a decision.

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: checkout-api
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: checkout-api
  minReplicas: 3
  maxReplicas: 12
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 70
  behavior:
    scaleDown:
      stabilizationWindowSeconds: 300   # wait 5 min of low usage before scaling down
kubectl autoscale deployment checkout-api --min=3 --max=12 --cpu-percent=70
kubectl get hpa checkout-api --watch
kubectl top pods -l app=checkout-api        # needs metrics-server; requests must be set to compute %

The HPA scales replica count, horizontally. It is a distinct concept from the VerticalPodAutoscaler (VPA), which resizes a single Pod's requests/limits, and from the Cluster Autoscaler or its newer alternative Karpenter, which add or remove nodes when Pods can't schedule for lack of capacity. The CKA blueprint names the HPA specifically; the other three sit in this course's own Autoscaling: HPA, VPA & Cluster Autoscaler deep dive, along with the Karpenter and Cluster Autoscaler tool pages if you want the node-level half of the story.

Self-healing primitives

☺ Like you're 10: Kubernetes doesn't wait for someone to notice a broken Pod — probes tell it how to check, and a control loop keeps nudging reality back toward what you asked for.

Self-healing on this exam is really two separate mechanisms working together. The first is the Deployment/ReplicaSet reconciliation loop — the same controller pattern that runs the whole platform: a controller continuously compares the number of Pods that exist against replicas in spec, and creates or deletes Pods to close the gap, whether a Pod died from a node failure, an eviction, or a human running kubectl delete pod by hand. The second is probes, which tell the kubelet how to judge a single container's health from the inside.

    spec:
      containers:
        - name: checkout-api
          image: registry.example.com/checkout-api:1.25.0
          startupProbe:                 # gates the other two probes until the app is up
            httpGet:
              path: /healthz/startup
              port: 8080
            failureThreshold: 30
            periodSeconds: 2            # up to 60s to start before liveness can kill it
          readinessProbe:
            httpGet:
              path: /healthz/ready
              port: 8080
            initialDelaySeconds: 5
            periodSeconds: 10
          livenessProbe:
            httpGet:
              path: /healthz/live
              port: 8080
            initialDelaySeconds: 15
            periodSeconds: 20
            failureThreshold: 3

Three probes, three different consequences on failure — this is one of the most reliably tested distinctions in the domain. A failing readiness probe removes the Pod from a Service's Endpoints without killing it — traffic stops arriving, the Pod keeps running and can recover on its own. A failing liveness probe gets the container killed and restarted by the kubelet, subject to restartPolicy (Always is the Deployment-relevant default). A startup probe exists for slow-booting applications: while it's failing, liveness and readiness aren't evaluated at all, so a legitimately slow startup can't be mistaken for a liveness failure and killed mid-boot.

◆ Key idea

Readiness controls traffic. Liveness controls whether the container gets restarted. A container that's alive but not ready is normal and healthy during startup or a temporary downstream outage — it should never be killed for that. Confusing the two is the classic way to turn a brief dependency hiccup into a full restart storm.

Pod admission & scheduling

☺ Like you're 10: Two gates decide where a Pod can land — taints push most Pods away from a node unless they're specifically allowed in, and affinity pulls a Pod only toward nodes that match rules you set.

A taint on a node repels Pods unless the Pod carries a matching toleration. Effects come in three flavors: NoSchedule blocks new Pods from scheduling there but doesn't touch Pods already running; PreferNoSchedule is a soft version the scheduler tries to honor but won't guarantee; NoExecute both blocks new scheduling and evicts already-running Pods that don't tolerate it. Node affinity works the opposite direction — it's a rule on the Pod that says which node labels it requires or prefers, and it's evaluated independently of taints. Passing the taint gate doesn't satisfy an affinity rule, and vice versa; a Pod has to clear both.

kubectl taint nodes node-gpu-1 gpu=true:NoSchedule
kubectl label nodes node-ssd-1 disk=ssd
apiVersion: v1
kind: Pod
metadata:
  name: checkout-worker
spec:
  tolerations:
    - key: "gpu"
      operator: "Equal"
      value: "true"
      effect: "NoSchedule"
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
          - matchExpressions:
              - key: disk
                operator: In
                values: ["ssd"]
    podAntiAffinity:
      preferredDuringSchedulingIgnoredDuringExecution:
        - weight: 100
          podAffinityTerm:
            labelSelector:
              matchLabels:
                app: checkout-worker
            topologyKey: kubernetes.io/hostname   # spread replicas across nodes
  containers:
    - name: checkout-worker
      image: registry.example.com/checkout-worker:1.9.0
      resources:
        requests:
          cpu: "1"
          memory: 2Gi
        limits:
          cpu: "2"
          memory: 2Gi
Pod: checkout-worker tolerates gpu=true:NoSchedule requires disk=ssd Node A taint: gpu=true:NoSchedule label: disk=ssd Scheduled here toleration + affinity match Node B no taint label: disk=hdd Rejected affinity mismatch (needs ssd) Node C taint: spot=true:NoSchedule label: disk=ssd Rejected taint not tolerated (spot) Both gates must clear: an untolerated taint rejects on its own; a matched taint still needs affinity to also match.

Resource requests are what the scheduler actually uses to place a Pod — it sums the requests of Pods already on a node and only places a new one where the requested amount still fits, regardless of real-time usage. Limits cap what a container may use once running; exceed a memory limit and the container is OOMKilled, but exceed a CPU limit and it's merely throttled, never killed. The combination of requests and limits on every container in a Pod determines its QoS class: Guaranteed when every container sets requests equal to limits for both CPU and memory, Burstable when at least one container sets a request without matching it to an equal limit, and BestEffort when no container sets any requests or limits at all. Under node memory pressure the kubelet evicts BestEffort Pods first, then Burstable ones exceeding their requests, and Guaranteed Pods last — QoS class is a direct exam translation of "how likely is this Pod to survive when the node runs low."

⚠ Watch out — omitting requests doesn't make scheduling easier

A Pod with no resource requests at all is not more flexible — it's BestEffort, first in line for eviction, and it can land on a node that's already fully committed by requests, then get starved the moment real usage climbs. It also can't be autoscaled by CPU utilization, since the HPA's percentage calculation needs a request to divide by. Setting even a conservative request is close to always the right call, on the exam and off it.

See this course's own Scheduling & Resource Management deep dive for node-level scheduler internals beyond what the CKA tests, and RBAC & Admission Control for how a ResourceQuota or LimitRange can enforce requests/limits at the namespace level before a Pod is ever admitted.

Where this domain's traps live

☺ Like you're 10: Most wrong answers here come from mixing up two things that sound alike but behave differently — know which is which, cold.

🎬 At the Pod Squad
🦫

Benny: Rolling out checkout-api 1.25.1 now — maxSurge 1, maxUnavailable 0, so we never drop under three replicas mid-rollout.

🦥

Sol: Before you kick it off — did you set requests and limits on the new Pod spec? I ran the numbers against last week's noon traffic, and 250 millicores per Pod is honest; leaving it blank isn't.

👺

Gizmo: Or, hot tip, just skip requests entirely. No requests, no scheduling arguments, the Pod goes wherever there's room. Simpler! 🤑

🐢

Timmy: Simpler until every Pod on that node is BestEffort and the kubelet evicts yours first the moment memory gets tight. Set requests — even conservative ones buy you a QoS class above the bottom.

🦫

Benny: Requests are in. Rollout's climbing — two v1 Pods, one v2, holding at three total exactly like Sol said it would.

🦊

Foxy: And if the new image crashes on start?

🦫

Benny: Then the readiness probe never passes, the rollout stalls itself instead of replacing healthy Pods with broken ones, and kubectl rollout undo puts the old ReplicaSet straight back. That's why we never delete it early.

🦫 Benny's workshop · 20 min

On a scratch cluster (kind or minikube is enough), create a Deployment with maxSurge: 1 and maxUnavailable: 0, then trigger a rollout to an image tag that doesn't exist in any reachable registry. Watch kubectl rollout status hang and kubectl get pods show a mix of old Pods still serving and one new Pod stuck in ImagePullBackOff. Confirm the old ReplicaSet never scaled down while the new one couldn't become ready, then run kubectl rollout undo and watch it converge back to the last-known-good state. That's the entire safety story of this domain, watched happening in real time instead of read about.

🐢 Timmy's checkpoint

1. What's the practical difference between maxSurge:1, maxUnavailable:0 and maxSurge:0, maxUnavailable:1 on a rolling update? 2. A failing readiness probe and a failing liveness probe both signal a problem — what does each one actually do to the Pod? 3. A Pod has a toleration for a node's taint but its required nodeAffinity doesn't match that node's labels — does it schedule there? 4. Why doesn't updating a ConfigMap automatically restart the Pods that reference it as environment variables? 5. What determines whether a Pod's QoS class is Guaranteed, Burstable, or BestEffort? 6. What does the HPA need present on the target container's spec before it can scale on CPU utilization, and what add-on does it depend on cluster-wide?

Check your answers
  1. maxSurge:1, maxUnavailable:0 never drops below the desired replica count during the rollout but briefly runs one Pod above it. maxSurge:0, maxUnavailable:1 never exceeds the desired count but briefly runs one below it. Pick the first when you can't tolerate reduced capacity; the second when you can't exceed a fixed resource ceiling.
  2. A failing readiness probe removes the Pod from the Service's Endpoints — no traffic reaches it, but the container keeps running and can self-recover. A failing liveness probe gets the container killed and restarted by the kubelet.
  3. No. Taints and node affinity are independent gates the Pod must clear separately — tolerating the taint only removes that one obstacle; the node's labels still have to satisfy the required affinity rule for the Pod to be eligible there.
  4. Environment variables are resolved once, at Pod start, from whatever the ConfigMap contained at that moment — the running container has no live connection back to the ConfigMap object to notice a later change.
  5. Whether requests equal limits, for every container in the Pod, across both CPU and memory. Guaranteed: all containers' requests equal their limits. Burstable: at least one container sets a request without an equal limit. BestEffort: no container sets any requests or limits at all.
  6. The container needs a CPU request set, since utilization is calculated as a percentage of that request — with no request there's no denominator. Cluster-wide, the HPA depends on the metrics-server add-on being installed and reachable; without it, kubectl get hpa shows <unknown> for the current metric.

Domain 2 is what runs on top of the control plane Domain 1 stands up. Go there next with Cluster Architecture, Installation & Configuration, or continue forward to how those workloads actually get reached from outside the cluster in Services & Networking.