Kai Ole Hartwig
7 Min. Lesezeit
Niedrig

Ein Mandant hält den Rollout aller an

Diese Feldnotiz ist für alle, die mehrere Mandanten mit Argo CD ausrollen und dabei eine feste Reihenfolge erzwingen. An einem Freitag scheiterte bei einem einzigen Mandanten der Setup-Hook. Danach kam drei Stunden lang bei keinem Mandanten dahinter eine Änderung an.

Ausgefallen ist dabei keine Seite. Gerade das macht den Fall lehrreich: Der Schutz hat funktioniert, nur hat es niemand gesehen. Den Auslöser beschreibt die Feldnotiz zu Mails und Bildern aus der Warteschlange.

01 — Mandanten rollen nacheinander aus

Argo CD hält einen Kubernetes-Cluster auf dem Stand, den ein Git-Repository beschreibt. Jeder Mandant der Plattform ist dort eine Application, also eine Einheit, die Argo CD für sich synchronisiert. Erzeugt werden diese Applications von einem einzigen ApplicationSet. Das ist eine Vorlage, die aus jeder Mandantendatei im Git-Repository eine Application macht.

Damit eine fehlerhafte Änderung nicht alle Mandanten gleichzeitig trifft, rollt das ApplicationSet sie mit der Strategie RollingSync aus. Das heißt: Schritt für Schritt, in fester Reihenfolge.

 

strategy:
  type: RollingSync
  rollingSync:
    steps:
      - matchExpressions:
          - key: example.org/tenant
            operator: In
            values: [mandant-a]
      - matchExpressions:
          - key: example.org/tenant
            operator: In
            values: [mandant-b]

 

Ein Schritt beginnt erst, wenn der vorherige gesund ist. Genau darum geht es: Ein Fehler soll bei einem Mandanten hängen bleiben, statt sich auf alle auszubreiten. Was das kostet, zeigt sich erst im Betrieb. Bleibt ein Schritt stehen, bleibt alles dahinter mit stehen.

02 — Ein Setup-Hook scheitert

Ausgelöst hat es eine neue Version einer TYPO3-Extension. Sie brachte eine Einstellungsvorlage mit, eine ext_conf_template.txt. Vor jedem Rollout läuft bei uns ein Setup-Hook: ein Job, den Argo CD vor dem eigentlichen Sync startet. Er richtet Schema und Extensions ein, und dabei schreibt TYPO3 die Standardwerte jeder Vorlage in die settings.php.

Der Code liegt im Pod aber als schreibgeschütztes Image-Volume. Das ist ein Dateisystem aus einem Container-Image, in das niemand schreiben kann.

 

In ConfigurationManager.php line 387:
  config/system/settings.php is not writable.

 

Der Hook ist ein Job mit der Annotation argocd.argoproj.io/hook: PreSync. Argo CD führt ihn aus, bevor es irgendetwas am Mandanten ändert. Scheitert er, bricht der Sync ab, und die laufenden Pods bleiben, wie sie sind. Genau so kam es: Der Mandant lief weiter auf der vorherigen Version, und seine Besucher haben nichts bemerkt.

So weit war das Verhalten richtig. Ein Setup, das scheitert, soll nicht die Hälfte einer neuen Version ausrollen.

03 — Der Mandant dahinter wartet stumm

Der gescheiterte Mandant war der zweite in der Reihe. Meine eigene Seite kommt weiter hinten, und auch für sie lag die neue Version längst im Git-Repository. Argo CD zeigte sie als OutOfSync, also nicht auf dem Stand aus Git. Einen Versuch, sie zu synchronisieren, unternahm es aber nicht. Einen Fehler meldete es auch nicht.

Ein Blick auf die Application machte es zunächst noch rätselhafter. Dort stand die automatische Synchronisierung auf aus.

 

syncPolicy:
  automated:
    enabled: false
    prune: true
    selfHeal: true

 

Das sieht aus, als hätte jemand die Automatik abgeschaltet. Es ist aber der Normalzustand. Mit RollingSync schreibt der ApplicationSet-Controller enabled: false in jede erzeugte Application und steuert die Syncs selbst, Schritt für Schritt. Das true im Template bleibt wirkungslos. Wer das nicht weiß, sucht an der falschen Stelle.

Die eigentliche Ursache stand zwei Schritte weiter vorn. Der Job des zweiten Mandanten hatte sein Backoff-Limit erreicht, also die Zahl der Versuche, nach der Kubernetes aufgibt. Solange dieser Schritt nicht gesund wurde, ging es nicht weiter.

04 — Was der Alarm sah, und wann

Für genau diesen Fall gibt es einen Alarm: ArgoAppOutOfSync schlägt an, wenn eine Application länger als eine Stunde nicht synchron ist. Er hat auch angeschlagen, für den gescheiterten Mandanten und für meine Seite, eine Stunde nach Beginn des Staus.

Bemerkt hatte ich es schon vorher, und zwar eher zufällig. Ich wartete auf die neue Version und ließ ein Skript prüfen, welche Version in jedem Pod läuft. Zwei Mandanten zeigten sie, zwei nicht. Erst die Frage nach dem Warum führte über die Application zum Job und von dort zur Fehlermeldung.

Die Stunde Verzögerung im Alarm ist Absicht. Ein normaler Rollout ist nach wenigen Minuten synchron, und ein Alarm, der bei jedem kurzen Abstand anschlägt, wird bald überhört. Der Preis dafür ist eine Stunde, in der ein Fix im Git liegt und nirgends ankommt.

05 — Wie sich der Stau auflöste

Die Lösung lag nicht im Cluster, sondern in der Extension. Version 1.1.1 kam ohne Einstellungsvorlage und liest ihre Werte direkt aus der Konfiguration, mit Standardwerten im Code. Ein Test schlägt fehl, sobald die Datei zurückkommt.

Den Rest erledigte die Kette, die auch sonst jede Änderung ausliefert. Der Abhängigkeits-Bot hob die Version in allen Websites an, jede Website baute ein neues Release, und der Bot trug es in die Mandantendateien ein. Diesmal lief der Setup-Hook des zweiten Mandanten durch, sein Schritt wurde gesund, und die Schritte dahinter liefen nacheinander an. Eingegriffen habe ich in den Cluster nicht.

Eine Frage ist noch offen. Zwei Mandanten sind mit derselben fehlerhaften Version durch denselben Hook gekommen, ohne dass er scheiterte. Beide Jobs mounten /app genauso schreibgeschützt. Warum TYPO3 dort nicht schreiben wollte, habe ich nicht geklärt.

06 — Was ich daraus mitnehme

Ein Punkt ist noch nicht umgesetzt. Ein Alarm, der nicht nur »nicht synchron« meldet, sondern den Schritt nennt, an dem der Rollout hängt, hätte die Stunde Suche erspart.

Häufige Fragen

Warum nicht alle Mandanten parallel synchronisieren?+

Dann wäre der fehlerhafte Hook bei allen Mandanten gleichzeitig gescheitert. Ausgefallen wäre trotzdem keine Seite, aber eine wirklich kaputte Version hätte alle auf einmal getroffen. Die Reihenfolge mit einem Pilotmandanten vorn schützt davor.

Hätte der Hook die Seite beschädigen können?+

Nicht in diesem Fall. Ein PreSync-Hook läuft, bevor Argo CD die Deployments anfasst. Scheitert er, bleibt alles auf dem vorherigen Stand. Gefährlicher sind Hooks, die eine Datenbank migrieren und danach scheitern. Dagegen hilft nur eine Migration, die die alte Version noch versteht.

Wie löst man so einen Stau am schnellsten?+

Indem man den Fehler beim stehenden Mandanten behebt, nicht indem man die Reihenfolge umgeht. Ein von Hand ausgelöster Sync der Mandanten dahinter hätte die neue, fehlerhafte Version auch dorthin gebracht. Der richtige Weg war der Fix in der Extension.

Fazit

Ein gestaffelter Rollout tut, was er soll: Er hält einen Fehler bei einem Mandanten fest. Dass er dabei alle Mandanten dahinter mit festhält, gehört zum Vertrag, auch wenn es sich im Betrieb wie ein zweiter Fehler anfühlt.

An diesem Freitag ist keine Seite ausgefallen. Drei Stunden lang kam aber auch keine Änderung an, und die einzige Spur führte über eine Einstellung, die wie ein Fehler aussah und keiner war. Wer RollingSync einsetzt, sollte wissen, wo er nachsieht, wenn ein Mandant nicht folgt: beim Schritt davor.

Rollt Argo CD bei dir mehrere Mandanten aus?

Ich sehe mir an, wie deine Mandanten ausgerollt werden, und richte Reihenfolge, Setup-Hooks und Alarme so ein, dass ein Fehler bei einem Mandanten stehen bleibt und du sofort siehst, wo.

Termin buchen →

Über den Autor

Foto von Kai Ole Hartwig.

Kai Ole Hartwig

Freiberuflicher DevSecOps-Berater · OnlyOle Consulting

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.