Wartungsarbeiten an Nodes gehören zum Betriebsalltag in Kubernetes. Pods werden evicted, neu verteilt und ersetzt — und bei richtiger Konfiguration merken Nutzer davon kaum etwas.
Genau dafür gibt es PodDisruptionBudgets (PDBs). Sie sollen verhindern, dass freiwillige Unterbrechungen wie ein Node-Drain zu viele Replikate derselben Workload gleichzeitig aus dem Verkehr ziehen.
Aber was passiert, wenn das PDB exakt so arbeitet, wie es konfiguriert ist, und die Nutzer trotzdem einen Ausfall erleben?
Nehmen wir an, ein Produktivservice läuft mit 3 Replikaten. Ein Node muss gepatcht werden, also startet das Plattform-Team einen routinemäßigen Drain. Das PDB erlaubt, dass 1 Pod evicted wird, 2 gesunde Replikate bleiben übrig, und ein Ersatz-Pod startet auf einem anderen Node.
Sieht alles korrekt aus. Und trotzdem liefert der Service für einige Sekunden 502 Bad Gateway zurück.
Das PDB war korrekt konfiguriert. Warum passiert das trotzdem?
Diese Frage hat mich dazu gebracht, mir PDBs und das größere Bild rund um Verfügbarkeit in Kubernetes genauer anzuschauen.
Das unterstreicht einen wichtigen Punkt: Die nötige Anzahl an Pods am Leben zu halten ist nur ein Teil davon, eine Anwendung verfügbar zu halten.
Was sichert ein PDB also tatsächlich zu, und wo liegen seine Grenzen? Was passiert mit dem Traffic, während ein Pod verschwindet? Und was braucht eine Anwendung darüber hinaus, um Unterbrechungen sicher zu überstehen?
Fangen wir mit einem einfachen Wartungsszenario an und setzen die Teile zusammen.
Das Wartungsszenario
Das Plattform-Team patcht im monatlichen Security-Wartungsfenster eine Reihe von Worker-Nodes. critical-service läuft mit drei Replikaten und ist so geschützt:
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: critical-service
namespace: critical
spec:
minAvailable: 2
selector:
matchLabels:
app: critical-service
Mit minAvailable: 2 darf Kubernetes freiwillig ein Replikat evicten, aber kein zweites, solange der Ersatz-Pod nicht wieder läuft und als gesund gezählt wird.
Das PDB hat also genau das getan, wofür es konfiguriert war: Während des gesamten Drains blieben zwei gesunde Replikate verfügbar. Trotzdem antwortete der Service mit 502 Bad Gateway.
Um zu verstehen, warum, müssen wir uns ansehen, was ein PDB tatsächlich garantiert — und genauso wichtig: was nicht.
Die Grenzen eines PDB
Ein PDB legt fest, wie viele Pods einer Workload bei geplanten Unterbrechungen mindestens verfügbar bleiben müssen.
Dafür gibt es zwei Varianten:
minAvailable— die Mindestanzahl (oder der Mindestanteil) an Pods, die jederzeit gesund bleiben müssen.maxUnavailable— die maximale Anzahl (oder der maximale Anteil) an Pods, die gleichzeitig ausfallen dürfen.
Den aktuellen Stand des Budgets zeigt Kubernetes so:
kubectl get pdb -n critical
Zum Beispiel:
NAME MIN AVAILABLE MAX UNAVAILABLE ALLOWED DISRUPTIONS AGE
critical-service 2 N/A 1 14d
Das wichtige Feld ist disruptionsAllowed: wie viel Spielraum für freiwillige Unterbrechungen aktuell noch übrig ist.
Bei drei gesunden Replikaten und minAvailable: 2 darf nur ein Pod gleichzeitig unterbrochen werden. Wird ein einzelner Pod evicted, bleiben immer noch zwei verfügbar. Ist aber aus irgendeinem Grund schon ein Replikat nicht verfügbar, liegt die Zahl der gesunden Replikate bei zwei, und disruptionsAllowed fällt auf 0. Die nächste Eviction muss dann warten. Deshalb ist der PDB-Status im Betrieb so nützlich: disruptionsAllowed ändert sich mit dem Gesundheitszustand der Workload, auch wenn gerade überhaupt keine Unterbrechung läuft.
Ein Pod kann auch durch eine ungeplante Unterbrechung verschwinden, etwa durch den Ausfall eines Nodes oder einer VM, durch Ressourcendruck oder weil der Cloud-Anbieter eine Spot-Instanz zurückfordert. Solche Ereignisse kann ein PDB nicht verhindern, aber das fehlende Replikat verkleinert den Spielraum für geplante Unterbrechungen.
Nehmen wir an, pod-c verschwindet von selbst:
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
pod-a 1/1 Running 0 2d
pod-b 1/1 Running 0 2d
pod-c 0/1 Unknown 0 2d
Wir haben weiterhin zwei gesunde Replikate, aber disruptionsAllowed steht jetzt schon auf 0.
Ein später gestarteter Drain wird damit einfach blockiert.
Es gibt noch eine wichtige Grenze dessen, was ein PDB steuert: Er greift nur bei geplanten Evictions.
Wer einen Pod zum Beispiel direkt löscht:
kubectl delete pod pod-a
nutzt die Eviction-API nicht, also wird das PDB gar nicht befragt. Dasselbe gilt, wenn kubectl drain explizit so konfiguriert wird, dass es Eviction umgeht:
kubectl drain <node> --disable-eviction
Ein PDB begrenzt also, wie viele Pods gleichzeitig durch geplante Evictions entfernt werden dürfen. Es verhindert nicht, dass ein Pod auf anderem Weg gelöscht wird.
Und selbst wenn das PDB eingehalten wird, steuert es nur die Anzahl der Pods, die unterbrochen werden dürfen. Über die Requests, die diese Pods bedienen, sagt es nichts aus, und auch nicht darüber, was mit dem Traffic passiert, während ein Pod beendet wird.
Damit sind wir bei der nächsten Frage:
Was passiert eigentlich mit dem Traffic, wenn ein Pod anfängt zu terminieren?
Was passiert, wenn ein Pod terminiert
Zurück zu den 3 Replikaten: pod-c wird evicted, pod-a und pod-b laufen weiter, und minAvailable: 2 ist die ganze Zeit erfüllt. Aus Sicht des PDB ist nichts schiefgelaufen.
Aber pod-c hatte beim Evicten möglicherweise noch laufende Requests oder offene Verbindungen.
Ein PDB garantiert nicht, dass:
- aktive Requests erfolgreich abgeschlossen werden
- die Anwendung keine neue Arbeit mehr annimmt
- Traffic-Komponenten die geänderten Endpoints sofort mitbekommen
- bestehende Verbindungen sauber auslaufen
- die restlichen Replikate den zusätzlichen Traffic sofort aufnehmen können
Das zeigt den Unterschied zwischen Pod-Verfügbarkeit und Anwendungs-Verfügbarkeit: Es können genug Pods gesund bleiben, während Requests weiterhin fehlschlagen. Und genau daraus können unsere 502er entstehen.
Wenn der Pod anfängt zu terminieren, sieht der Traffic-Pfad etwa so aus:
↓
Endpoint muss aus dem Service entfernt werden
↓
Traffic-Komponenten (Load Balancer, kube-proxy usw.) bemerken die Änderung und aktualisieren sich
↓
Routing wird aktualisiert
Das dauert einen Moment, und in dieser Zeit können Requests noch auf einem Pod landen, der bereits herunterfährt.
Reagiert die Anwendung auf SIGTERM und schließt sofort ihren Listener, schlägt jeder Request in diesem Fenster einfach fehl. Das PDB ist weiterhin korrekt. Zwei gesunde Replikate existieren weiterhin. Nutzer sehen trotzdem einen 502, einen Verbindungsabbruch oder einen Timeout. Nicht das PDB war hier das Problem, sondern das Shutdown-Verhalten des Pods.
Hier kommen Graceful Shutdown und Readiness ins Spiel. Eine Shutdown-Sequenz, die den Traffic wirklich schützt, sieht so aus:
↓
Keinen neuen Traffic mehr annehmen
↓
NotReady werden
↓
Laufende Requests abschließen
↓
Verbindungen schließen und beenden
Die konkrete Umsetzung hängt von der Anwendung ab, aber das Prinzip ist einfach:
Keinen neuen Traffic annehmen, laufende Arbeit abschließen, dann beenden.
terminationGracePeriodSeconds sollte der Anwendung genug Zeit geben, diesen Ablauf abzuschließen.
Readiness ist hier ebenfalls wichtig, weil es eine andere Frage beantwortet:
Soll dieser Pod gerade jetzt normalen Traffic bekommen?
Und das ist etwas anderes als:
Läuft dieser Prozess noch?
Ein Pod kann laufen und trotzdem absichtlich nicht bereit sein, Traffic zu bedienen.
Zusammengenommen ergänzen sich diese drei Mechanismen:
Readiness: Soll dieser Pod Traffic bekommen?
Graceful Shutdown: Wie fährt die Anwendung sauber herunter?
Zusammen schützen sie bei Wartungsarbeiten deutlich besser als ein PDB allein.
Eine Lücke bleibt aber:
Was ist, wenn das Problem überhaupt keine geordnete Pod-Eviction ist?
Das größere Verfügbarkeitsbild
Ein weiterer Kubernetes-Mechanismus, der manchmal mit PDBs verwechselt wird, ist Pod Priority and Preemption.
Eine PriorityClass sagt dem Scheduler, wie wichtig ein Pod im Vergleich zu anderen Pods ist. Wenn der Cluster voll ist und ein Pod mit höherer Priorität nicht mehr geschedult werden kann, darf Kubernetes Pods mit niedrigerer Priorität verdrängen, um Platz zu schaffen.
PDBs werden dabei berücksichtigt, sind aber keine absolute Garantie. In manchen Situationen kann Preemption ein PDB trotzdem verletzen.
Bei kritischen Workloads müssen Pod-Priorität und PDBs deshalb zusammen betrachtet werden.
Das Problem hat aber noch eine andere Seite. Ein PDB kann auch zu restriktiv sein. Statt zu viel Störung zuzulassen, bremst es dann die Wartung aus, obwohl der Cluster sie sicher verkraften würde.
Zum Beispiel:
minAvailable: 100%
oder:
maxUnavailable: 0
Keine dieser beiden Varianten lässt Raum für geplante Unterbrechungen:
↓
Eviction angefordert
↓
PDB erlaubt 0 Störungen
↓
Eviction abgelehnt
↓
Drain wartet
Der Cluster verhält sich korrekt. Die Policy ist für normale Wartung einfach zu restriktiv.
Das Ziel ist nicht, jedes PDB möglichst nachgiebig zu machen, sondern das Disruption Budget an die betriebliche Realität der Workload anzupassen.
Und selbst ein perfekt entworfenes PDB kann eine Workload nicht schützen, deren Replikate schlecht platziert sind.
Stell dir vor, die Pods A, B und C derselben Workload liegen alle auf Node 1. Fällt Node 1 aus, verschwinden alle drei Replikate gleichzeitig.
Das ist kein PDB-Problem. Das ist ein Placement-Problem.
Hier fügt sich das größere Bild zusammen.
Jede Schicht deckt einen anderen Fehlerfall ab. Hochverfügbarkeit entsteht aus der Kombination, nicht aus einem einzelnen Kubernetes-Feature.
Das Wichtigste
Ein gutes PDB sollte nicht nur im YAML korrekt aussehen, sondern sich unter echten Cluster-Bedingungen korrekt verhalten. Wenn disruptionsAllowed dauerhaft auf 0 bleibt, geh der Ursache nach. Vielleicht ist ein Replikat nicht gesund, ein Node ausgefallen, die Workload hat zu wenige Replikate, oder das PDB ist einfach zu restriktiv. Policies mit Kyverno oder OPA/Gatekeeper können offensichtlich problematische Konfigurationen abfangen, bevor sie in Produktion landen.
Statische Validierung kommt aber nur bis zu einem bestimmten Punkt. Am besten testest du dein Verfügbarkeits-Setup, indem du die Operation selbst durchführst: einen Node unter realistischem Traffic drainen und den kompletten Ablauf beobachten. Schau auf den PDB-Status, die Pod-Readiness, die Änderungen an den EndpointSlices, das Shutdown-Verhalten der Anwendung, Fehler und Latenz der Requests und darauf, wie schnell Ersatz-Pods bereit werden.
Ein PDB ist nur ein Teil der Verfügbarkeit. Entscheidend ist, ob die Anwendung weiterhin erfolgreich Traffic bedient, während Pods und Nodes ersetzt, neu gestartet oder entfernt werden.