• English icon
  • German icon
Erklärvideo

Kubernetes-Rollout auf AKS hängt: ProgressDeadlineExceeded, kein Rollback

· Reihe: Container auf Azure

azureakskubernetesdeploymentsprobeskubectl

Das Video ist auf Englisch.

Ich habe eine fehlerhafte Version auf Azure Kubernetes Service (AKS) ausgerollt: Ihre Readiness-Prüfung antwortete mit 404. Zehn Minuten lang schlug kein einziger Aufruf fehl. Dann meldete kubectl, der Rollout sei gescheitert, und Kubernetes unternahm nichts.

Dieser Beitrag zeigt, was ein Rolling Update mit einer Version macht, die nie bereit wird, wo der Fehler sichtbar wird und warum das Zurückrollen Ihre Aufgabe ist. Alles stammt aus der Übung des Microsoft-Learn-Moduls, ausgeführt in meinem eigenen Cluster.

Das Wichtigste

  • Der alte Pod bedient weiter. Bei einem Replikat darf ein Rolling Update einen zusätzlichen Pod starten und keinen wegnehmen. Der neue Pod startet neben dem alten, und der alte bleibt, bis der neue bereit ist. Meiner wurde es nie, und der Client hat keinen einzigen Fehler gesehen.
  • kubectl get deployment sieht gesund aus. READY 1/1 zählte den alten Pod. Der neue Pod stand auf Running 0/1 mit 0 Neustarts, und der Grund stand nur in den Events: Readiness probe failed: HTTP probe failed with statuscode: 404.
  • Nach zehn Minuten meldet Kubernetes nur. Nach progressDeadlineSeconds (standardmäßig 600) bekommt die Condition Progressing des Deployments den Grund ProgressDeadlineExceeded, und kubectl rollout status endet mit einem Fehler. Zurückgerollt wird nichts; beide Pods bleiben.
  • Das Undo ist Ihre Aufgabe, und mehr Replikate reißen ein größeres Loch. kubectl rollout undo stellte das alte Pod-Template sofort wieder her. Mit vier Replikaten nahm dieselbe fehlerhafte Version sofort einen alten Pod weg: READY 3/4, solange der Rollout hängt.

Die Übergabe: der neue Pod neben dem alten

In der Übung läuft eine kleine Python-API auf AKS. Ein Deployment hält ein Replikat davon am Laufen, hinter einem Service, der stabilen Adresse, die Aufrufe auf die Pods verteilt. Dann macht die Übung sie absichtlich kaputt: Die Readiness-Probe, die Prüfung, die sagt „ich bin bereit für Traffic“, fragt einen Pfad ab, den es nicht gibt.

readinessProbe:
httpGet:
path: /invalid-path
port: 8000
initialDelaySeconds: 5
periodSeconds: 10

Die Liveness-Probe in derselben Datei prüft weiter /healthz. Ich habe die Datei angewendet und die Pods mit einem kleinen eigenen Skript beobachtet, das jede Änderung ausgibt:

> kubectl apply -f k8s/probe-failure-deployment.yaml
deployment.apps/api-deployment configured
> python pods.py --secs 40
0.3s api-deployment-695945c854-nx6qj Running 1/1 restarts 0
0.3s api-deployment-6b4f47c8b7-6m9bb ContainerCreating 0/1 restarts 0
2.5s api-deployment-6b4f47c8b7-6m9bb Running 0/1 restarts 0

Der neue Pod lief, wurde aber nie bereit. Der alte blieb bei 1/1. Ein Client-Pod im Cluster rief den Service einmal pro Sekunde auf, wie ein Benutzer, und jede Antwort kam von dem Pod, der um 13:58 gestartet war, dem alten:

> kubectl logs client --tail 4
14:07:06 200 pod started 13:58:45.05
14:07:07 200 pod started 13:58:45.05
14:07:08 200 pod started 13:58:45.05
14:07:09 200 pod started 13:58:45.05

Warum? Ein Rolling Update folgt zwei Einstellungen, standardmäßig je ein Viertel der Replikate:

EinstellungStandardBei 1 ReplikatBei 4 Replikaten
maxSurge: zusätzliche Pods während des Rollouts (aufgerundet)25 %11
maxUnavailable: Pods, die fehlen dürfen (abgerundet)25 %01

Bei einem Replikat darf der Rollout einen zusätzlichen Pod starten, aber keinen wegnehmen. Der alte Pod bleibt also, bis der neue bereit ist, und ein Pod bekommt erst Traffic vom Service, wenn er bereit ist.

Wo steht der Fehler?

Der übliche erste Blick gilt dem Deployment selbst: wie viele seiner Pods bereit, aktuell und verfügbar sind. Der neue Pod war 42 Sekunden alt und hatte bis dahin jede Prüfung nicht bestanden:

> kubectl get deployment api-deployment
NAME READY UP-TO-DATE AVAILABLE AGE
api-deployment 1/1 1 1 8m30s

Es sieht völlig gesund aus, weil es bereite Pods zählt, und der bereite ist der alte Pod. Die Pod-Liste zeigt den neuen Pod mit 0/1 und null Neustarts: Nur die Readiness-Probe schlägt fehl, die Liveness-Probe, die einen Container neu startet, besteht weiter.

> kubectl get pods
NAME READY STATUS RESTARTS AGE
api-deployment-695945c854-nx6qj 1/1 Running 0 8m30s
api-deployment-6b4f47c8b7-6m9bb 0/1 Running 0 42s
client 1/1 Running 0 8m11s

Der Grund steht in den Events, den Notizen des Clusters darüber, was mit jedem Objekt passiert ist. Eine fehlgeschlagene Readiness-Probe ist ein Event mit dem Grund Unhealthy, alle zehn Sekunden aufs Neue:

> kubectl get events --field-selector reason=Unhealthy
LAST SEEN TYPE REASON OBJECT MESSAGE
2s Warning Unhealthy pod/api-deployment-6b4f47c8b7-6m9bb Readiness probe failed: HTTP probe failed with statuscode: 404

Dann habe ich kubectl dreißig Sekunden auf den Rollout warten lassen. Es lief in den Timeout, mit einem alten Replikat „pending termination“: Der Rollout wartete darauf, dass der alte Pod gehen darf.

> kubectl rollout status deployment api-deployment --timeout 30s
Waiting for deployment "api-deployment" rollout to finish: 1 old replicas are pending termination...
error: timed out waiting for the condition

Zehn Minuten später: zurückgerollt?

Ein Deployment hat eine Frist für den Fortschritt: progressDeadlineSeconds, standardmäßig 600 Sekunden, und Sie können sie im Deployment kürzer setzen. Macht der Rollout so lange keinen Fortschritt, wird das Deployment als gescheitert markiert. Ich habe denselben Befehl ohne Timeout ausgeführt und warten lassen:

> kubectl rollout status deployment api-deployment
Waiting for deployment "api-deployment" rollout to finish: 1 old replicas are pending termination...
error: deployment "api-deployment" exceeded its progress deadline

Das kam etwa zehn Minuten, nachdem ich die fehlerhafte Version angewendet hatte. Hat Kubernetes sie zurückgerollt? Nein.

> kubectl get pods
NAME READY STATUS RESTARTS AGE
api-deployment-695945c854-nx6qj 1/1 Running 0 17m
api-deployment-6b4f47c8b7-6m9bb 0/1 Running 0 10m
client 1/1 Running 0 17m

Beide Pods waren noch da. Das Log des Clients der letzten zwölf Minuten, der ganze hängende Rollout und etwas davor, enthielt 0 fehlgeschlagene und 718 beantwortete Aufrufe. Das Deployment meldet den Fehlschlag nur, in seiner Status-Condition Progressing:

> kubectl get deployment api-deployment -o jsonpath="{.status.conditions[1].reason}"
ProgressDeadlineExceeded

Das ist alles. Die Kubernetes-Dokumentation sagt es deutlich: Bei einem hängenden Rollout unternimmt Kubernetes nichts außer dieser Meldung. Es versucht es weiter, und der alte Pod bedient weiter, so lange Sie es zulassen. Release-Werkzeuge über Kubernetes können einen gescheiterten Rollout erkennen und für Sie zurückrollen. Kubernetes selbst tut es nicht.

Undo, und drei Fehler

kubectl rollout undo stellt das vorherige Pod-Template wieder her, die Beschreibung der Pods im Deployment:

> kubectl rollout undo deployment api-deployment
deployment.apps/api-deployment rolled back
> kubectl rollout status deployment api-deployment --timeout 120s
deployment "api-deployment" successfully rolled out

Das war sofort fertig (0,6 s), weil der alte Pod nie gegangen war. kubectl warnte außerdem, dass ein Undo die Annotation last-applied-configuration eines mit kubectl apply verwalteten Deployments nicht aktualisiert; das nächste apply sollte also aus einem Manifest kommen, das funktioniert.

Drei Fehler machen einen hängenden Rollout schlimmer:

  1. Das Deployment nur auf einen Blick prüfen. READY 1/1 war der alte Pod. Sehen Sie sich die Pods und die Events an.
  2. Ohne Timeout warten. In einer Pipeline ist kubectl rollout status Ihr Gate: Es endet mit einem Fehlercode, wenn der Rollout scheitert, sodass die Pipeline reagieren kann. Geben Sie ihm einen Timeout, der zu Ihrer App passt, hier zwei Minuten, und führen Sie das Undo in derselben Zeile aus, damit eine fehlerhafte Version nie zehn Minuten herumsteht:
kubectl rollout status deployment api-deployment --timeout 120s ||
kubectl rollout undo deployment api-deployment
  1. Derselbe falsche Pfad für beide Probes. Hier war nur die Readiness-Probe falsch. Stünde die Liveness-Probe auch darauf, würde das Kubelet, der Agent auf dem Node, den neuen Pod wieder und wieder neu starten: eine Crash-Schleife zusätzlich zum hängenden Rollout.

Derselbe Fehler mit vier Replikaten

Ein Viertel von vier ist eins: ein Pod zusätzlich und ein Pod weg. Ich habe das Deployment auf vier skaliert, dieselbe fehlerhafte Version noch einmal angewendet und die Pods mit demselben Skript beobachtet:

> python pods.py --secs 40
0.3s api-deployment-695945c854-56gl2 Terminating 1/1 restarts 0
0.3s api-deployment-695945c854-8h7sh Running 1/1 restarts 0
0.3s api-deployment-695945c854-9xct9 Running 1/1 restarts 0
0.3s api-deployment-695945c854-nx6qj Running 1/1 restarts 0
0.3s api-deployment-6b4f47c8b7-dfhtx ContainerCreating 0/1 restarts 0
0.3s api-deployment-6b4f47c8b7-ljp88 ContainerCreating 0/1 restarts 0
2.5s api-deployment-6b4f47c8b7-dfhtx Running 0/1 restarts 0
2.5s api-deployment-6b4f47c8b7-ljp88 Running 0/1 restarts 0
2.5s api-deployment-695945c854-56gl2 gone
> kubectl get deployment api-deployment
NAME READY UP-TO-DATE AVAILABLE AGE
api-deployment 3/4 2 3 18m

Ein alter Pod wurde schon nach einer Drittelsekunde beendet. Der Pod, der gegangen ist, braucht ebenfalls einen Ersatz, also starteten zwei neue Pods, und beide blieben bei 0/1 hängen. Bereit 3/4: ein Viertel der Kapazität weg, solange der Rollout hängt. Der Client bekam weiter seine Antworten, von den drei alten Pods. Niemand merkt es, bis der Traffic wächst und drei Pods nicht reichen. Bei einem hängenden Rollout bedeuten mehr Replikate nicht mehr Sicherheit, sondern ein größeres Loch.

Was beim Ausführen passiert ist

Getestet am 2. November 2026 in meinem eigenen Azure-Abonnement (AKS 1.36 in westeurope, Azure CLI 2.88.0, kubectl 1.36); danach wurde alles gelöscht. Der Cluster hatte einen Node vom Typ Standard_D2s_v5 mit angebundener Registry, wie in der Übung. Die fehlerhafte Version ging um 14:06:25 UTC hinein; kubectl rollout status ohne Timeout wartete 526,8 s und meldete die verpasste Frist um 14:16:27. Das Warten mit 30 Sekunden dauerte 30,4 s, und beide Wartebefehle endeten mit Exit-Code 1. Das Undo dauerte bei einem Replikat 0,6 s. Mit vier Replikaten dauerten das Hochskalieren und das Undo je 11,8 s, danach waren wieder vier Pods der alten Version bereit, einer davon neu. Die Zahl der Aufrufe stammt aus dem Log des Clients (kubectl logs client --since=12m, gezählt mit Select-String in PowerShell).

Was das Video weglässt

  • minReadySeconds und das Pausieren eines Rollouts mit kubectl rollout pause / resume.
  • Release-Werkzeuge, die für Sie zurückrollen, etwa Argo Rollouts oder Flagger.
  • Fehlschlagende Liveness-Probes (eine Crash-Schleife) und Startup-Probes.
  • Die Reparatur der Probe mit kubectl edit, wie in der Übung: Wie das Undo startet sie einen neuen Rollout mit einem Template, das funktioniert.

Weiterführende Links

Video-Transkript (Englisch)

I deployed a broken version to Azure Kubernetes Service. Its readiness check answered 404. For ten minutes, not one call failed. Then kubectl said the rollout had failed. So what did Kubernetes do about it?

In this video, you'll see what a rollout does with a version that never gets ready, and what happens after ten minutes. At the end: the same mistake with four replicas, and what it costs.

What happens when the new version never gets ready? In the Microsoft Learn module's exercise, a small Python API runs on Azure Kubernetes Service. A deployment keeps one copy of it running, one replica in one pod, behind a Service, the stable address that spreads calls over the pods. The exercise breaks it on purpose. Its readiness probe, the check that says "send me traffic", asks for a path that doesn't exist. So I applied the broken version, the exercise's probe failure file. Configured. And I watched the pods with a small script that prints every change. The new pod ran, but never got ready: zero of one, zero of its one container ready. The old pod stayed at one of one. A client pod inside the cluster called the Service once a second, like a user would. What did the client get while the new pod failed its check? The old version. I read the client's last four lines. Every answer came from the pod that started at 13:58: the old one. Here's why. With one pod, a rollout may start one extra pod, but may take none away. So the new pod starts next to the old one, and the old one stays until the new one is ready. Those are two settings, maxSurge and maxUnavailable, a quarter of the replicas each by default: rounded up for the extra pods, rounded down for the ones taken away. And a pod only gets traffic once it's ready. I showed that in the first video of this series; the card is up there now.

So where does it say that something is wrong? The usual first look is the deployment itself: how many of its pods are ready, up to date and available. The new pod has failed its check for a minute now. Does the deployment look broken? No. One of one ready. I asked for it, the way you'd check a rollout at a glance. One ready, one up to date, one available. It looks perfectly healthy. It counts ready pods, and the ready one is the old pod. The pod list says zero of one, with zero restarts, because only the readiness check fails. The liveness check, the one that restarts a container, still passes. The reason is in the events, the cluster's notes about what happened to each object. I asked for the events with the reason Unhealthy. Readiness probe failed: HTTP probe failed with statuscode: 404. Every ten seconds, again. Then I asked kubectl to wait for the rollout, for thirty seconds. Timed out. Still one old replica pending termination: the rollout is waiting for the old pod to be allowed to go. This series runs one Azure service at a time, for real. Subscribe to follow the series.

How long does Kubernetes wait? For a pod that never gets ready, a deployment has a progress deadline, ten minutes by default. If the rollout makes no progress for that long, the deployment is marked as failed. The setting is called progressDeadlineSeconds, and you can make it shorter in the deployment. So I ran the same command again, this time without a timeout, and let it wait. Exceeded its progress deadline. About ten minutes after I had applied the broken version. After ten minutes, does Kubernetes roll it back? No. I listed the pods. Both still there. The old pod, seventeen minutes old, and the broken one, ten minutes old, still not ready, still zero restarts. And the users? I counted the client's failed calls in the last twelve minutes: the whole stuck rollout and a little before. Zero failed. And the answered ones: seven hundred eighteen. The deployment just reports it, in a status condition called Progressing. I asked for its reason. ProgressDeadlineExceeded. That's all. Kubernetes takes no action on a stalled rollout other than this report. It keeps trying, and the old pod keeps serving, for as long as you let it. Release tools on top of Kubernetes can watch for this condition and roll back for you. Kubernetes itself doesn't.

So how do you get out of it? I rolled back with kubectl rollout undo, which puts the previous pod template back: the description of the pods inside the deployment. Rolled back. And the rollout finished at once, because the old pod had never left. Three mistakes make this worse. One: checking the deployment at a glance. Ready one of one was the old pod. Two: waiting without a timeout. In a pipeline, kubectl rollout status is your gate. It exits with an error code when the rollout fails, so the pipeline can act on it. Give it a timeout that fits your app, two minutes for this one. And if it fails, run the undo in the same line, so a broken version never sits there for ten minutes. Three: the same wrong path for both checks. Here only the readiness probe was wrong. With the liveness probe on it too, the kubelet, the agent on the node, would restart the new pod again and again, and you'd be in a crash loop on top of the stuck rollout.

To sum it up: a rolling update starts the new pod first, and keeps the old one until the new one is ready. After ten minutes, the deployment is marked ProgressDeadlineExceeded, and nothing more. Undo is your job.

And with four replicas? A quarter of four is one: one pod extra, and one pod away. I scaled the deployment to four, and applied the same broken version again. Configured. And I watched the pods with the same script. One old pod was already terminating, a third of a second in. The pod that left needs a replacement too, so two new pods started, and both got stuck at zero of one. Then I asked for the deployment. Ready three of four. A quarter of the capacity gone, for as long as the rollout stays stuck. The client still got its answers, from the three old pods. Nobody notices, until the traffic grows and three pods aren't enough. With a stuck rollout, more replicas don't mean more safety: they mean a bigger hole.

To give a new version only a small share of real traffic first, the video on screen does exactly that, on Azure Container Apps. If this was useful, subscribe for more practical .NET and Azure.

Brauchen Sie das in Ihrer eigenen Azure-Umgebung?

Ich entwickle, migriere und betreibe .NET-Plattformen auf Azure, mit Azure DevOps und Terraform. Wenn Ihr Team das sauber aufsetzen möchte, sehen Sie sich mein Angebot an.

Zu den Leistungen