Node-level workloads in Kubernetes need a controller that follows nodes rather than replica counts. A DaemonSet creates one Pod on every eligible node, which fits log collectors, monitoring agents, storage helpers, and network components that must run close to the kubelet.

A DaemonSet still uses a normal Pod template, selector, labels, resource requests, and security settings. The selector must match the template labels, and a nodeSelector or node affinity narrows the eligible node set when the daemon should run only on selected workers.

For a disposable validation, two labeled worker nodes in a temporary namespace keep the DaemonSet away from unrelated workloads. Use existing production node labels instead of adding throwaway labels when the DaemonSet belongs in a live cluster, and leave production labels and namespaces in place when those objects should remain.

Steps to create a Kubernetes DaemonSet:

  1. Create a namespace for the DaemonSet test.
    $ kubectl create namespace node-agents
    namespace/node-agents created

    Use the real application or platform namespace instead of node-agents when deploying a production node agent.
    Related: How to create a Kubernetes namespace

  2. Label the nodes that should receive the DaemonSet pods.
    $ kubectl label node worker-a worker-b daemonset=log-agent
    node/worker-a labeled
    node/worker-b labeled

    Replace worker-a and worker-b with node names from your cluster. If the nodes already have a stable pool or hardware label, use that existing label in the manifest instead of adding a temporary one.

  3. Create a manifest named log-agent-daemonset.yaml.
    log-agent-daemonset.yaml
    apiVersion: apps/v1
    kind: DaemonSet
    metadata:
      name: log-agent
      namespace: node-agents
      labels:
        app: log-agent
    spec:
      selector:
        matchLabels:
          app: log-agent
      template:
        metadata:
          labels:
            app: log-agent
        spec:
          nodeSelector:
            daemonset: log-agent
          containers:
            - name: log-agent
              image: busybox:1.36
              command:
                - sh
                - -c
                - echo node-agent running on $NODE_NAME; sleep 3600
              env:
                - name: NODE_NAME
                  valueFrom:
                    fieldRef:
                      fieldPath: spec.nodeName
              resources:
                requests:
                  cpu: 10m
                  memory: 16Mi
              securityContext:
                allowPrivilegeEscalation: false
                capabilities:
                  drop:
                    - ALL
                readOnlyRootFilesystem: true
                runAsNonRoot: true
                runAsUser: 65534
                seccompProfile:
                  type: RuntimeDefault

    The selector.matchLabels value must match the Pod template labels. The nodeSelector limits eligible nodes to the ones labeled daemonset=log-agent.

  4. Validate the manifest with a server-side dry run.
    $ kubectl apply --dry-run=server -f log-agent-daemonset.yaml
    daemonset.apps/log-agent created (server dry run)

    A server-side dry run asks the API server to validate the object without creating the DaemonSet pods.

  5. Apply the DaemonSet manifest.
    $ kubectl apply -f log-agent-daemonset.yaml
    daemonset.apps/log-agent created
  6. Wait for the DaemonSet rollout to finish.
    $ kubectl rollout status daemonset/log-agent --namespace node-agents --timeout=180s
    daemon set "log-agent" successfully rolled out
  7. Check the DaemonSet counts.
    $ kubectl get daemonset log-agent --namespace node-agents -o wide
    NAME        DESIRED   CURRENT   READY   UP-TO-DATE   AVAILABLE   NODE SELECTOR         AGE   CONTAINERS   IMAGES         SELECTOR
    log-agent   2         2         2       2            2           daemonset=log-agent   13s   log-agent    busybox:1.36   app=log-agent

    DESIRED, CURRENT, READY, and AVAILABLE should match the number of eligible nodes for the selector.

  8. Verify the DaemonSet pod placement.
    $ kubectl get pods --namespace node-agents -o wide
    NAME              READY   STATUS    RESTARTS   AGE   IP           NODE       NOMINATED NODE   READINESS GATES
    log-agent-hbn5c   1/1     Running   0          13s   10.244.2.2   worker-a   <none>           <none>
    log-agent-m7mgl   1/1     Running   0          13s   10.244.1.2   worker-b   <none>           <none>

    One Running pod on each labeled node confirms the controller placed the daemon only on eligible nodes.

  9. Read the DaemonSet pod logs.
    $ kubectl logs --namespace node-agents -l app=log-agent --prefix=true
    [pod/log-agent-hbn5c/log-agent] node-agent running on worker-a
    [pod/log-agent-m7mgl/log-agent] node-agent running on worker-b

    The NODE_NAME environment variable comes from the Pod's spec.nodeName field, so the log line proves each container started on its assigned node.
    Related: How to view Kubernetes pod logs

  10. Delete the lab namespace after the DaemonSet test.
    $ kubectl delete namespace node-agents
    namespace "node-agents" deleted

    Run this cleanup against the temporary namespace only. Deleting a namespace removes all objects inside it.

  11. Remove the temporary node labels.
    $ kubectl label node worker-a worker-b daemonset-
    node/worker-a unlabeled
    node/worker-b unlabeled

    Skip this cleanup when the label is a real node-pool, hardware, or placement label used by other workloads.