Deploy Node Services with Kubernetes DaemonSets

KubernetesBeginner
Practice Now

Introduction

Some software should run once on every eligible node rather than a fixed replica count. Log collectors, monitoring agents, and networking components commonly use DaemonSets for this node-oriented placement.

You will label the prepared node, deploy a node reporter, trace its ownership, change node eligibility, and perform a rolling update. The single-node lab makes the placement rule easy to observe while preserving the same controller behavior used by larger clusters.

The key question throughout this lab is not “how many replicas should run?” but “which nodes are eligible?” A DaemonSet continually answers that question and creates one Pod on each matching node. In this environment there is one node, so every eligibility change produces a clear one-to-zero or zero-to-one result.

Mark an Eligible Node

In this step, you will add a purpose-specific label to the node. Labels describe node attributes that controllers can select.

Begin by viewing the node and its existing labels. The output is wide, so do not try to memorize it; you only need to recognize that labels are key-value facts attached to the Node object.

cd /home/labex/project/daemonset-workloads
kubectl get nodes --show-labels

Add the new label. The --overwrite option is unnecessary here because setup removed any old value, but kubectl label will tell you if a conflicting value already exists:

kubectl label node labex-v135 practice-tier=operations
kubectl get node labex-v135 -L practice-tier

-L practice-tier adds that label as a table column. The node now advertises itself as eligible for the practice operations agent.

The second command gives you a focused result instead of the very long --show-labels line:

NAME         STATUS   ROLES           VERSION   PRACTICE-TIER
labex-v135   Ready    control-plane   v1.35.5   operations

PRACTICE-TIER=operations is the evidence you need. The label does not run anything by itself; it becomes meaningful when a workload selects it in the next step.

Deploy a DaemonSet

In this step, you will create a DaemonSet that targets nodes carrying the new label.

Read the manifest as three connected decisions before typing it. The selector and Pod label both use app: node-reporter, allowing the controller to recognize its Pods. nodeSelector then limits eligibility to nodes labeled practice-tier=operations. Finally, the Downward API copies the selected Pod's spec.nodeName into the container as NODE_NAME.

The loop keeps the demo agent alive. A real DaemonSet might run a log collector or monitoring agent instead.

cat <<'EOF' > node-reporter.yaml
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: node-reporter
  namespace: practice-daemon
spec:
  selector:
    matchLabels:
      app: node-reporter
  template:
    metadata:
      labels:
        app: node-reporter
    spec:
      nodeSelector:
        practice-tier: operations
      containers:
        - name: reporter
          image: busybox:1.36
          imagePullPolicy: IfNotPresent
          command: ["sh", "-c", "while true; do echo node=${NODE_NAME} version=v1; sleep 15; done"]
          env:
            - name: NODE_NAME
              valueFrom:
                fieldRef:
                  fieldPath: spec.nodeName
EOF

Create the namespace, apply the file, and wait for the DaemonSet rollout:

kubectl create namespace practice-daemon
kubectl apply -f node-reporter.yaml
kubectl rollout status daemonset/node-reporter -n practice-daemon --timeout=60s
kubectl get daemonset,pods -n practice-daemon -o wide

The downward API fieldRef exposes the scheduled node name without hard-coding it. On this one-node cluster, desired, current, and ready counts should all be one.

The Pod name contains a generated suffix, but the NODE column should be labex-v135. That is direct evidence that the eligibility rule and scheduling result agree.

On the one-node test cluster, the important columns looked like this:

NAME            DESIRED   CURRENT   READY   NODE SELECTOR
node-reporter   1         1         1       practice-tier=operations

NAME                  READY   STATUS    NODE
node-reporter-...      1/1     Running   labex-v135

Read the first row from left to right: one node matches the selector, one Pod exists, and that Pod is ready. On a larger cluster, these counts would grow with the number of eligible nodes.

Observe Eligibility and Ownership

In this step, you will prove that node eligibility, not a replicas field, controls the DaemonSet Pod count.

First capture a healthy baseline. The custom columns connect the generated Pod back to the DaemonSet, and the logs prove that the Downward API supplied the node name:

kubectl get pods -n practice-daemon -l app=node-reporter -o custom-columns='POD:.metadata.name,NODE:.spec.nodeName,OWNER:.metadata.ownerReferences[0].kind,READY:.status.containerStatuses[0].ready'
kubectl logs -n practice-daemon -l app=node-reporter --tail=3

The ownership table and log should resemble:

POD                   NODE         OWNER       READY
node-reporter-...     labex-v135   DaemonSet   true
node=labex-v135 version=v1

The OWNER field proves this is a controller-managed Pod. The log proves that NODE_NAME came from the Pod's actual spec.nodeName, not from a hard-coded value.

Temporarily remove the label and watch the Pod disappear:

kubectl label node labex-v135 practice-tier-
kubectl get daemonset node-reporter -n practice-daemon
kubectl wait --for=delete pod -n practice-daemon -l app=node-reporter --timeout=60s

A trailing - removes a label key. Restore eligibility and wait for the controller to create a replacement Pod:

After removal, the DaemonSet briefly reports the following stable relationship:

NAME            DESIRED   CURRENT   READY
node-reporter   0         0         0

This is a healthy response to changed eligibility, not a failure. No node currently matches the DaemonSet's nodeSelector, so the correct desired count is zero.

kubectl label node labex-v135 practice-tier=operations
kubectl rollout status daemonset/node-reporter -n practice-daemon --timeout=60s

Compare the Pod name before and after the label change. The replacement has a new Pod identity, while the DaemonSet name and desired behavior remain the same. This is reconciliation in action.

Roll Out a DaemonSet Update

In this step, you will update the saved manifest and observe the DaemonSet replace the node agent. Keeping the file and the live object aligned makes the desired state reproducible.

First create a working copy with cp. Then use the same focused text-editing pattern introduced in Kubernetes for Beginners: sed -i 's/old/new/' file replaces matching text directly in the file, while grep -n prints the matching line with its line number so you can inspect the result before applying it.

cp node-reporter.yaml node-reporter-v2.yaml
sed -i 's/version=v1/version=v2/' node-reporter-v2.yaml
grep -n 'version=' node-reporter-v2.yaml
kubectl apply -f node-reporter-v2.yaml
kubectl rollout status daemonset/node-reporter -n practice-daemon --timeout=60s
kubectl rollout history daemonset/node-reporter -n practice-daemon
kubectl logs -n practice-daemon -l app=node-reporter --tail=3

The copied file preserves the original v1 example for comparison. The one substitution changes only the version text inside the container command; applying the updated file changes the Pod template, which creates a new controller revision and replacement Pod. The logs should now contain version=v2.

rollout history confirms that Kubernetes recorded a new revision. DaemonSets therefore support controlled template updates much like Deployments, but the replacement is organized around eligible nodes instead of a replica count.

The tested output included two revisions and the new log message:

REVISION  CHANGE-CAUSE
1         <none>
2         <none>

node=labex-v135 version=v2

CHANGE-CAUSE=<none> only means no human-readable change annotation was recorded. Revision 2 plus version=v2 in the replacement Pod's log are the useful proofs that the rollout happened.

Summary

You used node labels to control DaemonSet eligibility, observed one-Pod-per-eligible-node reconciliation, traced controller ownership, and rolled out a node-agent update. You can now contrast DaemonSets with replica-oriented Deployments and completion-oriented Jobs.