DaemonSet: один Pod на каждом узле
ReplicaSet и Deployment отвечают на вопрос “сколько Pod-ов мне нужно?”. DaemonSet отвечает на другой вопрос: “сколько узлов в кластере?”. Каждому Node — по своему Pod-у. Это базовый паттерн для node-level workloads: логирование, мониторинг, CNI plugins, CSI drivers — всё, что должно жить на каждом узле кластера.
DaemonSet — старый и стабильный API, но в нём есть несколько важных нюансов: tolerations, scheduler integration, и взаимодействие с node lifecycle.
systemd: units, targets и система инициализации Linux
Что такое DaemonSet
DaemonSet — это API-объект apps/v1, который гарантирует, что на каждом узле, удовлетворяющем spec.template.spec.nodeSelector / spec.template.spec.affinity, работает ровно один Pod из этого DaemonSet-а.
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: fluentbit
namespace: logging
labels:
app: fluentbit
spec:
selector:
matchLabels:
app: fluentbit
template:
metadata:
labels:
app: fluentbit
spec:
tolerations:
- operator: Exists
containers:
- name: fluentbit
image: fluent/fluent-bit:3.0
volumeMounts:
- name: varlog
mountPath: /var/log
readOnly: true
- name: containerlogs
mountPath: /var/lib/docker/containers
readOnly: true
volumes:
- name: varlog
hostPath:
path: /var/log
- name: containerlogs
hostPath:
path: /var/lib/docker/containers
Поля очень похожи на ReplicaSet/Deployment, но НЕТ поля replicas. Число Pod-ов = число подходящих узлов в кластере.
DaemonSet существует в namespace, но Pod-ы он создаёт по одному на узел. Имена Pod-ов: <ds-name>-<random>. У каждого Pod-а в .spec.nodeName заданна нода, на которой он живёт — это назначение делается DaemonSetController-ом (или scheduler-ом — см. ниже).
Use cases
DaemonSet — фундаментальный паттерн для системных сервисов кластера.
Общая черта всех use cases: workload физически привязан к ноде — либо потому что использует hostPath, либо hostNetwork, либо потому что должен видеть процессы/девайсы конкретного узла.
Reconcile loop: как DaemonSetController работает
1. watch DaemonSets и Nodes
2. для каждого DaemonSet:
- найти все Nodes, удовлетворяющие nodeSelector/affinity
- для каждой такой Node:
- есть ли уже Pod от этого DS на этой Node?
- если нет — создать Pod с .spec.nodeName=<node>
- для Pods на нодах, которые перестали удовлетворять selector:
- удалить
Scheduler integration с v1.17
Раньше DaemonSetController создавал Pod-ы с уже выставленным .spec.nodeName — обходя scheduler. Это работало, но игнорировало pod affinity, preemption, и другие scheduling features.
С Kubernetes v1.17 (feature ScheduleDaemonSetPods, GA) DaemonSet идёт через обычный scheduler:
- Controller создаёт Pod БЕЗ
nodeName, но сnodeAffinityна конкретную ноду - Scheduler видит Pod, прогоняет его через filtering/scoring, биндит на ноду
- Это даёт DS преимущества: respect preemption, taint priority, resource quotas
Практическое следствие: DS-Pod может получить статус Pending если на ноде нет ресурсов. Controller не “силой” его запустит. Поэтому DS должен иметь умеренные resource requests, иначе застрянет на узлах с насыщенной нагрузкой.
Tolerations: запуск на control plane и tainted nodes
Большинство DaemonSets должны работать на всех нодах, включая control plane и любые tainted ноды.
Control plane ноды по умолчанию имеют taint:
node-role.kubernetes.io/control-plane:NoSchedule
Без подходящего toleration DaemonSet НЕ создаст Pod на этих нодах — а это критично для мониторинга и CNI.
spec:
template:
spec:
tolerations:
- key: node-role.kubernetes.io/control-plane
operator: Exists
effect: NoSchedule
ToleratesAllTaints: критические DaemonSets
Некоторые DaemonSets (особенно CNI plugins) должны стартовать до того как нода станет ready — иначе нода никогда не получит сеть. Для этого используют максимально широкий toleration:
tolerations:
- operator: Exists
Это значит “толерирую любой taint с любым key, effect, value”. Такие Pods запускаются на нодах с таинтами вроде:
node.kubernetes.io/not-ready:NoSchedule— нода ещё не readynode.kubernetes.io/unreachable:NoExecute— нода недоступна, обычные Pods выселяютсяnode.kubernetes.io/disk-pressure:NoSchedule— давление на диск
hostPath и hostNetwork
DaemonSets часто используют host-level ресурсы: файлы и сокеты на ноде, network namespace ноды.
hostPath
volumes:
- name: varlog
hostPath:
path: /var/log
type: Directory
Это монтирует /var/log НОДЫ внутрь контейнера. Используется для:
- Логирование: читать
/var/log/pods,/var/log/containers - Мониторинг: читать
/proc,/sysхоста для node metrics - Storage drivers: писать в
/var/lib/kubelet/plugins/<driver>для регистрации
hostPath — это огромный security risk: контейнер может читать/писать в файловую систему хоста. PodSecurityStandard baseline и restricted запрещают большинство hostPath. В production hostPath разрешён только для системных DaemonSets, которые ему действительно нужны.
hostNetwork
spec:
template:
spec:
hostNetwork: true
dnsPolicy: ClusterFirstWithHostNet
Pod использует network namespace ноды напрямую. Что это даёт:
- Видит все интерфейсы ноды (eth0, weave, calico, …)
- Биндится на порты хоста напрямую (если контейнер слушает :9100 — это :9100 хоста)
- Может слушать pod traffic — полезно для CNI и observability
dnsPolicy: ClusterFirstWithHostNet нужен потому что иначе Pod использовал бы resolv.conf хоста и не видел бы cluster DNS.
Примеры: kube-proxy (нужен доступ к iptables хоста), node-exporter (слушает :9100 хоста), calico-node (программирует routing хоста).
Update strategy: RollingUpdate и OnDelete
DaemonSet поддерживает две стратегии обновления.
spec:
updateStrategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 1
maxSurge: 0
RollingUpdate (default)
Controller обновляет Pods по одному (по умолчанию maxUnavailable: 1). Удаляет старый Pod на ноде, создаёт новый, ждёт ready, переходит к следующей ноде.
Параметры:
maxUnavailable(default 1) — сколько Pods могут быть не ready одновременно. Можно процент.maxSurge(с v1.21 GA) — сколько ДОПОЛНИТЕЛЬНЫХ Pods создать на ноде во время обновления. Если 1 — на короткое время на ноде будет два Pod-а (старый и новый). Дефолт 0 — для DaemonSets это редкий выбор.
OnDelete
Controller НЕ обновляет Pods автоматически после изменения template. Чтобы применить обновление, нужно вручную удалить старый Pod на ноде — controller создаст новый с новым template.
Use case: critical DaemonSets, где обновление должно быть строго контролируемым. Например, CNI: автоматическое обновление CNI на ноде может убить сеть всех Pods на этой ноде — лучше делать вручную в окно maintenance.
# Изменили DS, но Pods не обновились — стратегия OnDelete
kubectl rollout status ds/calico-node
# DaemonSet "calico-node" is not currently rolling out — using OnDelete strategy
# Применить обновление на одной ноде
kubectl delete pod -n kube-system <calico-pod-on-node-1>
# Controller создаст новый с новым template
Trimming: nodeSelector и nodeAffinity
DaemonSet может запускаться не на ВСЕХ нодах, а только на подмножестве — через nodeSelector или affinity.
spec:
template:
spec:
nodeSelector:
workload-type: gpu
Это запускает Pods только на нодах с label workload-type=gpu. Когда:
- Нода с этим label добавляется в кластер — controller создаёт Pod на ней
- Label убирается с ноды — controller удаляет Pod с неё
Более сложная фильтрация — через nodeAffinity:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/arch
operator: In
values: ["amd64"]
- key: node-role.kubernetes.io/control-plane
operator: DoesNotExist
kubectl: команды
# Создать
kubectl apply -f ds.yaml
# Список
kubectl get ds
# NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE
# fluentbit 5 5 5 5 5 <none> 1h
# Список Pods с распределением по нодам
kubectl get pods -l app=fluentbit -o wide
# Описать
kubectl describe ds/fluentbit
# Rollout (только при RollingUpdate strategy)
kubectl rollout status ds/fluentbit
kubectl rollout history ds/fluentbit
kubectl rollout undo ds/fluentbit
# Удалить (cascade — все Pods на всех нодах)
kubectl delete ds/fluentbit
В kubectl get ds колонки:
- DESIRED — сколько нод подходят под selector (если nodeSelector пустой — это все нода)
- CURRENT — сколько Pods создано controller-ом
- READY — сколько из них Ready
- UP-TO-DATE — сколько Pods с текущим template (важно во время rolling update)
- AVAILABLE — сколько Ready + старше minReadySeconds