DaemonSets, Jobs and CronJobs

Three controllers beyond Deployments: DaemonSets run one Pod on each node, Jobs run work that finishes, and CronJobs run Jobs on a schedule.

40 min 3 hands-on tasksWorkloads · 15% of the exam

Key ideas in this lesson · tap one for a simple explanation

What you will be able to do

  • Write a DaemonSet from a Deployment skeleton, and decide whether it should tolerate the control-plane taint.
  • Size a Job with completions, parallelism, backoffLimit and activeDeadlineSeconds, and confirm it from .status.succeeded.
  • Schedule a CronJob with the right concurrencyPolicy and history limits, and start it by hand with kubectl create job --from.

A keeps a set number of running forever. That fits a web app, but not every kind of work. Kubernetes has a controller for each shape:

Three shapes of work

DaemonSet

one Pod on every node

Job

runs until the work is done

CronJob

starts a Job on a schedule

ControllerWhat it runsExample
DeploymentA set number of Pods, foreverYour Django web app
One Pod on each A log collector that reads every node's logs
Pods until enough of them succeed, then stopsA one-off database migration
A new Job each time the schedule comes roundA report every night at 02:30

can generate Jobs and CronJobs, but not DaemonSets. So this lesson also shows the fastest way to write a DaemonSet by hand.

DaemonSets: one Pod per node

A DaemonSet runs one copy of its Pod on every node that its rules allow. When a node joins the cluster, the DaemonSet adds a Pod there. When a node leaves, its Pod goes too. That is how network agents, and log collectors end up on every node.

The normal places these Pods, so still apply:

  • The has a NoSchedule taint. A DaemonSet adds a few by itself, but not one for this taint.
  • So by default, a DaemonSet runs only on the worker nodes: 2 Pods on this lab.
  • To cover every node, control plane included, write the control-plane toleration into the Pod template.

There is no kubectl create daemonset. Instead, generate a Deployment and turn it into a DaemonSet:

There is no kubectl create daemonset; convert a Deployment
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

Here is a full DaemonSet. Keep the tolerations block to include the control plane, or remove it to stay on the workers:

Every node including the control plane, or only some nodes
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

A Job runs Pods until a set number of them succeed, then stops. Example: a database migration for your Django app. It must run, finish, and not start again.

The fields that shape a Job
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"]

A Job's Pods must use restartPolicy: Never or OnFailure. The two differ in what happens after a failure:

restartPolicyAfter a failure
NeverThe failed Pod stays, which is handy for kubectl logs. The Job creates a new Pod.
OnFailureThe restarts inside the same Pod.
  • Either way, each retry counts toward backoffLimit, and the wait between retries grows.
  • A Job ends with the condition Complete, or with Failed and a reason: BackoffLimitExceeded or DeadlineExceeded.
  • A Job's Pod template cannot be changed. To change a Job, delete it and create it again.

Create a Job, wait for it, then check it:

Create, wait, inspect
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 creates a new Job each time its schedule comes round. Example: a report that your app emails every night at 02:30.

What a CronJob creates

CronJob

holds the schedule

30 2 * * *

Job

one per run

Pod

does the work, then exits

Each level has its own spec, so the Pod template sits deep inside the CronJob: jobTemplate.spec.template.spec.

A CronJob with its guardrails
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
  concurrencyPolicy: Forbid        # Allow (default) | Forbid | Replace
  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"]

The schedule uses the five classic cron fields: minute, hour, day of month, month and day of week. Some common ones:

ScheduleMeans
*/5 * * * *Every 5 minutes
0 * * * *Every hour, on the hour
0 9 * * 1-509:00 on weekdays
30 2 * * *02:30 every day

What if a run is due while the last one is still going? concurrencyPolicy decides:

concurrencyPolicyThe next run is due, but the last one is still running
Allow (default)Start another Job. Runs may overlap
ForbidSkip this run. The running Job carries on
ReplaceDelete the running Job and start the new one

To test a CronJob without waiting, run kubectl create job NAME --from=cronjob/CRON. It copies the job template and records the CronJob as the owner, so the history limits count it too.

Apps that need a fixed name and their own disk, such as a database, use a , taught in StatefulSets and volumeClaimTemplates.

In the official docs· open on exam day too

  • DaemonSetsearch: daemonset
  • Jobssearch: job completions parallelism
  • CronJobsearch: cronjob concurrency policy
Hands-on task 1 of 3wl-batch-1
~5 min4% weightWorkloads

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.

Cluster:kubectl config use-context kind-cka

  1. 1

    Set up the scenario

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

    Solve it in your terminal

    Use kubectl and the Kubernetes docs, like on exam day. Save any answer files under /tmp/cka/.

  3. 3

    Check your work

    curl -fsSL https://codewithmuh.com/labs/cka/t/wl-batch-1/verify | bash
    Got FAIL? Read which check failed, fix it, run it again.
  4. 4

    Clean up (optional)

    curl -fsSL https://codewithmuh.com/labs/cka/t/wl-batch-1/cleanup | bash
Hands-on task 2 of 3wl-batch-2
~5 min4% weightWorkloads

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.

Cluster:kubectl config use-context kind-cka

  1. 1

    Set up the scenario

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

    Solve it in your terminal

    Use kubectl and the Kubernetes docs, like on exam day. Save any answer files under /tmp/cka/.

  3. 3

    Check your work

    curl -fsSL https://codewithmuh.com/labs/cka/t/wl-batch-2/verify | bash
    Got FAIL? Read which check failed, fix it, run it again.
  4. 4

    Clean up (optional)

    curl -fsSL https://codewithmuh.com/labs/cka/t/wl-batch-2/cleanup | bash
Hands-on task 3 of 3wl-batch-3
~6 min5% weightWorkloads

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.

Cluster:kubectl config use-context kind-cka

  1. 1

    Set up the scenario

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

    Solve it in your terminal

    Use kubectl and the Kubernetes docs, like on exam day. Save any answer files under /tmp/cka/.

  3. 3

    Check your work

    curl -fsSL https://codewithmuh.com/labs/cka/t/wl-batch-3/verify | bash
    Got FAIL? Read which check failed, fix it, run it again.
  4. 4

    Clean up (optional)

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

Finished this lesson?

0 of 3 tasks passed. Your readiness counts tasks, so run them before you move on.