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.
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.
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 wideapiVersion: 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
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.
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=onceCronJobs: Jobs on a schedule
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"]*/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
- DaemonSet ↗search: “daemonset”
- Jobs ↗search: “job completions parallelism”
- CronJob ↗search: “cronjob concurrency policy”
- StatefulSets ↗search: “statefulset”
context · kubectl config use-context kind-cka
Run an agent on every worker node
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.- 01
Set up the scenario
curl -fsSL https://codewithmuh.com/labs/cka/t/wl-batch-1/setup | bash - 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/. - 03
Check your work
curl -fsSL https://codewithmuh.com/labs/cka/t/wl-batch-1/verify | bashCounts toward your readiness score. - 04
Clean up
curl -fsSL https://codewithmuh.com/labs/cka/t/wl-batch-1/cleanup | bash
context · kubectl config use-context kind-cka
A Job that must succeed three times, one at a time
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.- 01
Set up the scenario
curl -fsSL https://codewithmuh.com/labs/cka/t/wl-batch-2/setup | bash - 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/. - 03
Check your work
curl -fsSL https://codewithmuh.com/labs/cka/t/wl-batch-2/verify | bashCounts toward your readiness score. - 04
Clean up
curl -fsSL https://codewithmuh.com/labs/cka/t/wl-batch-2/cleanup | bash
context · kubectl config use-context kind-cka
Schedule a CronJob and run it once by hand
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.- 01
Set up the scenario
curl -fsSL https://codewithmuh.com/labs/cka/t/wl-batch-3/setup | bash - 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/. - 03
Check your work
curl -fsSL https://codewithmuh.com/labs/cka/t/wl-batch-3/verify | bashCounts toward your readiness score. - 04
Clean up
curl -fsSL https://codewithmuh.com/labs/cka/t/wl-batch-3/cleanup | bash
Finished reading?
0 of 3 tasks passed. Readiness counts tasks, so run them before you move on.