Skip to content
Codeloom
Kubernetes

Kubernetes DaemonSets Explained with Practical Examples

Learn how DaemonSets run exactly one pod per node for logging, monitoring, and networking agents. Includes tolerations and update strategies.

·6 min read · By Codeloom
Intermediate 9 min read

What you'll learn

  • What DaemonSets are and when to use them instead of Deployments
  • How DaemonSets schedule pods on every node
  • Targeting specific nodes with node selectors and tolerations
  • Update strategies for rolling out DaemonSet changes
  • Common patterns for log collectors, monitoring agents, and CNI plugins

Prerequisites

None — this post is self-contained.

A Deployment runs N replicas wherever the scheduler finds room. A DaemonSet runs exactly one pod on every node (or a selected subset). This distinction makes DaemonSets the right choice for node-level infrastructure: log collectors, metrics exporters, storage daemons, and network plugins.

When to Use a DaemonSet

Use a DaemonSet when the workload must run on every node:

  • Log collection - Fluentd, Fluent Bit, or Promtail tailing container logs on each node
  • Monitoring - Node Exporter exposing host metrics to Prometheus
  • Networking - CNI plugins like Calico or Cilium that configure pod networking
  • Storage - CSI node plugins that mount volumes on each node
  • Security - Falco or other runtime security agents inspecting system calls

If your workload does not need to be on every node, use a Deployment instead.

Basic DaemonSet

A minimal DaemonSet that runs a log collector on every node:

apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: fluentbit
  namespace: logging
  labels:
    app: fluentbit
spec:
  selector:
    matchLabels:
      app: fluentbit
  template:
    metadata:
      labels:
        app: fluentbit
    spec:
      containers:
        - name: fluentbit
          image: fluent/fluent-bit:latest
          resources:
            requests:
              cpu: 100m
              memory: 128Mi
            limits:
              cpu: 200m
              memory: 256Mi
          volumeMounts:
            - name: varlog
              mountPath: /var/log
              readOnly: true
            - name: containers
              mountPath: /var/lib/docker/containers
              readOnly: true
      volumes:
        - name: varlog
          hostPath:
            path: /var/log
        - name: containers
          hostPath:
            path: /var/lib/docker/containers

Apply it, and Kubernetes creates one Fluent Bit pod on every node in the cluster. When a new node joins, the DaemonSet controller automatically schedules a pod on it. When a node is removed, the pod is garbage collected.

How Scheduling Works

DaemonSets bypass the normal scheduler. The DaemonSet controller creates a pod for each eligible node and sets the spec.nodeName field directly (in older versions) or uses node affinity (in current versions). This means:

  • DaemonSet pods ignore most scheduling constraints like resource pressure
  • They tolerate the node.kubernetes.io/not-ready taint by default
  • They are not evicted during node drain unless explicitly told to

Check DaemonSet status with:

kubectl get daemonset -n logging

# Output:
# NAME       DESIRED   CURRENT   READY   UP-TO-DATE   AVAILABLE
# fluentbit  5         5         5       5             5

DESIRED matches the number of eligible nodes. If READY is less than DESIRED, pods are failing to start.

Targeting Specific Nodes

Sometimes you want a DaemonSet on a subset of nodes. Use a node selector:

spec:
  template:
    spec:
      nodeSelector:
        node-role.kubernetes.io/worker: ""
      containers:
        - name: agent
          image: monitoring-agent:latest

This runs the agent only on nodes labeled node-role.kubernetes.io/worker. Control plane nodes are excluded.

For more expressive matching, use node affinity:

spec:
  template:
    spec:
      affinity:
        nodeAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            nodeSelectorTerms:
              - matchExpressions:
                  - key: disktype
                    operator: In
                    values:
                      - ssd
      containers:
        - name: storage-agent
          image: storage-agent:latest

Tolerations for Control Plane Nodes

By default, control plane nodes have taints that prevent regular workloads from scheduling on them. If your DaemonSet must run on control plane nodes (for example, a monitoring agent), add tolerations:

spec:
  template:
    spec:
      tolerations:
        - key: node-role.kubernetes.io/control-plane
          operator: Exists
          effect: NoSchedule
      containers:
        - name: node-exporter
          image: prom/node-exporter:latest

To run on every node regardless of taints, use a blanket toleration:

tolerations:
  - operator: Exists

Be cautious with blanket tolerations. They schedule pods even on nodes tainted for specific reasons like being unready or having disk pressure.

Update Strategies

DaemonSets support two update strategies:

RollingUpdate (Default)

Pods are updated one node at a time. The old pod is terminated before the new one is created on the same node:

spec:
  updateStrategy:
    type: RollingUpdate
    rollingUpdate:
      maxUnavailable: 1
      maxSurge: 0

maxUnavailable controls how many nodes can have their DaemonSet pod down simultaneously. Setting it to 1 (the default) means nodes are updated sequentially. Setting it to 25% or a higher number speeds up rollouts at the cost of temporary gaps in coverage.

Starting in Kubernetes 1.22, maxSurge allows creating the new pod before terminating the old one, enabling zero-downtime updates for DaemonSets:

spec:
  updateStrategy:
    type: RollingUpdate
    rollingUpdate:
      maxUnavailable: 0
      maxSurge: 1

This creates the new pod first, waits for it to be ready, then terminates the old pod. This is ideal for networking plugins where a gap in coverage would disrupt traffic.

OnDelete

Pods are only updated when manually deleted:

spec:
  updateStrategy:
    type: OnDelete

This gives you full control over the rollout order. Update a node by deleting its DaemonSet pod, and the controller creates a replacement with the new spec. This is useful for critical infrastructure where you want to update nodes one at a time during maintenance windows.

Monitoring a DaemonSet Rollout

# Watch rollout progress
kubectl rollout status daemonset/fluentbit -n logging

# Check rollout history
kubectl rollout history daemonset/fluentbit -n logging

# Roll back to previous version
kubectl rollout undo daemonset/fluentbit -n logging

Resource Limits

DaemonSet pods compete for resources with application pods on each node. Always set resource requests and limits to prevent a misbehaving agent from starving application workloads:

resources:
  requests:
    cpu: 100m
    memory: 128Mi
  limits:
    cpu: 200m
    memory: 256Mi

Account for DaemonSet resource consumption when sizing nodes. If every node runs a log collector (128 Mi), a monitoring agent (64 Mi), and a CNI plugin (128 Mi), that is 320 Mi of memory per node reserved for infrastructure before any application pods.

Priority Classes

Infrastructure DaemonSets should use a high-priority PriorityClass to avoid being evicted when a node is under memory pressure:

apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
  name: system-node-critical
value: 2000001000
globalDefault: false
description: "Critical node-level infrastructure"
---
spec:
  template:
    spec:
      priorityClassName: system-node-critical
      containers:
        - name: fluentbit
          image: fluent/fluent-bit:latest

Kubernetes provides built-in priority classes system-node-critical and system-cluster-critical for this purpose.

Practical Recommendations

Use DaemonSets for any workload that must run on every node. Always set resource requests to prevent resource starvation. Use RollingUpdate with maxSurge: 1 for zero-downtime updates of critical infrastructure. Add tolerations explicitly rather than using blanket tolerations. Assign high priority classes to infrastructure DaemonSets so they survive node pressure events. Monitor DESIRED vs READY counts to detect nodes where the DaemonSet pod is failing.