GCP ACE: Beyond Deployment — DaemonSet, StatefulSet, Job, and CronJob
Every GKE article so far has used a Deployment — the right default for stateless, scalable apps. But the exam tests three other workload controllers, each solving a problem a Deployment can’t.
DaemonSet — exactly one Pod per node
A Deployment’s Pods land wherever the scheduler decides, and you control the count with replicas. A DaemonSet is different: it guarantees one Pod on every eligible node, with no replica count to set. When a node joins the cluster, the DaemonSet’s controller schedules a Pod on it automatically; when a node leaves, that Pod is garbage-collected with it.
| DaemonSet | Deployment | |
|---|---|---|
| Placement | One Pod per eligible node | N replicas, wherever the scheduler picks |
| Replica count | None — derived from node count | spec.replicas |
| Scales with | The cluster’s node count | A number you (or an HPA) set |
| Typical use | Node-local agents | Stateless, horizontally scalable apps |
Typical use cases: a logging agent (Fluentd, Fluent Bit) that reads each node’s log files, a monitoring agent (Prometheus node-exporter) that reports per-node metrics, or a CSI storage plugin that needs to run on every node to mount volumes.
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: fluentd-elasticsearch
namespace: kube-system
spec:
selector:
matchLabels:
name: fluentd-elasticsearch
template:
metadata:
labels:
name: fluentd-elasticsearch
spec:
containers:
- name: fluentd-elasticsearch
image: quay.io/fluentd_elasticsearch/fluentd:v2.5.2
resources:
limits:
memory: 200Mi
requests:
cpu: 100m
memory: 200Mi
terminationGracePeriodSeconds: 30
spec.selector.matchLabels must match the labels in spec.template.metadata.labels — the API server rejects the manifest otherwise, same rule as a Deployment.
StatefulSet — stable identity for stateful apps
A Deployment’s Pods are interchangeable: if web-7f8c9 dies, web-2k9x1 takes its place and nothing cares. That’s wrong for a database or anything that needs to keep its name, its network address, and its own disk across restarts. That’s what a StatefulSet guarantees.
Naming and ordering: Pods are named <statefulset-name>-<ordinal>, starting at 0 — a StatefulSet called web with 3 replicas creates web-0, web-1, web-2. By default (podManagementPolicy: OrderedReady), they’re created in order, each waiting until the previous one is Running and Ready; they’re removed in reverse order on scale-down.
Stable network identity: a StatefulSet must reference a headless Service (clusterIP: None) via spec.serviceName. Each Pod then gets a predictable DNS name: <pod-name>.<service-name>.<namespace>.svc.cluster.local — for example web-0.nginx.default.svc.cluster.local. That name stays the same even after the Pod is rescheduled to a different node.
Stable storage: volumeClaimTemplates creates one PersistentVolumeClaim per Pod, named <template-name>-<pod-name> (e.g. www-web-0). When a Pod is rescheduled, it reattaches to that same PVC — its data follows it. By default, PVCs are not deleted when the StatefulSet is scaled down or removed; you have to clean them up yourself (or use persistentVolumeClaimRetentionPolicy on newer clusters).
apiVersion: v1
kind: Service
metadata:
name: nginx
labels:
app: nginx
spec:
clusterIP: None # headless: enables per-Pod DNS records
selector:
app: nginx
ports:
- name: web
port: 80
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: web
spec:
serviceName: "nginx" # must match the headless Service above
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: registry.k8s.io/nginx-slim:0.24
volumeMounts:
- name: www
mountPath: /usr/share/nginx/html
volumeClaimTemplates: # creates www-web-0, www-web-1, www-web-2
- metadata:
name: www
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 1Gi
Job — run to completion
A Deployment keeps its Pods running forever; a Job runs Pods until a target number succeed, then stops.
| Field | Default | Meaning |
|---|---|---|
completions | 1 | Successful Pod completions required before the Job is done |
parallelism | 1 | Max Pods running at the same time |
backoffLimit | 6 | Pod failures tolerated before the Job is marked failed (exponential backoff, capped at 6 minutes between retries) |
The Pod template’s restartPolicy must be Never or OnFailure — Always isn’t valid inside a Job.
apiVersion: batch/v1
kind: Job
metadata:
name: pi
spec:
completions: 5
parallelism: 2
backoffLimit: 4
template:
spec:
restartPolicy: Never
containers:
- name: pi
image: perl:5.34.0
command: ["perl", "-Mbignum=bpi", "-wle", "print bpi(2000)"]
This runs until 5 Pods succeed, at most 2 at a time, tolerating up to 4 failures.
CronJob — Jobs on a schedule
A CronJob creates Jobs on a repeating schedule, same syntax as Unix cron:
┌───────── minute (0–59)
│ ┌─────── hour (0–23)
│ │ ┌───── day of month (1–31)
│ │ │ ┌─── month (1–12)
│ │ │ │ ┌─ day of week (0–6, Sunday = 0)
* * * * *
concurrencyPolicy decides what happens if a new run is due while the previous Job is still going:
| Value | Behavior |
|---|---|
Allow (default) | Runs the new Job alongside the still-running one |
Forbid | Skips the new run entirely |
Replace | Cancels the running Job, starts the new one |
apiVersion: batch/v1
kind: CronJob
metadata:
name: hello
spec:
schedule: "0 2 * * *" # daily at 02:00
concurrencyPolicy: Forbid
jobTemplate:
spec:
template:
spec:
restartPolicy: OnFailure
containers:
- name: hello
image: busybox:1.28
command: ["/bin/sh", "-c", "date; echo Hello"]
Exam Takeaways
- DaemonSet guarantees one Pod per eligible node, no
replicasfield — used for node-local agents (logging, monitoring, CSI plugins). - StatefulSet gives Pods a stable name (
name-0,name-1, …), a stable DNS identity (via a required headless Service), and a stable PVC per Pod. - StatefulSet Pods default to ordered, sequential creation/deletion (
OrderedReady); PVCs are not deleted automatically on scale-down. - Job runs Pods until
completionssuccesses are reached;parallelismcaps concurrent Pods;backoffLimitcaps tolerated failures. - Job Pods must use
restartPolicy: NeverorOnFailure— neverAlways. - CronJob’s
concurrencyPolicy(Allow/Forbid/Replace) controls overlap between runs — know all three for the exam. - If a question describes “one agent per node,” think DaemonSet. “Stable network identity + its own disk,” think StatefulSet. “Run until done,” think Job. “On a schedule,” think CronJob.