Kubernetes DaemonSet Controller: Ensuring Pod Distribution Across Cluster Nodes

The DaemonSet controller guarantees that a specific Pod runs on every node (or selected nodes) within a cluster. When a new node joins the cluster, the controller automatically deploys the Pod to that node. Conversely, when a node is removed, the corresponding Pod is garbage collected.

Common Use Cases

DaemonSets are typically employed for cluster-wide infrastructure services:

  • Storage daemons: Deploy storage solutions like GlusterFS or Ceph on each node
  • Log collection agents: Run Fluentd, Logstash, or similar agents across all nodes
  • Monitoring exporters: Deploy node-level collectors such as Prometheus Node Exporter or Zabbix agents
  • Network plugins: CNI implementations that require a copy on every host

EFK Stack Deployment with DaemonSet

The following example deploys the Elasticsearch, Fluentd, and Kibana stack for centralized logging.

Prerequisites

Ensure a StorageClass is available for persistent storage. Elasticsearch requires dynamic provisioning for its persistent volumes.

Elasticsearch Cluster Setup

Service Configuration

Elasticsearch uses two distinct ports: 9200 for HTTP API acess (search, monitoring, client connections) and 9300 for inter-node cluster communication (master election, shard allocation, node discovery).

apiVersion: v1
kind: Namespace
metadata:
  name: logging
  labels:
    component: efk
---
apiVersion: v1
kind: Service
metadata:
  name: elasticsearch
  namespace: logging
  labels:
    component: elasticsearch
spec:
  clusterIP: None
  selector:
    component: elasticsearch
  ports:
  - name: http
    port: 9200
  - name: transport
    port: 9300
---
apiVersion: v1
kind: Service
metadata:
  name: elasticsearch-http
  namespace: logging
  labels:
    component: elasticsearch
spec:
  selector:
    component: elasticsearch
  ports:
  - name: http
    port: 9200
    targetPort: 9200
    nodePort: 32005
  type: NodePort

StatefulSet for Elasticsearch

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: elasticsearch
  namespace: logging
  labels:
    component: elasticsearch
spec:
  replicas: 3
  serviceName: elasticsearch
  selector:
    matchLabels:
      component: elasticsearch
  template:
    metadata:
      labels:
        component: elasticsearch
    spec:
      initContainers:
      - name: fix-permissions
        image: busybox:latest
        imagePullPolicy: IfNotPresent
        command: ["sh", "-c", "chown -R 1000:1000 /data"]
        securityContext:
          privileged: true
        volumeMounts:
        - name: data
          mountPath: /usr/share/elasticsearch/data
      - name: increase-vm-max-map
        image: busybox:latest
        imagePullPolicy: IfNotPresent
        command: ["sysctl", "-w", "vm.max_map_count=262144"]
        securityContext:
          privileged: true
      - name: increase-ulimit
        image: busybox:latest
        imagePullPolicy: IfNotPresent
        command:
        - sh
        - -c
        - |
          ulimit -Hn unlimited
          ulimit -Sn unlimited
          ulimit -n 65536
        securityContext:
          privileged: true
      containers:
      - name: elasticsearch
        image: docker.elastic.co/elasticsearch/elasticsearch:7.17.19
        imagePullPolicy: IfNotPresent
        resources:
          limits:
            cpu: "1"
          requests:
            cpu: "0.5"
        env:
        - name: cluster.name
          value: logging-cluster
        - name: node.name
          valueFrom:
            fieldRef:
              fieldPath: metadata.name
        - name: discovery.seed_hosts
          value: "elasticsearch-0.elasticsearch,elasticsearch-1.elasticsearch,elasticsearch-2.elasticsearch"
        - name: cluster.initial_master_nodes
          value: "elasticsearch-0,elasticsearch-1,elasticsearch-2"
        - name: bootstrap.memory_lock
          value: "false"
        - name: ES_JAVA_OPTS
          value: "-Xms512m -Xmx512m"
        ports:
        - containerPort: 9200
          name: http
        - containerPort: 9300
          name: transport
        volumeMounts:
        - name: data
          mountPath: /usr/share/elasticsearch/data
        - name: localtime
          mountPath: /etc/localtime
      volumes:
      - name: localtime
        hostPath:
          path: /etc/localtime
          type: File
  volumeClaimTemplates:
  - metadata:
      name: data
    spec:
      storageClassName: "nfs-storage"
      accessModes: ["ReadWriteOnce"]
      resources:
        requests:
          storage: 1Gi

Environment Variible Explanations

Variable Purpose
node.name Resolves naming conflicts during cluster bootstrap
discovery.seed_hosts Specifies addresses for node discovery during cluster formation
cluster.initial_master_nodes Lists master-eligible nodes for initial election (required in production)
ES_JAVA_OPTS Configures JVM heap size (512MB minimum recommended)
bootstrap.memory_lock Disables memory swapping; Kubernetes already handles this at node level

Verifying Elasticsearch Deployment

Query cluster health status:

curl http://<node-ip>:32005/_cluster/health?pretty

Expected response indicates a healthy cluster:

{
  "cluster_name": "logging-cluster",
  "status": "green",
  "number_of_nodes": 3,
  "number_of_data_nodes": 3
}

Kibana Deployment

apiVersion: v1
kind: Service
metadata:
  name: kibana
  namespace: logging
  labels:
    component: kibana
spec:
  selector:
    component: kibana
  ports:
  - port: 5601
    targetPort: 5601
    nodePort: 30006
  type: NodePort
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: kibana
  namespace: logging
  labels:
    component: kibana
spec:
  replicas: 1
  selector:
    matchLabels:
      component: kibana
  template:
    metadata:
      labels:
        component: kibana
    spec:
      containers:
      - name: kibana
        image: docker.elastic.co/kibana/kibana:7.17.19
        imagePullPolicy: IfNotPresent
        resources:
          limits:
            cpu: "1"
          requests:
            cpu: "0.5"
        env:
        - name: ELASTICSEARCH_HOSTS
          value: http://elasticsearch:9200
        volumeMounts:
        - name: localtime
          mountPath: /etc/localtime
        ports:
        - containerPort: 5601
      volumes:
      - name: localtime
        hostPath:
          path: /etc/localtime

Fluentd DaemonSet for Log Collection

Fluentd runs as a DaemonSet to collect logs from every node in the cluster.

Fluentd Configurasion

apiVersion: v1
kind: ConfigMap
metadata:
  name: fluentd-config
  namespace: logging
  labels:
    component: fluentd
data:
  fluent.conf: |
    <source>
      @type tail
      format json
      path /var/log/containers/*.log
      parser cri
      tag kube.*
      read_from_head true
      <parse>
        @type cri
        format regex
        Regex ^(?<time>[^ ]+) (?<stream>stdout|stderr) (?<logtag>[^ ]*) (?<message>.*)$
      </parse>
    </source>
    <match kube.**>
      @type elasticsearch
      host elasticsearch.logging.svc.cluster.local
      port 9200
      logstash_format true
      logstash_prefix kubernetes
      logstash_dateformat %Y.%m.%d
      include_tag_key true
      tag_key @log_name
    </match>

DaemonSet Manifest

apiVersion: v1
kind: ServiceAccount
metadata:
  name: log-collector
  namespace: logging
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: log-collector
rules:
- apiGroups: [""]
  resources: ["pods", "namespaces"]
  verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: log-collector
roleRef:
  kind: ClusterRole
  name: log-collector
  apiGroup: rbac.authorization.k8s.io
subjects:
- kind: ServiceAccount
  name: log-collector
  namespace: logging
---
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: fluentd
  namespace: logging
  labels:
    component: fluentd
spec:
  selector:
    matchLabels:
      component: fluentd
  template:
    metadata:
      labels:
        component: fluentd
    spec:
      serviceAccount: log-collector
      tolerations:
      - key: "node-role.kubernetes.io/control-plane"
        effect: "NoSchedule"
      initContainers:
      - name: setup
        image: fluent/fluentd-kubernetes-daemonset:v1.16.5-debian-elasticsearch7-amd64-1.0
        imagePullPolicy: IfNotPresent
        command: ["bash", "-c", "cp /config/fluent.conf /etc/fluent/conf.d/"]
        volumeMounts:
        - name: config
          mountPath: /config
      containers:
      - name: fluentd
        image: fluent/fluentd-kubernetes-daemonset:v1.16.5-debian-elasticsearch7-amd64-1.0
        imagePullPolicy: IfNotPresent
        env:
        - name: K8S_NODE_NAME
          valueFrom:
            fieldRef:
              fieldPath: spec.nodeName
        - name: FLUENT_ELASTICSEARCH_HOST
          value: "elasticsearch.logging.svc.cluster.local"
        - name: FLUENT_ELASTICSEARCH_PORT
          value: "9200"
        - name: FLUENT_ELASTICSEARCH_SCHEME
          value: "http"
        - name: FLUENTD_SYSTEMD_CONF
          value: disable
        - name: FLUENT_CONTAINER_TAIL_EXCLUDE_PATH
          value: /var/log/containers/fluent*
        resources:
          limits:
            memory: 512Mi
          requests:
            cpu: 100m
            memory: 200Mi
        volumeMounts:
        - name: varlog
          mountPath: /var/log
        - name: containerlogs
          mountPath: /var/log/containers
          readOnly: true
        - name: config
          mountPath: /etc/fluent/conf.d
      terminationGracePeriodSeconds: 30
      volumes:
      - name: varlog
        hostPath:
          path: /var/log
      - name: containerlogs
        hostPath:
          path: /var/log/containers
      - name: config
        configMap:
          name: fluentd-config

DaemonSet vs Deployment: Key Differences

Aspect Deployment DaemonSet
Pod scheduling Spread across available nodes One per node (or matching nodes)
Use case Stateless applications Infrastructure daemons
Scaling Horizontal pod autoscaling supported Scale with cluster size
Priority App availability is primary Node-level coverage is primary

Deployments excel at managing stateless services where exact node placement is irrelevant. DaemonSets are essential when specific pods must run on every node—typically for infrastructure concerns like logging, monitoring, or networking.

DaemonSet Management

Update Strategies

DaemonSet supports two update strategies:

  • RollingUpdate: Gradually replaces existing Pods with new ones based on maxUnavailable
  • OnDelete: Only replaces pods when manually deleted

Pod Communication with DaemonSets

Four approaches exist for accessing DaemonSet pods:

  1. Push mode: Pods push metrics/events to external services
  2. NodePort/HostPort: Direct access via node IP and configured port
  3. Headless Service: DNS A records for each pod instance
  4. LoadBalancer Service: Random node selection via service proxy

Taints and Tolerations

DaemonSet pods automatically receive tolerations for node conditions. Additional tolerations can be specified in the pod template when pods must run on tainted nodes (such as master nodes).

Accessing Kibana

After deployment completes, access Kibana at http://<node-ip>:30006. Configure an index pattern matching kubernetes-* to begin querying collected logs.

Tags: kubernetes daemonset Controllers EFK Fluentd

Posted on Sat, 11 Jul 2026 17:38:52 +0000 by AngelicS