Kubernetes Pod-Shutdown im Detail
14 Jan 2023Problemstellung
Vor längerer Zeit hatten wir bei unseren Services häufig HTTP 502 (Gateway-Timeout) Antworten,
sobald die Pods des aufgerufenen Services neu deployed wurden.
Die betreffenden Pods enthalten zwei Container: nginx
und application
.
Der nginx
-Container terminiert die TLS-Anfrage und fungiert als Proxy
für den application
-Container.
In dem Deployment-Objekt war bei beiden Containern folgendes konfiguriert worden:
terminationGracePeriodSeconds: 10
preStopHook: sleep 10
Das war natürlich völliger Unsinn. Warum ? Sehen wir uns an, wie der Lebenszyklus definiert ist:
Terminierung von Pods
Wenn Kubernetes einen Pod terminiert, werden zwei Dinge als erstes ausgeführt:
- Der Pod wird aus dem Service entfernt (aus den IP-Tables gestrichen), so dass neue Aufrufer des Services nicht an diesen sterbenden Pod geleitet werden.
- Die sog.
preStop
-Hooks aller Container werden ausgeführt.
Erst wenn der preStop
-Hook ausgeführt wurde, schickt Kubernetes ein SIGTERM
an PID 1 des Containers.
Der Container kann dann einen applikationsinternen graceful shutdown ausführen, laufende Requests abarbeiten.
Sollte der Container aber innerhalb der terminationGracePeriodSeconds
nicht beendet sein, schickt Kubernetes ein SIGKILL
an den Container.
Das sollte nie passieren.
Mehr dazu in der Kubernetes-Dokumentation: Pod-Termination
Hier ein beispielhaftes Ablaufdiagramm, mit einem preStopHook
von 10s
und einer terminationGracePeriod
von 30s
.
Korrektur der Konfiguration
Mit dem Wissen verstehen wir auch warum die anfänglich beschriebene Konfiguration (preStopHook
und terminationGracePeriod
auf 10s
) Unsinn ist:
Der Container hat gar keine Zeit sich sauber herunterzufahren.
Zudem war der preStopHook
bei application
und nginx
identisch. Die Reihenfolge für den SIGTERM ist dann zufällig.
- Wenn
application
zuerst herunterfährt, erhält der Client vom noch lebendennginx
Container eine HTTP 502-Antwort, da dasproxy_pass
Ziel nicht mehr erreicht werden kann. - Wenn
nginx
zuerst herunterfährt, erhält der Client ein Connection-Timeout, was immer besser ist, da HttpClients wie Netty automatisch einen Retry machen
Schlussendlich haben wir die preStop-Zeiten so angepasst, dass der nginx-Container immer zuerst den SIGTERM
erhält.
Was sollte man sich merken ?
- Kubernetes nimmt den zu terminierenden Pod aus dem Service heraus, das läuft aber nicht immer auf allen Nodes sofort synchron. Vereinzelte Requests können den “sterbenden” Pod noch erreichen.
- Kubernetes schickt erst nach dem Ausführen des
preStopHook
einSIGTERM
an den Container - Wenn der Container innerhalb der
terminationGracePeriodSeconds
nicht beendet ist, schickt Kubernetes einSIGKILL
an den Container. Wichtig ist: Dieser Timer beginnt zeitgleich mit dem preStopHook, nicht erst nach dessen Abschluss. - Web-Services müssen immer unter PID 1 ausgeführt werden, damit sie auf den
SIGTERM
reagieren können.