Erst die Freigabe, dann der Schalter
Zweimal an einem Tag hingen neue Pods im Start, weil sie ihr Zertifikat ein paar Sekunden zu früh verlangt hatten. Beim zweiten Mal war eine Seite zehn Minuten weg. Die Ursache war kein Fehler im Signer, sondern die Reihenfolge, in der eine Änderung ausgerollt wird. Teil 7 von „The Ops Log“.
TL;DR
TL;DR: Ein PodCertificateRequest, den der Signer ablehnt, bleibt abgelehnt. Der kubelet fragt für diesen Pod nicht noch einmal. Bringt ein Merge Request eine neue Freigabe und den Workload, der sie nutzt, gleichzeitig, fragen die neuen Pods, bevor der Signer seine Policy neu geladen hat, und hängen danach dauerhaft im Init. Die Freigabe kommt deshalb in einen eigenen Merge Request, und ein Gate prüft das.
Was passiert ist
Pod-Zertifikate in Kubernetes laufen über einen Signer, und mein Signer entscheidet nach einer Policy. Dort steht für jeden ServiceAccount, ob er ein Zertifikat bekommt und wofür. Fehlt ein Eintrag, lehnt der Signer die Anfrage ab. Das ist gewollt, denn was die Policy nicht erlaubt, soll es nicht geben.
Weniger offensichtlich ist, was danach passiert. Eine abgelehnte Anfrage ist für den kubelet endgültig. Er stellt für denselben Pod keine neue, und der Pod bleibt im Init stehen, weil sein Volume nie fertig wird. Das Ereignis dafür ist kurz und deutlich:
MountVolume.SetUp failed for volume "tls":
PodCertificateRequest was permanently denied: reason="NoGrant"
Ein Rennen gegen den eigenen Signer
Eine neue Freigabe und der Schalter, der einen Workload auf das Pod-Zertifikat umstellt, wirken wie ein Paar und landen deshalb gern im selben Merge Request. Argo CD wendet dann beides im selben Sync an. Die neuen Pods starten sofort und stellen ihre Anfrage, während der Signer seine neue Policy noch lädt. Sie sind schneller als die Freigabe, und ihre Ablehnung bleibt.
Das passierte zweimal an einem Tag. Beim ersten Mal traf es einen neuen Pod des Edge-Proxys, und weil die alten Pods weiterliefen, merkte niemand außer mir etwas. Beim zweiten Mal traf es den Webserver eines internen Werkzeugs. Der stellt beim Rollout keine zweite Instanz neben die erste, die alte war also schon weg, als die neue hängen blieb. Die Seite antwortete zehn Minuten lang mit 503.
Was sofort half
Die Reparatur war banal. Sobald der Signer die Freigabe kannte, habe ich die hängenden Pods gelöscht. Ihre Nachfolger stellten neue Anfragen, bekamen ihre Zertifikate und liefen nach ein paar Minuten. Dass die Ursache so einfach war, machte die zehn Minuten nicht besser.
Ein Gate vor dem Merge
Damit das nicht wieder passiert, prüft jetzt ein Gate jeden Merge Request. Es rendert alle Workloads und sammelt jede Kombination aus Signer, Namensraum und ServiceAccount, für die ein Pod ein Zertifikat verlangt. Jede davon muss auf der Merge-Basis schon freigegeben sein, also auf dem Stand, gegen den der Merge Request läuft. Eine Freigabe, die erst mit demselben Merge Request kommt, reicht nicht.
Ein Gate, das nie rot war, beweist nichts. Deshalb habe ich es gegen den echten Stand von vor dem Ausfall laufen lassen, also gegen die Merge-Basis des Merge Requests, der die zehn Minuten verursacht hat. Es meldet genau die Pods, die damals hängen geblieben sind, mit dem Hinweis, die Freigabe zuerst allein auszuliefern. Gegen den heutigen Stand ist es grün.
Die Lehre
Was ein anderes System erst laden muss, sollte nicht im selben Rollout landen wie das, was es nutzt. Eine neue Freigabe geht deshalb allein raus und wird live, bevor der Schalter folgt. Wo eine Ablehnung endgültig ist, darf die Reihenfolge nicht vom Zufall eines Syncs abhängen, und genau deshalb gehört ihre Prüfung vor den Merge.
Häufige Fragen
Wie weiß man, dass das Gate funktioniert?+
Man lässt es gegen einen Stand laufen, an dem es rot sein muss. Hier war das die Merge-Basis des Merge Requests, der den Ausfall ausgelöst hat, und das Gate meldet genau die Pods von damals. Gegen den aktuellen Stand ist es grün, und erst beides zusammen ist der Nachweis.
Hätten Sync-Wellen in Argo CD das nicht gelöst?+
Nur zum Teil, denn eine Welle ordnet zwar das Anwenden der Objekte, nicht aber den Moment, in dem der Signer seine Policy tatsächlich neu geladen hat. Liegen Freigabe und Workload außerdem in verschiedenen Anwendungen, hilft eine Welle gar nicht. Ein eigener Merge Request macht die Reihenfolge dagegen sichtbar und prüfbar.
Warum stellt der kubelet keine neue Anfrage?+
Eine Ablehnung ist eine Entscheidung und kein vorübergehender Fehler, deshalb behandelt der kubelet sie für diesen Pod als endgültig. Erst ein neuer Pod stellt eine neue Anfrage, und so reicht es, die hängenden Pods zu löschen, sobald die Freigabe live ist.
Fazit
In beiden Fällen hat der Signer richtig entschieden. Falsch war meine Annahme, dass zwei Änderungen, die zusammengehören, auch gemeinsam ausgeliefert werden dürfen. Bei Pod-Zertifikaten gehört die Reihenfolge zur Korrektheit, weil eine Ablehnung nicht zurückgenommen wird. Das Gate macht aus dieser Erfahrung eine Regel, und der Lauf gegen den alten Stand zeigt, dass es den Ausfall gefangen hätte.
Wer den Anfang verpasst hat, findet ihn in Teil 6: Als die Abwehr den eigenen Edge-Proxy sperrte. Die ganze Umstellung auf Pod-Zertifikate beschreibt die Feldnotiz.
Ich prüfe deine Rollouts auf Reihenfolgen, die ein Sync zufällig entscheidet.
Dabei schaue ich mir an, welche Änderungen in deiner GitOps-Pipeline voneinander abhängen und wo eine Ablehnung oder ein Fehlschlag endgültig ist. Für diese Stellen baue ich Gates, die vor dem Merge prüfen, und weise nach, dass sie auch rot werden können.
Statt Beratung auf Papier betreibe ich diese Gates anschließend mit.
Über den Autor

Kai Ole Hartwig
Programmiert seit 2002 – autodidaktisch gelernt, 2012 mit KO-Web selbständig gemacht. Über 100 Projekte, Fokus auf Security, Performance, Automatisierung und Qualität. Heute freiberuflich: DevSecOps-Beratung, Schulungen und Softwareentwicklung.
