StatefulSet
For workloads that need a stable identity and storage — a clustered database, a message queue. Each replica gets a predictable name (db-0, db-1, ...) and its own PersistentVolumeClaim that follows it across rescheduling, instead of the interchangeable Pods a Deployment manages.
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: db
spec:
serviceName: db
replicas: 3
selector:
matchLabels:
app: db
template:
metadata:
labels:
app: db
spec:
containers:
- name: postgres
image: postgres:16-alpine
volumeMounts:
- name: data
mountPath: /var/lib/postgresql/data
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 20GiPods are created and scaled in order (db-0 before db-1), which matters for clustered databases that need to join an existing member before they can start.
DaemonSet
Runs exactly one copy of a Pod on every node (or every node matching a selector), automatically extending to new nodes as they join. The standard fit for log shippers, metrics agents, and CNI plugins — anything that needs to be everywhere, not scaled independently.
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: log-agent
spec:
selector:
matchLabels:
app: log-agent
template:
metadata:
labels:
app: log-agent
spec:
containers:
- name: fluent-bit
image: fluent/fluent-bit:3.1
volumeMounts:
- name: varlog
mountPath: /var/log
volumes:
- name: varlog
hostPath:
path: /var/logJob
Runs a Pod to completion — once, or a fixed number of times — and retries it on failure. For one-off or batch work, not long-running services.
apiVersion: batch/v1
kind: Job
metadata:
name: db-migrate
spec:
backoffLimit: 3
template:
spec:
restartPolicy: Never
containers:
- name: migrate
image: docktionary/app:1.4.0
command: ["npm", "run", "migrate"]For work that splits across many Pods — processing a batch of files, say — completions sets how many successful Pod runs the Job needs in total, and parallelism caps how many run at once.
spec:
completions: 20
parallelism: 5 # 5 Pods running at a time until 20 have succeeded
backoffLimit: 3CronJob
A Job template run on a cron schedule — the cluster-native replacement for a crontab entry, except the thing it runs is a container.
apiVersion: batch/v1
kind: CronJob
metadata:
name: nightly-report
spec:
schedule: "0 2 * * *"
concurrencyPolicy: Forbid
jobTemplate:
spec:
template:
spec:
restartPolicy: OnFailure
containers:
- name: report
image: docktionary/app:1.4.0
command: ["npm", "run", "nightly-report"]concurrencyPolicy decides what happens if a scheduled run is still going when the next one is due: Allow (the default) runs them side by side, Forbid skips the new run entirely, and Replace cancels the still-running one and starts fresh. Forbid is usually the right call for anything that shouldn’t run twice at once, like a report that writes to the same output location.
Checking progress
kubectl get statefulsets,daemonsets,jobs,cronjobs
kubectl logs job/db-migrate
kubectl get pods -l app=db -w # watch StatefulSet pods come up in order