DaemonSets, Jobs, CronJobs and StatefulSets

The controllers beyond Deployments: DaemonSets for one Pod per node, Jobs and CronJobs for work that finishes, and StatefulSets for Pods that need a stable identity.

Workloads · 15%40 min3 tasks

After this lesson

  • 01You can write a DaemonSet from a Deployment skeleton and decide whether it should tolerate the control-plane taint.
  • 02You can size a Job with completions, parallelism, backoffLimit and activeDeadlineSeconds and confirm it from .status.succeeded.
  • 03You can schedule a CronJob with the right concurrencyPolicy and history limits, and trigger it by hand with kubectl create job --from.
  • 04You can explain what a StatefulSet guarantees that a Deployment does not.

Picking the right controller is part of Understand the primitives used to create robust, self-healing, application deployments. Each one re-creates Pods on its own terms: per node, until a count of successes, on a clock, or with a fixed name. kubectl can generate Jobs and CronJobs but not DaemonSets or StatefulSets, so this lesson also shows the fastest way to write those by hand.

DaemonSets: one Pod per node

A DaemonSet runs one copy of its Pod on every node that passes its scheduling rules, and adds or removes Pods as nodes join or leave. That is how CNI agents, kube-proxy, log collectors and node exporters get everywhere. The default scheduler places them, so taints apply. The controller adds tolerations for not-ready, unreachable, disk-pressure, memory-pressure, pid-pressure and unschedulable automatically, which is why a DaemonSet still places its Pod on a cordoned node and why kubectl drain needs --ignore-daemonsets. It does not add one for node-role.kubernetes.io/control-plane: a DaemonSet that must run on every node, control plane included, needs that toleration written in.

There is no kubectl create daemonset; convert a Deploymentbash
kubectl -n wl-batch create deployment node-agent --image=registry.k8s.io/pause:3.10 --dry-run=client -o yaml \
  | sed -e 's/^kind: Deployment/kind: DaemonSet/' -e '/^  replicas:/d' -e '/^  strategy:/d' -e '/^status:/d' > ds.yaml
kubectl apply -f ds.yaml
kubectl -n wl-batch get ds node-agent        # DESIRED CURRENT READY per eligible node
kubectl -n wl-batch get pods -l app=node-agent -o wide
Every node including the control plane, or only some nodesyaml
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: log-agent
  namespace: wl-batch
spec:
  selector:
    matchLabels:
      app: log-agent
  updateStrategy:
    type: RollingUpdate       # default, one node at a time; OnDelete waits for you to delete Pods
  template:
    metadata:
      labels:
        app: log-agent
    spec:
      tolerations:            # remove this block to stay off the control plane
        - key: node-role.kubernetes.io/control-plane
          operator: Exists
          effect: NoSchedule
      # nodeSelector:         # or restrict to labeled nodes
      #   pool: logging
      containers:
        - name: agent
          image: busybox:1.36
          command: ["sh", "-c", "while true; do sleep 3600; done"]

Jobs: run until enough Pods succeed

The fields that shape a Jobyaml
apiVersion: batch/v1
kind: Job
metadata:
  name: resize-images
  namespace: wl-batch
spec:
  completions: 6                 # successful Pods needed (default 1)
  parallelism: 2                 # Pods running at once (default 1)
  backoffLimit: 3                # failed retries before the Job fails (default 6)
  activeDeadlineSeconds: 300     # hard wall-clock limit, beats backoffLimit
  ttlSecondsAfterFinished: 600   # delete the Job and its Pods 10 min after it ends
  template:
    spec:
      restartPolicy: Never       # Never or OnFailure; Always is rejected
      containers:
        - name: worker
          image: busybox:1.36
          command: ["sh", "-c", "echo resizing; sleep 3"]

restartPolicy: Never means a failed container leaves its Pod behind (handy for kubectl logs) and the Job creates a fresh Pod; OnFailure restarts the container inside the same Pod. Either way retries count toward backoffLimit, with a growing delay between them. A Job ends with the condition Complete, or Failed with reason BackoffLimitExceeded or DeadlineExceeded. Its Pod template is immutable, so changing a Job means deleting and re-creating it. completionMode: Indexed gives each Pod a JOB_COMPLETION_INDEX for sharded work.

Create, wait, inspectbash
kubectl -n wl-batch create job once --image=busybox:1.36 -- sh -c 'echo done'   # writes restartPolicy: Never
kubectl -n wl-batch create job once --image=busybox:1.36 --dry-run=client -o yaml -- sh -c 'echo done' > job.yaml   # then add completions etc.
kubectl -n wl-batch wait --for=condition=complete job/once --timeout=120s
kubectl -n wl-batch get job once -o jsonpath='{.status.succeeded}/{.spec.completions}'; echo
kubectl -n wl-batch logs job/once
kubectl -n wl-batch get pods -l job-name=once

CronJobs: Jobs on a schedule

A CronJob with its guardrailsyaml
apiVersion: batch/v1
kind: CronJob
metadata:
  name: nightly-report
  namespace: wl-batch
spec:
  schedule: "30 2 * * *"           # minute hour day-of-month month day-of-week
  timeZone: "Etc/UTC"              # optional; otherwise kube-controller-manager's local time
  concurrencyPolicy: Forbid        # Allow (default) | Forbid | Replace
  startingDeadlineSeconds: 120     # skip a run that could not start within 2 minutes
  successfulJobsHistoryLimit: 3    # default 3
  failedJobsHistoryLimit: 1        # default 1
  suspend: false
  jobTemplate:
    spec:
      backoffLimit: 2
      template:
        spec:
          restartPolicy: OnFailure
          containers:
            - name: report
              image: busybox:1.36
              command: ["sh", "-c", "date; echo report done"]
concurrencyPolicyWhen the next run is due and the last one is still running
AllowStart another Job; runs may overlap
ForbidSkip this run; the running Job continues
ReplaceDelete the running Job and start the new one

*/5 * * * * is every five minutes, 0 * * * * every hour on the hour, 0 9 * * 1-5 weekdays at 09:00. If the controller finds more than 100 missed schedules for a CronJob (after a long outage, say), it does not start the Job and logs an error; startingDeadlineSeconds limits how far back it counts. To test one without waiting, run kubectl create job NAME --from=cronjob/CRON: it copies the job template and records the CronJob as owner, so the history limits apply to it as well.

StatefulSets in brief

A StatefulSet gives each Pod a stable ordinal name (db-0, db-1), a stable DNS name through the headless Service named in spec.serviceName (db-0.db.NAMESPACE.svc.cluster.local), and, with volumeClaimTemplates, its own PVC that follows it across rescheduling. By default Pods are created in order, each Ready before the next, removed in reverse order, and updated from the highest ordinal down; podManagementPolicy: Parallel drops the ordering and rollingUpdate.partition stages an update. There is no kubectl create statefulset; copy the example from the docs. Storage details are in the Storage module.

In the docs · the tab you get on exam day

Taskwl-batch-1
Workloads4%~5 min

context · kubectl config use-context kind-cka

Run an agent on every worker node

In namespace wl-batch create a DaemonSet node-agent that runs exactly one Pod (image registry.k8s.io/pause:3.10, Pod label app=node-agent) on each worker node and none on the control plane. Each container requests 10m CPU and 16Mi memory.
  1. 01

    Set up the scenario

    curl -fsSL https://codewithmuh.com/labs/cka/t/wl-batch-1/setup | bash
  2. 02

    Solve it in your terminal

    Work the way the exam makes you: kubectl, the docs, nothing else. Put any answer files under /tmp/cka/.

  3. 03

    Check your work

    curl -fsSL https://codewithmuh.com/labs/cka/t/wl-batch-1/verify | bash
    Counts toward your readiness score.
  4. 04

    Clean up

    curl -fsSL https://codewithmuh.com/labs/cka/t/wl-batch-1/cleanup | bash
Taskwl-batch-2
Workloads4%~5 min

context · kubectl config use-context kind-cka

A Job that must succeed three times, one at a time

In namespace wl-batch create a Job process-items using image busybox:1.36 that runs sh -c 'echo processing; sleep 2'. The Job must finish after 3 successful Pods, run only one Pod at a time, retry a failure at most 2 times, never restart a failed container in place, and be stopped if it runs longer than 120 seconds. Wait until it has completed.
  1. 01

    Set up the scenario

    curl -fsSL https://codewithmuh.com/labs/cka/t/wl-batch-2/setup | bash
  2. 02

    Solve it in your terminal

    Work the way the exam makes you: kubectl, the docs, nothing else. Put any answer files under /tmp/cka/.

  3. 03

    Check your work

    curl -fsSL https://codewithmuh.com/labs/cka/t/wl-batch-2/verify | bash
    Counts toward your readiness score.
  4. 04

    Clean up

    curl -fsSL https://codewithmuh.com/labs/cka/t/wl-batch-2/cleanup | bash
Taskwl-batch-3
Workloads5%~6 min

context · kubectl config use-context kind-cka

Schedule a CronJob and run it once by hand

In namespace wl-batch create a CronJob heartbeat that runs every 5 minutes with image busybox:1.36 and command sh -c 'date; echo alive'. A new run must never start while the previous one is still running. Keep the last 2 successful Jobs and the last 1 failed Job. Then, without waiting for the schedule, start a Job named heartbeat-manual from this CronJob and make sure it succeeds.
  1. 01

    Set up the scenario

    curl -fsSL https://codewithmuh.com/labs/cka/t/wl-batch-3/setup | bash
  2. 02

    Solve it in your terminal

    Work the way the exam makes you: kubectl, the docs, nothing else. Put any answer files under /tmp/cka/.

  3. 03

    Check your work

    curl -fsSL https://codewithmuh.com/labs/cka/t/wl-batch-3/verify | bash
    Counts toward your readiness score.
  4. 04

    Clean up

    curl -fsSL https://codewithmuh.com/labs/cka/t/wl-batch-3/cleanup | bash