Kubernetes-Upgrades: Zero-Downtime aus Platform-Sicht
Control Plane, Node Pools, API-Deprecation und kommunikative Planung.
von PlatPrep
Ein Dienstagvormittag. Das Cluster-Upgrade auf 1.29 lief planmäßig — zumindest nach dem Plan. Nodes wurden einer nach dem anderen drainiert, neue kamen hoch, alles grün im Monitoring. Dann: drei Support-Tickets in zehn Minuten. Die Checkout-API hatte kurz Fehler geworfen. Kein langer Ausfall, aber genug für aufgeregte Slack-Nachrichten.
Was war passiert? Beide Replica-Pods liefen auf demselben Node. Der wurde drainiert. Das PDB sagte minAvailable: 1 — aber ein PodDisruptionBudget schützt nur vor freiwilligen Evictions, die es zählen kann. Beim Drain dieses einen Nodes war Pod eins evicted, Pod zwei startete gerade auf einem neuen Node und war noch nicht ready: null verfügbare Replicas, PDB formal nie verletzt. Ein Edge Case? Nein. Ein erwartbares Problem, wenn Workloads nicht verteilt sind.
Verteilung ist die Voraussetzung, nicht die Kür
podAntiAffinity mit requiredDuringSchedulingIgnoredDuringExecution erzwingt, dass Replicas auf verschiedenen Nodes landen — oder der Pod bleibt pending, was beim zu kleinen Cluster auch erst mal jemand merken muss. Die preferred-Variante ist weicher, hilft im Ernstfall aber nicht garantiert. Moderner und flexibler sind topologySpreadConstraints: Sie verteilen Replicas gleichmäßig über Nodes oder Availability Zones (maxSkew: 1 über topology.kubernetes.io/zone) und decken damit Upgrade und AZ-Ausfall gleichzeitig ab.
Dazu das PDB, typischerweise maxUnavailable: 1. Die Kombination ist der Punkt: Das PDB drosselt, wie schnell drainiert werden darf, die Verteilung sorgt dafür, dass diese Drosselung überhaupt greift. Und ein klassischer Stolperstein: ein PDB mit minAvailable: 1 bei nur einem Replica blockiert jeden Drain dauerhaft — dann hängt das Node-Upgrade, bis jemand das Deployment hochskaliert oder das PDB anfasst.
Die Reihenfolge und der Version Skew
Der Prozess folgt immer derselben Ordnung: Control Plane zuerst, dann Worker. Bei EKS, GKE oder AKS upgraded der Provider die Control Plane, du kümmerst dich um die Node Groups — idealerweise mit Surge-Einstellungen, sodass neue Nodes hochkommen, bevor alte drainiert werden. Kubernetes’ Skew-Policy erlaubt dabei, dass das kubelet bis zu drei Minor-Versionen älter ist als der API-Server (seit 1.28; davor zwei). Aber: Upgrades gehen immer nur eine Minor-Version pro Schritt. Wer von 1.27 auf 1.31 will, macht vier Control-Plane-Schritte — und sollte für jeden die Release Notes gelesen haben.
Der gefährlichere Teil sind API-Deprecations. Removed APIs sind der Klassiker für kaputte Deployments nach dem Upgrade: Das berüchtigtste Beispiel war 1.25, als unter anderem policy/v1beta1 PodDisruptionBudgets und PodSecurityPolicy endgültig verschwanden. Tools wie pluto oder kubent scannen Cluster und Manifeste auf deprecated APIs, bevor das Upgrade sie zum Problem macht. Wichtig dabei: Nicht nur die Live-Objekte prüfen, sondern auch Helm-Charts und GitOps-Repos — sonst repariert der nächste Sync die alte, kaputte API-Version wieder rein.
Der Drain im Detail: wo die 502er herkommen
Readiness Probes müssen stimmen — ein Pod, der als ready gilt, bevor er Traffic verarbeiten kann, produziert Fehler direkt nach jedem Rollout, nicht nur bei Upgrades. Aber auch das Herunterfahren hat eine Race Condition, die viele nicht kennen: Wenn ein Pod terminiert wird, passieren zwei Dinge parallel — der Container bekommt SIGTERM, und der Endpoint wird aus den Services entfernt. Parallel heißt: Der Load Balancer kann noch ein paar Sekunden Requests an einen Pod schicken, der schon im Shutdown ist. Das Standard-Gegenmittel ist ein preStop-Hook mit ein paar Sekunden Sleep, damit der Pod Requests weiter annimmt, bis die Endpoint-Propagation durch ist, plus sauberes SIGTERM-Handling in der App (laufende Requests zu Ende bearbeiten, dann beenden) und ein passendes terminationGracePeriodSeconds.
Langlaufende Verbindungen — WebSockets, gRPC-Streams, DB-Connections — werden beim Drain trotzdem gekappt. Reconnect-Logik ist Applikationsverantwortung, aber als Platform Engineer musst du diese Grenze kommunizieren: ein Upgrade-Fenster ankündigen, dokumentieren, was beim Drain passiert, und Teams die Möglichkeit geben, vorher zu testen. Die unbequeme Wahrheit ist, dass “Zero Downtime” eine Eigenschaft der Workloads ist, die die Plattform nur ermöglichen kann.
Wer das alles regelmäßig statt einmal im Jahr macht, hat es leichter: Quartalsweise Upgrades halten die Schritte klein, und jedes Upgrade läuft zuerst auf einem Staging-Cluster mit denselben Workload-Mustern. Nebenbei: Mit Karpenter werden Node-Rotationen zum Routinevorgang, weil Drains ohnehin laufend passieren — Cluster, die Consolidation aushalten, halten auch Upgrades aus. Und für den Fall, dass doch etwas Wichtiges verloren geht, gehört ein getestetes Backup dazu — siehe Velero: Backup und DR wirklich testen.
Die Checkliste am Ende ist kurz: Verteilung (Anti-Affinity oder Topology Spread), PDBs mit sinnvollen Werten, Readiness Probes und Shutdown-Handling, Deprecation-Scan, ein Schritt pro Minor-Version, Staging zuerst. Kein Punkt davon ist allein hinreichend. Zusammen funktioniert es — und im Interview ist genau diese Schichtung die Antwort, die zeigt, dass du Upgrades schon erlebt hast und nicht nur geplant. Grundlagen und Übungen dazu im Kubernetes-Lernpfad.