Some workloads need to run on every node (or every node matching a selector) — not scaled by replica count, but by node count. Log collectors, monitoring agents, network plugins, storage drivers — these are DaemonSet territory.
1. What a DaemonSet Does
A DaemonSet ensures that exactly one Pod runs on each eligible node. When a node joins the cluster, the DaemonSet automatically schedules a Pod on it. When a node is removed, the Pod is garbage-collected.
Common DaemonSet Use Cases
| Category | Examples |
|---|---|
| Logging | Fluentd, Fluent Bit, Filebeat — collect logs from every node |
| Monitoring | Prometheus Node Exporter, Datadog Agent — node-level metrics |
| Networking | kube-proxy, Calico, Cilium — CNI plugins that configure every node |
| Storage | CSI node plugins — mount volumes on each node |
| Security | Falco — runtime security monitoring on every node |
DaemonSet Manifest
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: fluentd
namespace: kube-system
spec:
selector:
matchLabels:
app: fluentd
template:
metadata:
labels:
app: fluentd
spec:
containers:
- name: fluentd
image: fluent/fluentd:v1.16
resources:
requests:
cpu: 100m
memory: 200Mi
limits:
memory: 500Mi
volumeMounts:
- name: varlog
mountPath: /var/log
- name: containers
mountPath: /var/lib/docker/containers
readOnly: true
volumes:
- name: varlog
hostPath:
path: /var/log
- name: containers
hostPath:
path: /var/lib/docker/containers
replicas field. Unlike Deployments, DaemonSets don't have a replica count. The number of Pods is determined by the number of matching nodes. You control which nodes via nodeSelector, nodeAffinity, and tolerations.
2. Controlling Which Nodes Get a Pod
nodeSelector — Simple Label Matching
# Only schedule on nodes with label "disk=ssd":
spec:
template:
spec:
nodeSelector:
disk: ssd
nodeAffinity — Advanced Rules
# Only schedule on nodes in specific zones:
spec:
template:
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: topology.kubernetes.io/zone
operator: In
values: [us-east-1a, us-east-1b]
Tolerations — Running on Control Plane Nodes
Control plane nodes have taints that prevent scheduling. To run a DaemonSet on them (e.g., for monitoring), add tolerations:
# Tolerate control plane taint:
spec:
template:
spec:
tolerations:
- key: node-role.kubernetes.io/control-plane
operator: Exists
effect: NoSchedule
# For older clusters:
- key: node-role.kubernetes.io/master
operator: Exists
effect: NoSchedule
# Tolerate ALL taints (run on every node no matter what):
tolerations:
- operator: Exists # matches any key, any value, any effect
node.kubernetes.io/unschedulable and node.kubernetes.io/not-ready — so they still schedule on cordoned or not-ready nodes. This ensures system-critical agents keep running.
kubectl get ds -n kube-system.
3. Update Strategies
| Strategy | Behavior | Use Case |
|---|---|---|
RollingUpdate (default) | Kill old Pod, start new Pod — one node at a time | Most DaemonSets (logging, monitoring) |
OnDelete | Only updates Pods when manually deleted | Critical system agents where you control timing |
RollingUpdate Parameters
spec:
updateStrategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 1 # How many nodes can lose their DS Pod at once
maxSurge: 0 # (K8s 1.22+) Extra Pods allowed during update
| Config | Behavior | Trade-off |
|---|---|---|
maxUnavailable: 1, maxSurge: 0 | Delete old, then create new (one at a time) | Brief gap — no Pod on that node during transition |
maxUnavailable: 0, maxSurge: 1 | Create new, wait until Ready, then delete old | Zero-gap but temporarily 2 Pods on one node |
maxUnavailable: "10%" | Update 10% of nodes simultaneously | Faster rollout, higher blast radius |
maxUnavailable: 1 with maxSurge: 0 carefully — during the transition, that node briefly has no networking plugin. For logging agents, maxSurge: 1 is safer (no log gap). For critical infrastructure, OnDelete gives you full control over rollout timing.
Checking Rollout Status
# Watch DaemonSet rollout: kubectl rollout status daemonset/fluentd -n kube-system # See current state: kubectl get ds fluentd -n kube-system # NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE AGE # fluentd 3 3 3 3 3 5d # DESIRED = number of eligible nodes # UP-TO-DATE = Pods running latest template # If UP-TO-DATE < DESIRED, rollout is in progress
4. Practical Patterns
Host-Level Access
DaemonSet Pods often need access to the host's filesystem, network, or devices:
spec:
template:
spec:
# Use host network (share the node's IP):
hostNetwork: true
# Use host PID namespace (see all processes on node):
hostPID: true
# Mount host directories:
volumes:
- name: host-root
hostPath:
path: /
- name: host-proc
hostPath:
path: /proc
# Required for some agents (e.g., Falco needs kernel access):
containers:
- name: agent
securityContext:
privileged: true # ⚠️ Use only when necessary
privileged: true or hostNetwork are security-sensitive. In CKS context: these should be restricted to specific namespaces (kube-system), governed by Pod Security Standards, and the images should be from trusted registries only. Audit them carefully.
Resource Reservation
DaemonSet Pods consume resources on every node. Plan for this:
# If your DS requests 200Mi memory on each node, # and you have 100 nodes, that's 20Gi of memory reserved cluster-wide. # This reduces allocatable capacity for other workloads. # Check DS resource footprint: kubectl get ds -A -o custom-columns=\ NAME:.metadata.name,\ NS:.metadata.namespace,\ CPU_REQ:.spec.template.spec.containers[0].resources.requests.cpu,\ MEM_REQ:.spec.template.spec.containers[0].resources.requests.memory
DaemonSet vs Static Pods
| DaemonSet | Static Pod | |
|---|---|---|
| Managed by | DaemonSet controller (API server) | kubelet directly (manifest on disk) |
| Visible in API | ✅ Full CRUD | Mirror Pod (read-only) |
| Updates | Rolling update strategy | Modify file on disk → kubelet restarts |
| Use case | User workloads (logging, monitoring) | Bootstrap (etcd, API server, scheduler) |
| Node selection | nodeSelector, affinity, tolerations | Runs only on the node where the file exists |
/etc/kubernetes/manifests/. kube-proxy and CNI plugins are DaemonSets.
5. DaemonSet vs Deployment: When Which?
| Need | Use | Why |
|---|---|---|
| One Pod per node (infrastructure agent) | DaemonSet | Automatic: new node → new Pod |
| Exactly N replicas regardless of nodes | Deployment | Replica count is independent of cluster size |
| Host-level access (hostPath, hostNetwork) | DaemonSet | Makes sense per-node (monitoring, networking) |
| Scale independently of infrastructure | Deployment + HPA | Application workloads |
Summary
| Concept | Key Point |
|---|---|
| DaemonSet | Ensures one Pod on every (or selected) node |
| No replicas field | Pod count = eligible node count |
| Targeting nodes | nodeSelector, nodeAffinity, tolerations |
| Control plane nodes | Need explicit toleration for node-role.kubernetes.io/control-plane:NoSchedule |
| RollingUpdate | maxUnavailable controls how many nodes lose a Pod simultaneously |
| maxSurge (1.22+) | Allows new Pod before old is deleted (zero-gap updates) |
| OnDelete | Manual control — Pod only updates when you delete it |
| Common uses | kube-proxy, CNI, log collectors, monitoring agents, security agents |
📝 Quiz: DaemonSets
Q1: You have a 5-node cluster (3 workers + 2 control plane). A DaemonSet has no tolerations. How many Pods does it run?
node-role.kubernetes.io/control-plane:NoSchedule taint. Without a matching toleration, DaemonSet Pods don't schedule there.Q2: How do you make a DaemonSet run on ALL nodes including control plane?
tolerations:
- key: node-role.kubernetes.io/control-plane
operator: Exists
effect: NoScheduleOr use
- operator: Exists (tolerates all taints).Q3: A new worker node joins the cluster. What happens to existing DaemonSets?
Q4: What's the difference between maxUnavailable: 1, maxSurge: 0 and maxUnavailable: 0, maxSurge: 1 for a DaemonSet update?
maxUnavailable:0, maxSurge:1: New Pod starts first (temporarily 2 Pods on one node), once Ready the old Pod is deleted. Zero-gap but needs extra resources per node during transition.
Q5: kube-proxy runs as a DaemonSet but etcd runs as a Static Pod. Why the difference?
Q6: You want a logging DaemonSet to run only on nodes labeled environment=production. A new node without this label joins. Does it get a Pod?
nodeSelector: {environment: production}, only nodes with that label are eligible. The new unlabeled node doesn't match, so no Pod is created. If you later add the label to the node, the Pod will be created automatically.