DaemonSets
|
This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production; see the section bibliography for the reference material consulted while preparing these pages. |
StatefulSets covers the controller that gives each Pod a stable identity tied to an ordinal; a DaemonSet is the other axis a controller can bind Pods to: instead of a replica count, it binds a Pod to every node (or every node matching a selector), one copy per node, and keeps that mapping true as nodes join or leave the cluster. This page is written against Kubernetes 1.37; see the section index for the full version baseline. Unlike most of this section, no O’Reilly book chapter covers DaemonSets — both books' sampler material omits the chapter (Book 2’s own table of contents lists it as one of the chapters the 1st-edition sampler leaves out), so everything below is drawn straight from the official documentation, with no "book era" table to reconcile.
What a DaemonSet is, and when to reach for one
A Deployment (ReplicaSets and Deployments) answers "keep
N replicas of this Pod running somewhere in the cluster", and the scheduler is free to place all N of them on the
same node or spread them however it likes. A DaemonSet answers a different question entirely: "run exactly one
copy of this Pod on every node that matches this selector, and keep it that way as nodes are added or removed" — there is no replicas field at all, because the desired count is derived from the number of matching nodes, not
set by hand. Three use cases come up repeatedly, and all three share the same shape: a piece of infrastructure that
has to exist per node, not per application:
-
A log shipper — an agent such as Fluent Bit or Filebeat that tails every container’s log files on a node and forwards them to a central store, so logs survive Pod deletion instead of only living in
kubectl logs. -
A node exporter — a metrics agent such as Prometheus’s
node_exporterthat reads/procand/syson the host to expose CPU, memory, disk and network metrics for that node, one target per node (Prometheus: Containers and Kubernetes Monitoring covers scraping these). -
A CNI or networking agent — Calico’s
calico-node, Cilium’s agent, or kube-proxy itself (where it still runs as a DaemonSet rather than as a static Pod), each of which must be present on every node before that node can route Pod traffic at all, including on control-plane nodes that would otherwise run no application workload.
All three need kubectl get pods -n <namespace> -o wide to show exactly one Pod per node with a NODE column that
never repeats, which is the DaemonSet controller’s whole job: it watches the cluster’s node list the same way a
ReplicaSet watches its Pods, and reconciles "one Pod here" for every node the selector matches, creating a Pod on a
newly-joined node and deleting the Pod on a node that is removed or stops matching.
Source: Kubernetes Docs — DaemonSet.
Scheduling: nodeSelector, node affinity and tolerations
By default, with no selector at all, a DaemonSet targets every node in the cluster — including control-plane nodes, which is exactly what a CNI agent or kube-proxy needs and what an application-level log shipper usually does too, since logs exist on every node regardless of what runs there. Narrowing that set uses the same two mechanisms any other Pod does, covered in full in Scheduling and Disruptions:
-
spec.template.spec.nodeSelectorrestricts the DaemonSet to nodes carrying a given label, e.g. only nodes labelednode-role.kubernetes.io/edgefor an edge-specific agent. -
spec.template.spec.affinity.nodeAffinityexpresses the same restriction with the fullerrequiredDuringSchedulingIgnoredDuringExecution/preferredDuringSchedulingIgnoredDuringExecutionsyntax, needed once the condition is more than a flat label match (anIn/NotIn/Existsexpression, or several terms ORed together). Internally, the DaemonSet controller itself compiles a per-nodenodeAffinityterm onto each Pod it creates — since Kubernetes 1.17 it no longer setsspec.nodeNamedirectly, it lets the default scheduler place the Pod exactly as it would any other, which is what lets a DaemonSet Pod also carry ordinary Pod- and node-affinity rules, resource requests, and priority classes rather than bypassing the scheduler entirely.
Reaching every node, including ones with a NoSchedule or NoExecute taint (most commonly
node-role.kubernetes.io/control-plane, or a custom taint on a dedicated node pool), needs a matching
tolerations entry on the Pod template — without it, the DaemonSet’s own Pod is scheduled everywhere except the
tainted nodes, silently leaving them without the agent. A handful of tolerations for conditions the cluster itself
manages (node.kubernetes.io/not-ready, node.kubernetes.io/unreachable, node.kubernetes.io/disk-pressure,
node.kubernetes.io/memory-pressure, node.kubernetes.io/unschedulable, all with effect: NoExecute or
NoSchedule) are added automatically by an admission controller for every DaemonSet Pod, precisely so a node
agent — especially a CNI agent, which is often what would clear those conditions — keeps running through the
conditions it exists to fix. A control-plane taint is never added automatically; it must be tolerated explicitly, as
in the log-agent example below.
A Fluent Bit-style log-agent DaemonSet
The manifest below follows the shape of the official Fluent Bit Kubernetes logging guide: a DaemonSet mounts the
node’s own log directory read-only through a hostPath volume
(Volumes and Ephemeral Storage covers hostPath and its
risks in full), reads its own configuration from a ConfigMap
(ConfigMaps), and tolerates the control-plane taint so every node — including the ones running only cluster infrastructure — gets its logs shipped. The configuration below is a
placeholder: it tails Pod log files and writes parsed records to standard output, not to any real backend, so the
example has no credentials or endpoint of its own to leak.
apiVersion: v1
kind: ConfigMap
metadata:
name: fluent-bit-config
namespace: bookshelf
data:
fluent-bit.conf: |
[SERVICE]
Flush 5
Log_Level info
[INPUT]
Name tail
Path /var/log/pods/bookshelf_*/*/*.log
multiline.parser cri # kind runs containerd, which writes CRI-format logs
Tag bookshelf.*
[OUTPUT]
Name stdout
Match *
# Replace with a real backend, e.g. Elasticsearch or a hosted logging service.
---
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: fluent-bit-agent
namespace: bookshelf
labels:
app.kubernetes.io/name: fluent-bit-agent
spec:
selector:
matchLabels:
app.kubernetes.io/name: fluent-bit-agent
updateStrategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 1
template:
metadata:
labels:
app.kubernetes.io/name: fluent-bit-agent
spec:
tolerations:
- key: node-role.kubernetes.io/control-plane
operator: Exists
effect: NoSchedule
containers:
- name: fluent-bit
image: fluent/fluent-bit:3.1
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
memory: 128Mi
volumeMounts:
- name: varlogpods
mountPath: /var/log/pods
readOnly: true
- name: config
mountPath: /fluent-bit/etc/
volumes:
- name: varlogpods
hostPath:
path: /var/log/pods
- name: config
configMap:
name: fluent-bit-config
A node exporter or CNI agent DaemonSet looks the same at the controller level — one Pod per selected node, the same
tolerations/updateStrategy shape — but typically also sets hostNetwork: true and hostPID: true so the
container observes the node’s own network interfaces and process table directly, rather than the Pod’s own isolated
namespaces; that trade-off (and the security review it deserves) belongs to
Pod Security, not repeated here.
kubectl apply -f fluent-bit-daemonset.yaml
kubectl get daemonset fluent-bit-agent -n bookshelf
kubectl get pods -n bookshelf -l app.kubernetes.io/name=fluent-bit-agent -o wide
NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE
fluent-bit-agent 3 3 3 3 3 <none> 2m
NAME READY STATUS RESTARTS AGE NODE
fluent-bit-agent-4f2xk 1/1 Running 0 2m kind-worker
fluent-bit-agent-9m7qz 1/1 Running 0 2m kind-worker2
fluent-bit-agent-t8v2n 1/1 Running 0 2m kind-control-plane
Source: Kubernetes Docs — DaemonSet.
Update strategy: RollingUpdate and OnDelete
spec.updateStrategy.type controls how an edited Pod template reaches the Pods a DaemonSet already runs — the same
question ReplicaSets and Deployments answers for a
Deployment’s spec.strategy, with a smaller set of knobs because there is exactly one Pod per node rather than an
arbitrary replica count to shift between two ReplicaSets:
updateStrategy.type |
Behaviour |
|---|---|
|
The controller deletes and recreates a bounded number of node Pods at a time, oldest first, until every node runs
the new template. |
|
The controller only ever updates its own template; it never touches an existing Pod. A new node still gets a Pod running the new template immediately, but every already-running Pod keeps running the old template until something else deletes it — a person, a node drain, or a script — at which point its replacement picks up the new template. This is the right choice for an agent so sensitive (or so tied to node-level state) that its rollout must be driven one node at a time, by hand or by a controlled script, rather than trusted to the controller’s own pacing. |
kubectl set image daemonset/fluent-bit-agent fluent-bit=fluent/fluent-bit:3.2 -n bookshelf
kubectl rollout status daemonset/fluent-bit-agent -n bookshelf
kubectl rollout history daemonset/fluent-bit-agent -n bookshelf
kubectl rollout undo daemonset/fluent-bit-agent -n bookshelf
kubectl rollout status, history and undo work against a DaemonSet exactly as they do against a Deployment
(ReplicaSets and Deployments's kubectl rollout section
applies unchanged); kubectl scale does not, because a DaemonSet has no replicas field to scale.
DaemonSets vs static Pods
Both a DaemonSet Pod and a static Pod end up running on every targeted node without an explicit nodeName
per-Pod assignment, which invites confusing the two, but they solve different problems at different layers:
| DaemonSet | Static Pod | |
|---|---|---|
Who creates the Pod |
The DaemonSet controller, running in |
The kubelet on that node, reading manifest files from a local directory (or, less commonly, a URL) that the kubelet itself polls — no controller, no scheduler, no API server round trip required to create it. |
Requires a working control plane |
Yes — the API server, controller manager and scheduler must already be up. |
No — this is precisely why kubeadm bootstraps |
Visible via |
Fully — |
A read-only mirror Pod is published to the API server so |
Update mechanism |
|
Editing (or removing) the manifest file on that one node — there is no fleet-wide update mechanism at all; each node’s kubelet only ever looks at its own manifest directory. |
In practice static Pods are reserved for the small set of components that must exist before there is a cluster to schedule anything onto — the control-plane components kubeadm installs this way, and occasionally a node-local break-glass debugging Pod — while every node-level application agent (a log shipper, a metrics exporter, most CNI plugins) is a DaemonSet, because it needs the API-server visibility, centralized rollout and RBAC that a static Pod’s manifest file cannot provide.
Source: Kubernetes Docs — Static Pods.