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:
- Push mode: Pods push metrics/events to external services
- NodePort/HostPort: Direct access via node IP and configured port
- Headless Service: DNS A records for each pod instance
- 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.