Scheduler und Controller Manager
Wie Pods auf Nodes geplant und der Desired State durchgesetzt wird.
Lernziele
Nach dieser Lektion kannst du erklären, wie der Scheduler in den Phasen Filtering, Scoring und Binding einen Node für jeden Pod auswählt und wie der Controller Manager mit Control Loops den Desired State durchsetzt. Du kannst einen Pending-Pod systematisch debuggen, kennst die Rolle des Node Controllers beim Umgang mit ausgefallenen Nodes und weißt, wofür das Scheduler Framework mit seinen Plugins gut ist.
Das Problem: Wer entscheidet, und wer räumt hinterher auf?
Aus Lektion 2 weißt du: Nach kubectl apply liegt ein Deployment-Objekt in etcd — mehr nicht. Es laufen keine Container, es ist kein Node ausgewählt. Zwei Fragen sind offen: Wer übersetzt „ich will 3 Replicas” in konkrete Pod-Objekte und hält diese Zahl dauerhaft ein, auch wenn Pods sterben? Und wer entscheidet, auf welchem der vielleicht 50 Nodes mit unterschiedlich viel freiem Speicher, GPUs und Zonen-Zugehörigkeit jeder Pod laufen soll?
Kubernetes trennt diese beiden Jobs sauber: Der Controller Manager sorgt dafür, dass die richtigen Objekte existieren. Der Scheduler sorgt dafür, dass jeder Pod einen passenden Platz bekommt. Beide arbeiten ausschließlich über die API — sie kennen sich nicht und brauchen sich nicht zu kennen.
Control Loops: das Reconciliation-Muster konkret
Der kube-controller-manager ist ein einzelner Prozess, in dem Dutzende Controller laufen. Jeder folgt demselben Muster, das du aus Lektion 1 als Prinzip kennst und hier als Mechanik siehst:
beobachte Objekte (Watch) → vergleiche Desired mit Actual State → korrigiere → wiederhole
Die wichtigsten Vertreter:
- Deployment Controller: sieht ein Deployment, erstellt/verwaltet ReplicaSets. Bei einem Image-Update erstellt er ein neues ReplicaSet und skaliert das alte herunter — die Mechanik hinter Rolling Updates (Lektion 7).
- ReplicaSet Controller: zählt. Desired 3, Actual 2? Er erstellt ein Pod-Objekt. Actual 4? Er löscht eins. Mehr ist es nicht — und genau diese Schlichtheit macht Self-Healing zuverlässig.
- Node Controller: überwacht die Heartbeats der Nodes (über Lease-Objekte). Bleibt ein Node stumm, markiert er ihn nach ca. 40 Sekunden als
NotReady; nach weiteren rund 5 Minuten (default Tolerationnode.kubernetes.io/not-ready) werden die Pods evicted und — sofern ein Controller wie ReplicaSet dahintersteht — auf anderen Nodes neu erstellt. Diese bewusste Trägheit verhindert, dass ein kurzer Netzwerk-Schluckauf eine Massenumsiedlung auslöst.
Wichtig fürs Verständnis: Controller reagieren auf Zustand, nicht auf Ereignisse. Ein abgestürzter Controller verpasst nichts — beim Neustart liest er den aktuellen Zustand und gleicht ab. Das heißt Level-Triggered statt Edge-Triggered und ist der Grund, warum Kubernetes Ausfälle seiner eigenen Komponenten so gut wegsteckt.
Der Scheduler: Filtering, Scoring, Binding
Der kube-scheduler beobachtet Pods, die noch keinem Node zugewiesen sind (spec.nodeName leer), und durchläuft für jeden drei Phasen:
Filtering: Alle Nodes raus, auf denen der Pod nicht laufen kann. Klassische Filter: zu wenig freie CPU/Memory gemessen an den Requests des Pods (nicht am tatsächlichen Verbrauch — wichtig für Lektion 10), nicht tolerierte Taints, nicht erfüllte Node-Affinity, fehlende Ports oder Volumes in der falschen Zone. Übrig bleiben die feasible Nodes.
Scoring: Die verbleibenden Nodes bekommen Punkte. Kriterien sind etwa gleichmäßige Ressourcenauslastung, ob das Image schon lokal vorhanden ist, und Topology-Spread-Vorgaben. Der Node mit der höchsten Summe gewinnt.
Binding: Der Scheduler schreibt die Entscheidung als Binding-Objekt in die API — spec.nodeName wird gesetzt. Das war’s. Den Container startet das kubelet (Lektion 4), nicht der Scheduler.
Finden die Filter keinen einzigen tauglichen Node, bleibt der Pod Pending — der mit Abstand häufigste Scheduler-„Fehler” in der Praxis, und fast immer kein Fehler des Schedulers, sondern eine unerfüllbare Anforderung. Eine Nuance für Senior-Gespräche: Preemption — ein Pending-Pod mit hoher PriorityClass kann niedriger priorisierte Pods von einem Node verdrängen.
Seit der Scheduler auf dem Scheduler Framework basiert, sind alle diese Phasen Plugin-Punkte (Filter, Score, Bind, …). Eigene Plugins oder ein zweiter Scheduler-Deployment mit eigenem schedulerName decken Spezialfälle ab — Batch-Scheduling für ML-Workloads ist das übliche Beispiel. Für die allermeisten Plattformen gilt aber ehrlich: Der Default-Scheduler, gut konfiguriert, reicht.
Pending-Pods debuggen: der Standardfall
Weil „Pod hängt in Pending” die häufigste Troubleshooting-Frage zu diesem Thema ist, hier der Weg, den du auswendig können solltest:
kubectl describe pod <name>
Am Ende der Ausgabe stehen die Events, und der Scheduler schreibt dort erstaunlich präzise hinein, woran es liegt:
0/3 nodes are available: 1 node(s) had untolerated taint
{node-role.kubernetes.io/control-plane: }, 2 Insufficient cpu.
Diese eine Zeile sagt dir: Ein Node ist Control Plane (Taint), zwei haben zu wenig anforderbare CPU. Die Lösung ist dann fachlich: Requests senken, Nodes vergrößern, Cluster Autoscaler — aber nie „Scheduler neu starten”.
Praxis: Scheduling sichtbar machen
kind create cluster --name schedlab --config - <<EOF
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
- role: worker
- role: worker
EOF
# 1. Self-Healing des ReplicaSet Controllers erleben
kubectl create deployment web --image=nginx --replicas=3
kubectl get pods -o wide
kubectl delete pod $(kubectl get pods -l app=web -o name | head -1)
kubectl get pods # sofort ist Ersatz da — anderer Name, gleiche Anzahl
# 2. Einen Pending-Pod provozieren: absurd hohe CPU-Anforderung
kubectl run greedy --image=nginx \
--overrides='{"spec":{"containers":[{"name":"greedy","image":"nginx",
"resources":{"requests":{"cpu":"100"}}}]}}'
kubectl get pod greedy # STATUS: Pending
kubectl describe pod greedy | tail -5 # Events: Insufficient cpu
# 3. Scheduling per Taint verhindern und wieder erlauben
kubectl taint nodes schedlab-worker dedicated=ml:NoSchedule
kubectl run tainttest --image=nginx
kubectl get pod tainttest -o wide # landet auf worker2
kubectl taint nodes schedlab-worker dedicated=ml:NoSchedule-
Aufräumen mit kind delete cluster --name schedlab.
Typische Stolperfallen
Scheduling auf Basis von Requests, nicht Realität: Der Scheduler rechnet mit den deklarierten Requests. Ein Node kann real zu 20 % ausgelastet sein und trotzdem „voll” für den Scheduler, weil die Summe der Requests sein Kontingent erschöpft — und umgekehrt. Überzogene Requests verschwenden so still Cluster-Kapazität.
„Der Pod ist weg und kommt nicht wieder”: Ein nackter Pod (ohne Deployment/ReplicaSet) wird nach dem Löschen von niemandem neu erstellt — es gibt keinen Controller, der einen Desired State durchsetzt. Self-Healing ist eine Controller-Eigenschaft, keine Pod-Eigenschaft.
Node-Ausfall: zu schnelle Erwartung: Zwischen Node-Ausfall und Pod-Neustart auf anderen Nodes liegen per Default mehrere Minuten (NotReady-Erkennung plus Eviction-Toleration). Wer im Interview behauptet, das passiere „sofort”, kennt den Mechanismus nicht; wer die Trade-offs der Verkürzung benennt (Flapping-Gefahr), punktet.
Scheduler-Restart als Heilmittel: Ein Pending-Pod liegt praktisch nie am Scheduler-Prozess. Die Events in kubectl describe nennen die Ursache — dort anfangen, nicht bei den Control-Plane-Pods.
Interview-Vorbereitung
Auf „Wie funktioniert der Kubernetes Scheduler?” antwortest du mit dem Dreischritt: Filtering (harte Ausschlusskriterien: Requests vs. Allocatable, Taints, Affinity) → Scoring (Ranking der tauglichen Nodes) → Binding (nur spec.nodeName setzen — Start macht das kubelet). Beim Controller Manager führst du das Control-Loop-Muster an einem konkreten Beispiel vor (ReplicaSet Controller: zählen und korrigieren) und nennst Level-Triggered Reconciliation als Robustheits-Argument.
Typische Follow-ups:
- „Pod hängt in Pending — Vorgehen?” —
kubectl describe pod, Events lesen; typisch: Insufficient cpu/memory, untolerated Taints, Affinity/Volume-Konflikte. - „Was passiert, wenn ein Node ausfällt?” — Node Controller setzt NotReady (~40 s), nach ~5 min Eviction; Controller erstellen Ersatz-Pods, der Scheduler platziert sie neu.
- „Deployment vs. ReplicaSet Controller — wer macht was?” — Deployment Controller managt ReplicaSets (Rollout-Logik), ReplicaSet Controller hält die Replica-Zahl.
- „Wann ein Custom Scheduler?” — Selten; Scheduler Framework Plugins oder zweiter Scheduler für Spezialfälle wie Gang-Scheduling, sonst Default behalten.
Zusammenfassung
Der Controller Manager setzt mit seinen Control Loops durch, dass die richtigen Pods existieren — Deployment und ReplicaSet Controller für Workloads, der Node Controller für die Reaktion auf Node-Ausfälle, alle nach dem Muster „beobachten, vergleichen, korrigieren”. Der Scheduler entscheidet, wo jeder Pod läuft: Filtering nach harten Kriterien, Scoring der Kandidaten, Binding als reiner API-Eintrag. Pending-Pods debuggst du über die Events, nicht über die Control Plane.
Damit ist die Entscheidungsseite komplett. Die nächste Lektion wechselt auf den Worker Node: Wie das kubelet aus der PodSpec über CRI echte Container macht und wie kube-proxy Service-IPs in Netzwerkregeln übersetzt.