Heiko Maaß Software-Entwicklung und Elektronische Musik

Kubernetes Pod-Shutdown im Detail

Problemstellung

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.

Ablaufdiagramm des Shutdown-Prozesses

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 lebenden nginx Container eine HTTP 502-Antwort, da das proxy_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 ein SIGTERM an den Container
  • Wenn der Container innerhalb der terminationGracePeriodSeconds nicht beendet ist, schickt Kubernetes ein SIGKILL 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.