CertGrid CertGrid
Hands-on Lab·Certified Kubernetes Application Developer

Choosing a Workload Object

"Choose and use the right workload resource" is a CKAD objective, and the differences are easier to see than to remember. All five run side by side here - Deployment, StatefulSet, DaemonSet, Job and CronJob - and the comparison is made from what they actually did: how they named their Pods, how many they made, what storage they claimed, and what happened when one was deleted.

Application Design and Build Guide 15 of 44 Beginner

Written against the versions above. The DaemonSet's DESIRED count is computed from the nodes that will accept the Pod, so on this four-node cluster it reads 3 - the control plane's taint excludes it. That is a property of the cluster, not of the DaemonSet, and it is the number people misread as a fault.

Four nodes, one tainted. The node count is what makes the DaemonSet's arithmetic visible and gives the StatefulSet somewhere to spread.
Server NameIP AddressOSRolesCPURAMHDD
CKA1001192.168.0.175Ubuntu 26.04 LTSControl Plane Node2 Core4 GB50 GB
CKA1001-NODE01192.168.0.176Ubuntu 26.04 LTSWorker Node2 Core4 GB50 GB
CKA1001-NODE02192.168.0.177Ubuntu 26.04 LTSWorker Node2 Core4 GB50 GB
CKA1001-NODE03192.168.0.178Ubuntu 26.04 LTSWorker Node2 Core4 GB50 GB

Before you start

  1. Five objects, one namespace

    One of each, all running at once:

    • Deployment web, 2 replicas
    • StatefulSet db, 2 replicas with a claim each
    • DaemonSet agent
    • Job once
    • CronJob nightly, at 03:00

    The listing shows them in their own vocabularies - a Deployment reports READY 2/2, a Job reports COMPLETIONS 1/1, a CronJob reports a schedule and no Pods at all until it fires. Those columns are the first hint that these are not variations on one idea.

    bash Example session
    kubectl create namespace ckad-wlnamespace/ckad-wl createdkubectl -n ckad-wl rollout status deploy/web --timeout=240sWaiting for deployment "web" rollout to finish: 0 of 2 updated replicas are available...Waiting for deployment "web" rollout to finish: 1 of 2 updated replicas are available...deployment "web" successfully rolled outkubectl -n ckad-wl rollout status statefulset/db --timeout=240sWaiting for 2 pods to be ready...Waiting for 1 pods to be ready...Waiting for 1 pods to be ready...partitioned roll out complete: 2 new pods have been updated...sleep 20; kubectl -n ckad-wl get deploy,statefulset,daemonset,job,cronjobNAME                  READY   UP-TO-DATE   AVAILABLE   AGEdeployment.apps/web   2/2     2            2           29s NAME                  READY   AGEstatefulset.apps/db   2/2     28s NAME                   DESIRED   CURRENT   READY   UP-TO-DATE   AVAILABLE   NODE SELECTOR   AGEdaemonset.apps/agent   3         3         3       3            3           <none>          28s NAME             STATUS     COMPLETIONS   DURATION   AGEjob.batch/once   Complete   1/1           3s         28s NAME                    SCHEDULE    TIMEZONE   SUSPEND   ACTIVE   LAST SCHEDULE   AGEcronjob.batch/nightly   0 3 * * *   <none>     False     0        <none>          28s

    Expected resultAll five present, each reporting different columns.

    Success conditionYou have every workload kind in front of you at once.

  2. How they name their Pods

    POD                    OWNER        NODE
    agent-...              DaemonSet    cka1001-node01
    agent-...              DaemonSet    cka1001-node02
    agent-...              DaemonSet    cka1001-node03
    db-0                   StatefulSet  ...
    db-1                   StatefulSet  ...
    once-...               Job          ...
    web-...-...            ReplicaSet   ...

    Three naming schemes, and each says something:

    • db-0, db-1 - an ordinal. Stable, predictable, addressable.
    • web-- - a template hash and a random suffix. Interchangeable by design.
    • agent-, one per node - placement is the identity.

    Note the Deployment's Pods are owned by a ReplicaSet, not by the Deployment. That extra layer is what makes rollbacks cheap, and it is why kubectl get rs is part of debugging a rollout.

    bash Example session
    kubectl -n ckad-wl get pods -o 'custom-columns=POD:.metadata.name,OWNER:.metadata.ownerReferences[0].kind,NODE:.spec.nodeName' --sort-by=.metadata.namePOD                    OWNER         NODEagent-6ckgf            DaemonSet     cka1001-node01agent-h2c7b            DaemonSet     cka1001-node02agent-rfhqf            DaemonSet     cka1001-node03db-0                   StatefulSet   cka1001-node02db-1                   StatefulSet   cka1001-node03once-xstmc             Job           cka1001-node03web-5d6b5c7df9-4pmxx   ReplicaSet    cka1001-node02web-5d6b5c7df9-r7q9f   ReplicaSet    cka1001-node01

    Expected resultThree distinct naming patterns and two owner kinds.

    Success conditionYou can tell which controller made a Pod from its name alone.

  3. What each one guarantees

    Storage. Only the StatefulSet claimed any:

    CLAIM       STATUS
    data-db-0   Bound
    data-db-1   Bound

    One PVC per replica, from volumeClaimTemplates. A Deployment cannot do this - every replica would share one claim, and ReadWriteOnce means only one node could mount it.

    Node coverage. The DaemonSet computed its own replica count:

    DESIRED   READY
    3         3

    against four nodes, because the control plane's taint excludes it. You never set that number.

    Completion. The Job finished and stopped:

    COMPLETIONS   CONDITION
    1             Complete

    and the CronJob has done nothing at all, correctly:

    SCHEDULE     LASTRUN   ACTIVE
    0 3 * * *    <none>    <none>

    So the decision, in the order worth asking it:

    | Question | Answer | |---|---| | Does it finish? | Job, or CronJob on a schedule | | One per node? | DaemonSet | | Stable name or its own disk? | StatefulSet | | None of the above | Deployment |

    Most of the time the answer is Deployment. The point of the other four is knowing when it is not.

    bash Example session
    kubectl -n ckad-wl get pvc -o 'custom-columns=CLAIM:.metadata.name,STATUS:.status.phase,OWNER:.metadata.labels.app'CLAIM       STATUS   OWNERdata-db-0   Bound    dbdata-db-1   Bound    dbkubectl -n ckad-wl get daemonset agent -o 'custom-columns=DESIRED:.status.desiredNumberScheduled,READY:.status.numberReady'DESIRED   READY3         3kubectl get nodes --no-headers | wc -l4kubectl -n ckad-wl get job once -o 'custom-columns=COMPLETIONS:.status.succeeded,CONDITION:.status.conditions[*].type'COMPLETIONS   CONDITION1             SuccessCriteriaMet,Completekubectl -n ckad-wl get cronjob nightly -o 'custom-columns=SCHEDULE:.spec.schedule,LASTRUN:.status.lastScheduleTime,ACTIVE:.status.active'SCHEDULE    LASTRUN   ACTIVE0 3 * * *   <none>    <none>

    Expected resultClaims only for the StatefulSet, DESIRED 3 on four nodes, a completed Job, an idle CronJob.

    Success conditionYou can pick a workload kind from what the application needs.

  4. The difference that decides it

    Delete a Pod from the Deployment and one from the StatefulSet, and look at what came back:

    POD                    OWNER
    agent-...              DaemonSet
    db-0                   StatefulSet
    db-1                   StatefulSet
    web-<hash>-<new>       ReplicaSet

    db-1 is db-1 again - same name, and it reattached the same data-db-1 claim. The Deployment's replacement has a new random suffix and carries nothing over.

    That single behaviour is the whole choice. If any part of your application cares which instance it is - a database replica, a queue consumer with a partition, anything that owns data on disk - it needs a StatefulSet. If every instance is interchangeable, a Deployment is simpler, rolls faster and scales more freely.

    CKAD leans heavily on Deployments, and being able to say why the other four exist is what the objective is asking for.

    bash Example session
    kubectl -n ckad-wl delete pod web-$(kubectl -n ckad-wl get pods -l app=web -o jsonpath='{.items[0].metadata.name}' | cut -d- -f2-) --wait=true 2>/dev/null || kubectl -n ckad-wl delete pod -l app=web --field-selector=status.phase=Running --wait=truepod "web-5d6b5c7df9-4pmxx" deleted from ckad-wl namespacekubectl -n ckad-wl delete pod db-1 --wait=truepod "db-1" deleted from ckad-wl namespacesleep 25; kubectl -n ckad-wl get pods -o 'custom-columns=POD:.metadata.name,OWNER:.metadata.ownerReferences[0].kind' --sort-by=.metadata.namePOD                    OWNERagent-6ckgf            DaemonSetagent-h2c7b            DaemonSetagent-rfhqf            DaemonSetdb-0                   StatefulSetdb-1                   StatefulSetonce-xstmc             Jobweb-5d6b5c7df9-ht4qj   ReplicaSetweb-5d6b5c7df9-r7q9f   ReplicaSetkubectl delete namespace ckad-wl --ignore-not-found --wait=true

    Expected resultdb-1 back under its own name; the Deployment Pod replaced by a stranger.

    Success conditionYou can justify a StatefulSet instead of reaching for one by habit.

Troubleshooting

Official sources