Ein Kubernetes DaemonSet sorgt dafür, dass auf jeder Node deines Clusters genau ein Pod einer bestimmten Vorlage läuft. Kommt eine neue Node dazu, startet Kubernetes den Pod dort automatisch. Fällt eine Node weg, verschwindet ihr Pod mit ihr. Du gibst also keine Anzahl von Replikaten vor, sondern die Nodes selbst bestimmen die Anzahl.
Das brauchst du immer dann, wenn eine Anwendung nicht irgendwo im Cluster laufen soll, sondern überall: Log-Sammler, Monitoring-Agenten, Netzwerk-Komponenten, Security-Scanner, Storage-Treiber. Alles, was den Zustand der einzelnen Maschine beobachtet oder erweitert, gehört in ein DaemonSet.
Ich betreibe Kubernetes-Cluster in Produktion und habe das Kubernetes-Praxisbuch bei Rheinwerk geschrieben (2024). In meinen Clustern laufen DaemonSets für Metriken, Logs, Laufzeit-Sicherheit und Storage, und die meisten Fehler damit sind mir im eigenen Betrieb oder in Kundenprojekten schon begegnet. Alle meine Beiträge zu Kubernetes findest du gesammelt auf der Seite Kubernetes.
Was ein DaemonSet in Kubernetes tut
Der DaemonSet-Controller schaut sich alle Nodes im Cluster an und prüft für jede einzelne: Läuft dort schon ein Pod aus meiner Vorlage? Falls nein, erzeugt er einen. Falls ein Pod zu viel läuft, löscht er ihn. Das ist derselbe Abgleich von Soll und Ist, den du vom ReplicaSet kennst, nur dass die Zielgröße nicht in einem Feld replicas steht, sondern aus der Liste der passenden Nodes folgt.
Das Manifest sieht einem Deployment zum Verwechseln ähnlich: apiVersion: apps/v1, ein Selector, eine Pod-Vorlage. Der Unterschied liegt in kind: DaemonSet und darin, dass das Feld replicas fehlt. Genau diese Ähnlichkeit macht die Manifeste so leicht lesbar. Wer ein Deployment schreiben kann, kann auch ein DaemonSet schreiben.
Ein bekanntes Beispiel aus dem Cluster selbst: In vielen Setups läuft kube-proxy als DaemonSet, ebenso das Netzwerk-Plugin. Ohne diese Pods hätte die Node keine Verbindung zum Rest des Clusters. Das zeigt schon, welche Art von Programm in ein DaemonSet gehört: Dienste, die für die Node wichtig sind, nicht für einzelne Nutzer. Die offizielle Beschreibung findest du in der Kubernetes-Dokumentation zu DaemonSets.
Kubernetes DaemonSet vs Deployment: wann du welches nimmst
Die Frage nach dem Unterschied ist die häufigste, die ich zu dem Thema bekomme. Die kurze Antwort: Ein Deployment beantwortet die Frage „wie viele?", ein DaemonSet die Frage „wo?". Beim Deployment sagst du „drei Pods, egal auf welchen Nodes", und der Scheduler verteilt sie nach freien Ressourcen. Beim DaemonSet sagst du „ein Pod je Node", und die Verteilung steht damit fest.
|
Deployment |
DaemonSet |
| Anzahl der Pods |
replicas, von dir festgelegt |
eine je passender Node, vom Cluster festgelegt |
| Skalierung |
Replikate hoch- oder runterdrehen |
nur über die Zahl der Nodes oder den Node-Filter |
| Neue Node kommt dazu |
nichts passiert, bis der Scheduler etwas dorthin legt |
Pod wird dort sofort gestartet |
| Typische Anwendung |
Webserver, APIs, Worker |
Log-Sammler, Monitoring-Agent, CNI, CSI, Security-Agent |
| Zugriff auf die Node |
selten nötig |
oft nötig: hostPath, hostNetwork, hostPort |
| Standard-Update |
Rolling Update über Replikate |
Rolling Update Node für Node |
Ein Deployment ist die richtige Wahl für alles, was deine Anwender benutzen. Ein DaemonSet ist die richtige Wahl für alles, was der Cluster über sich selbst wissen oder auf jeder Maschine tun muss. Wenn du überlegst, eine normale Anwendung als DaemonSet zu betreiben, nur damit sie auf jeder Node läuft: Das ist fast immer ein Umweg. Ein Deployment mit Pod-Anti-Affinity verteilt Pods auch, ohne dich an die Anzahl der Nodes zu binden.
In meinen Clustern auf Hetzner Cloud sind die DaemonSets immer dieselben vier Kandidaten: der Node Exporter für Prometheus, der Log-Sammler für Loki, Falco für die Laufzeit-Überwachung und der Node-Teil des CSI-Treibers, der Volumes an die Maschine hängt. Keiner davon würde als Deployment Sinn ergeben, weil jeder genau eine Maschine betreut.
Ein DaemonSet als YAML: Beispiel mit dem Node Exporter
Das folgende Manifest rollt den Node Exporter von Prometheus auf jede Node aus. Er liest CPU, Speicher, Festplatte und Netzwerk der Maschine und stellt die Werte als Metriken bereit. Dafür braucht er Zugriff auf das Dateisystem der Node, deshalb der hostPath-Mount, und er soll unter der IP-Adresse der Node erreichbar sein, deshalb hostNetwork.
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: node-exporter
namespace: monitoring
labels:
app: node-exporter
spec:
selector:
matchLabels:
app: node-exporter
template:
metadata:
labels:
app: node-exporter
spec:
hostNetwork: true
hostPID: true
tolerations:
- key: node-role.kubernetes.io/control-plane
operator: Exists
effect: NoSchedule
containers:
- name: node-exporter
image: quay.io/prometheus/node-exporter:latest
args:
- --path.rootfs=/host
ports:
- containerPort: 9100
name: metrics
volumeMounts:
- name: root
mountPath: /host
readOnly: true
volumes:
- name: root
hostPath:
path: /
Das Tag latest steht hier nur, damit das Beispiel zeitlos bleibt. Im echten Cluster pinnst du eine feste Version, sonst kannst du beim nächsten Node-Neustart eine andere Version bekommen als auf den übrigen Nodes. Der Block tolerations sorgt dafür, dass der Pod auch auf Control-Plane-Nodes landet, dazu gleich mehr.
Nach kubectl apply -f node-exporter.yaml prüfst du das Ergebnis mit ein paar Befehlen:
kubectl get daemonset -n monitoring
kubectl get pods -n monitoring -o wide
kubectl rollout status daemonset/node-exporter -n monitoring
Die erste Ausgabe zeigt dir die Spalten DESIRED, CURRENT, READY und NODE SELECTOR. Wenn DESIRED kleiner ist als die Zahl deiner Nodes, dann schließt entweder ein Taint oder ein Node-Filter Nodes aus. Die zweite Ausgabe mit -o wide zeigt dir, auf welcher Node jeder Pod gelandet ist. Das ist der schnellste Blick, um zu sehen, ob eine Node fehlt.
Tolerations: DaemonSet auf Control-Plane-Nodes bringen
Wenn du ein DaemonSet ausrollst, solltest du dir vorher eine Frage stellen: Auf welchen Nodes soll der Pod wirklich laufen? In den meisten Cluster-Setups tragen die Control-Plane-Nodes einen Taint, der normale Pods fernhält. Ein Monitoring-Agent oder Log-Sammler soll aber gerade dort laufen, denn ohne ihn siehst du von deinen wichtigsten Maschinen nichts.
Die Lösung sind Tolerations in der Pod-Vorlage. Sie sagen dem Scheduler: Diesen Taint darf der Pod ignorieren. Für die Control-Plane sieht das so aus, mit dem älteren Schlüssel master als zweitem Eintrag, falls dein Cluster ihn noch verwendet:
tolerations:
- key: node-role.kubernetes.io/control-plane
operator: Exists
effect: NoSchedule
- key: node-role.kubernetes.io/master
operator: Exists
effect: NoSchedule
Ein Detail, das viele nicht wissen: Der DaemonSet-Controller fügt jedem Pod von sich aus einige Tolerations hinzu. Dazu gehören zum Beispiel die Taints für not-ready, unreachable, disk-pressure und memory-pressure. Der Grund ist logisch: Ein Pod, der die Node überwacht, darf nicht von der Node verdrängt werden, nur weil die Node gerade unter Last steht. Sonst verlierst du genau in dem Moment die Sicht, in dem du sie brauchst. Die vollständige Liste steht im Abschnitt zu Taints und Tolerations bei DaemonSets in der Dokumentation.
Schau dir das ruhig einmal an: kubectl get pod <name> -o yaml zeigt dir im Abschnitt tolerations mehr Einträge, als du im Manifest geschrieben hast. Wer das nicht kennt, wundert sich beim ersten Vergleich zwischen Manifest und laufendem Pod.
nodeSelector: DaemonSet nur auf bestimmten Nodes
Ein DaemonSet muss nicht auf jeder Node laufen. Mit einem nodeSelector in der Pod-Vorlage beschränkst du es auf Nodes mit bestimmten Labels. Typische Fälle: ein GPU-Treiber nur auf Nodes mit Grafikkarte, ein Storage-Agent nur auf Nodes mit lokalen Festplatten, ein Agent nur auf Linux-Nodes in einem gemischten Cluster.
spec:
template:
spec:
nodeSelector:
kubernetes.io/os: linux
node-role.example.com/storage: "true"
Der Controller berücksichtigt den Selector beim Zählen: DESIRED entspricht dann der Zahl der Nodes, die alle Labels tragen. Labelst du später eine weitere Node passend, startet der Pod dort ohne dein Zutun. Entfernst du das Label, wird der Pod beendet. Für komplexere Regeln, etwa „auf allen Nodes außer denen mit Label X", nimmst du statt nodeSelector eine nodeAffinity mit requiredDuringSchedulingIgnoredDuringExecution, die Syntax ist dieselbe wie bei jedem anderen Pod.
Aus der Praxis: Ich nutze den Selector selten, weil meine Cluster homogen sind. Wo ich ihn nutze, ist beim CSI-Treiber, der nur auf Nodes laufen soll, an die tatsächlich Volumes gehängt werden. Alles andere darf auf jede Node.
Updates, Priorität und Erreichbarkeit im Betrieb
Ein DaemonSet aktualisiert sich standardmäßig per Rolling Update: Wenn du das Image in der Vorlage änderst, tauscht der Controller die Pods Node für Node aus. Wie viele Nodes gleichzeitig ohne Pod sein dürfen, steuert maxUnavailable unter updateStrategy.rollingUpdate, der Standard ist eine Node. Die Alternative OnDelete tauscht Pods erst aus, wenn du sie von Hand löschst. Das ist sinnvoll für Komponenten, bei denen du jede Node einzeln prüfen willst, etwa ein Netzwerk-Plugin.
spec:
updateStrategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 1
Weil DaemonSet-Pods für die Node wichtiger sind als normale Anwendungs-Pods, solltest du ihnen eine höhere priorityClassName geben. Bei Speicherknappheit verdrängt Kubernetes dann zuerst die weniger wichtigen Pods, und dein Log-Sammler läuft weiter. Für Node-kritische Komponenten gibt es die eingebaute Klasse system-node-critical, für eigene Agenten lohnt sich eine eigene PriorityClass, die über den Anwendungs-Pods liegt.
Ein Punkt, der gerade bei Log-Sammlern und Metrik-Agenten gern übersehen wird: Das DaemonSet sorgt für einen Pod je Node, es begrenzt aber nicht, was dieser Pod einsammelt. Steht in der Konfiguration des Agenten eine clusterweite Suche nach Zielen, entdeckt jede Instanz dieselben Ziele, und du bekommst dieselben Logs oder Metriken so oft, wie du Nodes hast. Die Einschränkung auf die eigene Node gehört in die Konfiguration des Agenten, üblicherweise über einen Filter auf den Node-Namen, den du dem Pod per Downward API aus spec.nodeName als Umgebungsvariable mitgibst.
Bleibt die Frage, wie andere Dienste einen DaemonSet-Pod erreichen. Vier Muster kommen vor. Push: Der Pod schickt seine Daten selbst weg, wie ein Log-Sammler, der an Loki liefert, und ist von außen gar nicht erreichbar. Node-IP mit hostPort oder hostNetwork: Der Pod ist unter der Adresse der Node erreichbar, so holt Prometheus die Metriken beim Node Exporter ab. Service: geht wie bei jeder Anwendung, allerdings verteilt der Service die Anfragen, du triffst also nicht gezielt den Pod einer bestimmten Node. Headless Service: liefert dir über DNS die Adressen aller Pods, wenn du sie einzeln ansprechen willst.
Eine ehrliche Alternative gibt es auch: Du könntest die Agenten per systemd direkt auf den Nodes installieren. Dann verlierst du aber Logs, Metriken, Rolling Updates und das YAML-Manifest, mit dem der Cluster den Zustand selbst herstellt. In meinen Projekten hat das DaemonSet deshalb jedes Mal gewonnen, auch bei Software, die eigentlich für den Host gedacht ist.
Häufige Fragen
Was ist der Unterschied zwischen DaemonSet und Deployment?
Ein Deployment betreibt eine von dir festgelegte Anzahl Pods irgendwo im Cluster. Ein DaemonSet betreibt genau einen Pod auf jeder passenden Node, und die Anzahl folgt aus der Zahl der Nodes. Deployments sind für Anwendungen, DaemonSets für Dienste, die jede Maschine einzeln betreuen.
Wie beschränke ich ein DaemonSet auf bestimmte Nodes?
Mit einem nodeSelector oder einer nodeAffinity in der Pod-Vorlage. Der Controller startet dann nur auf Nodes, deren Labels passen, und passt die Zahl der Pods automatisch an, wenn du Labels setzt oder entfernst.
Warum läuft mein DaemonSet nicht auf den Control-Plane-Nodes?
Weil diese Nodes in den meisten Setups einen Taint mit dem Effekt NoSchedule tragen. Trag in der Pod-Vorlage eine Toleration für node-role.kubernetes.io/control-plane ein, dann landet der Pod auch dort. Prüfe mit kubectl describe node, welche Taints deine Nodes tatsächlich haben.
Kann ich ein DaemonSet skalieren?
Nicht über eine Replikatzahl. Ein DaemonSet hat pro Node höchstens einen Pod. Mehr Pods bekommst du nur über mehr Nodes, weniger über einen engeren Node-Filter. Wenn du mehrere Pods pro Node brauchst, ist das DaemonSet das falsche Objekt.
Wie aktualisiere ich ein DaemonSet ohne Ausfall?
Mit der Standardstrategie RollingUpdate und maxUnavailable: 1 tauscht Kubernetes die Pods Node für Node aus. Auf jeder Node fehlt der Agent für die Dauer eines Pod-Neustarts, mehr nicht. Mit kubectl rollout status daemonset/<name> beobachtest du den Fortschritt, mit kubectl rollout undo gehst du zurück.
Wie du weitermachst
Die beiden größten Anwendungsfälle für DaemonSets sind Monitoring und Logging. Wie der Node Exporter aus dem Beispiel oben zusammen mit Prometheus und Grafana zu einem vollständigen Überwachungs-Setup wird, zeige ich in Kubernetes Monitoring mit Prometheus. Wie ein Log-Sammler als DaemonSet die Container-Logs jeder Node einsammelt und an Loki liefert, steht in Kubernetes Logging mit Loki und Grafana. Und wenn dir der Unterschied zum Deployment noch nicht ganz klar ist, lies zuerst Kubernetes Deployment: Rollout erklärt.
Der praktische nächste Schritt: Nimm das Node-Exporter-Manifest von oben, roll es in deinem Cluster aus und vergleiche kubectl get nodes mit kubectl get daemonset. Wenn die Zahlen nicht übereinstimmen, weißt du jetzt, wo du nach Taints und Labels suchst.
Ausführlich mit allen Beispielen, vom ersten DaemonSet bis zur PriorityClass, steht das in Kapitel 5 meines Kubernetes-Praxisbuchs bei Rheinwerk.