DaemonSet: агент на каждом узле

DaemonSet гарантирует ровно один под на каждом узле — это способ запускать фоновых агентов: сбор логов, метрик, сетевые плагины.

DaemonSet — контроллер, который держит по одному поду на каждом подходящем узле кластера. Добавили узел — под появился автоматически; вывели узел — под исчез вместе с ним.

Deployment и StatefulSet отвечают на вопрос «сколько реплик приложения мне нужно». DaemonSet отвечает на другой: «что должно работать на каждой машине». Это принципиально иная модель размещения. Вы не задаёте число подов — оно равно числу узлов и меняется само, когда кластер растёт или сжимается.

Такая модель нужна инфраструктурным агентам, которые обслуживают сам узел, а не пользовательский трафик: сборщику логов, который читает файлы контейнеров на диске узла; экспортёру метрик, снимающему загрузку CPU и памяти конкретной машины; сетевому плагину (CNI), настраивающему маршруты; агенту безопасности. Всем им важно присутствовать везде и иметь доступ к ресурсам своего узла.

Зачем это на практике

Классический пример — сбор логов. Контейнеры пишут stdout в файлы на диске узла (обычно под /var/log/). Чтобы собрать эти логи централизованно, на каждой машине должен крутиться агент (Fluent Bit, Vector, Promtail), который читает локальные файлы и отправляет их в хранилище. Запускать его Deployment-ом бессмысленно: три реплики на пяти узлах оставят два узла без сборщика, а сам Kubernetes волен ставить реплики куда угодно. DaemonSet же гарантирует: на каждом узле — ровно один сборщик, и новый узел получит его автоматически.

Типичные сценарии для DaemonSet:

  • Логи: Fluent Bit, Fluentd, Vector, Promtail читают логи всех контейнеров узла.
  • Мониторинг: node-exporter (Prometheus) снимает метрики «железа» каждого узла.
  • Сеть: CNI-плагины (Calico, Cilium) и kube-proxy настраивают сеть на узлах.
  • Хранилище и безопасность: агенты CSI, сканеры уязвимостей, средства аудита.

Манифест и доступ к ресурсам узла

Манифест похож на Deployment, но kind: DaemonSet и нет поля replicas — число подов определяется числом узлов. Агенту обычно нужен hostPath-том, чтобы читать файлы прямо с диска узла:

apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: fluent-bit
  namespace: logging
spec:
  selector:
    matchLabels:
      app: fluent-bit
  template:
    metadata:
      labels:
        app: fluent-bit
    spec:
      containers:
        - name: fluent-bit
          image: fluent/fluent-bit:3.0
          volumeMounts:
            - name: varlog
              mountPath: /var/log
              readOnly: true
      volumes:
        - name: varlog
          hostPath:
            path: /var/log        # читаем логи с диска самого узла

Проверяем: подов столько же, сколько узлов, и они «размазаны» по разным машинам.

kubectl apply -f fluent-bit.yaml

# Колонки DESIRED и CURRENT равны числу узлов
kubectl get daemonset -n logging
# NAME         DESIRED   CURRENT   READY   NODE SELECTOR
# fluent-bit   3         3         3       <none>

# По одному поду на узел — смотрим распределение
kubectl get pods -n logging -o wide

Запуск только на части узлов

Иногда агент нужен не везде, а на определённых узлах — например, только на тех, где есть GPU. Это задаётся через nodeSelector по меткам узлов: DaemonSet поставит поды лишь туда, где метка совпала.

    spec:
      nodeSelector:
        hardware: gpu        # под только на узлах с этой меткой

Чтобы запускаться и на управляющих узлах (control-plane), которые по умолчанию закрыты taint-ом, DaemonSet добавляет соответствующий toleration — поэтому многие системные агенты есть и на мастерах.

Как это работает под капотом

Контроллер DaemonSet постоянно сравнивает список узлов кластера со списком уже запущенных подов. Алгоритм прост: для каждого узла, подходящего по nodeSelector, affinity и taints/tolerations, должен существовать под — если его нет, контроллер его создаёт. Поды DaemonSet жёстко привязаны к своему узлу: планировщик не «размазывает» их, а контроллер сразу проставляет целевой узел.

Когда в кластер добавляется новый узел, контроллер замечает его и создаёт под автоматически — без вашего вмешательства. Когда узел выводится из кластера, его под исчезает вместе с ним и нигде не пересоздаётся, потому что под был привязан именно к этой машине. Поэтому у DaemonSet не бывает «лишних» реплик: их число всегда равно числу подходящих узлов.

Обновление образа в DaemonSet идёт по стратегии RollingUpdate (по умолчанию), но с поправкой на «один под на узел»: за раз обновляется ограниченное число узлов, которым управляет параметр maxUnavailable (по умолчанию 1). Контроллер удаляет старый под на узле, дожидается готовности нового и переходит к следующему узлу. Есть и стратегия OnDelete — тогда новый образ применяется к узлу только после ручного удаления его пода.

Частые ошибки

  • Указание replicas в DaemonSet. Поля нет: число подов задаётся числом узлов. Попытка «масштабировать» DaemonSet через replicas ничего не даст.
  • Использование Deployment там, где нужен агент на каждом узле. Deployment не гарантирует присутствие на всех узлах и не среагирует на добавление нового — часть машин останется без агента.
  • Забытый toleration для control-plane. Если агент должен мониторить и управляющие узлы, без подходящего toleration он туда не попадёт из-за taint-ов.
  • Чрезмерные права и ресурсы. Агент работает на каждом узле, поэтому утечка памяти или жадные лимиты бьют по всему кластеру разом. Для DaemonSet особенно важно выставлять аккуратные requests/limits.

Итоги

  • DaemonSet держит по одному поду на каждом подходящем узле; число подов равно числу узлов.
  • Новый узел получает под автоматически, выведенный — теряет его вместе с собой.
  • Применяется для логов, метрик узла, сетевых плагинов и других инфраструктурных агентов.
  • В отличие от Deployment, не имеет replicas и гарантирует присутствие на каждом узле, а не заданное число копий.
  • Ограничить узлы можно через nodeSelector/affinity; обновление — RollingUpdate с maxUnavailable или OnDelete.
Проверьте себя
1. Сколько подов запускает DaemonSet?
AРовно столько, сколько указано в поле replicas
BПо одному на каждый подходящий узел кластера
CВсегда ровно три, по числу зон доступности
DОдин на весь кластер
2. В каком случае DaemonSet подходит лучше, чем Deployment?
AДля веб-приложения, которому нужно 5 одинаковых реплик за балансировщиком
BДля агента сбора логов или метрик, который должен работать на каждом узле
CДля базы данных с упорядоченным запуском подов
DДля разовой задачи обработки данных, которая завершается
3. Что произойдёт, когда в кластер добавят новый узел при работающем DaemonSet?
AНичего, пока вручную не увеличить replicas
BКонтроллер автоматически создаст на новом узле под DaemonSet
CВсе существующие поды перезапустятся
DDaemonSet удалит под со старого узла и перенесёт на новый