Continous Deployment und Deploymentstrategien
November 01, 2022
opskubernetes
Eine hohe Erreichbarkeit ist für viele Applikationen essentiell und gutes Testen bei Aktualisierungen von Applikationen ist deshalb unvermeindlich. Das Problem: gewisse Konfigurationen und Umstände lassen sich außerhalb der Produktionsumgebung trotz beinnahe identischer Staging-Umgebung nicht testen oder sie existiert gar nicht erst, da Software kontinuierlich ausgeliefert werden soll. Damit neue Software ausgerollt werden kann, muss deshalb eine Strategie her, die den möglichen Schaden neuer Versionen minimiert — Canary Releases, das graduelle Ausrollen neuer Versionen.
In einem einfachen Setup werden bei der Aktualisierung einer neuen Version einer Applikation alte Dateien ersetzt und neugestartet. Im Falle von docker heißt das beispielsweise, dass ein neues Docker Image gepullt wird, bestehende Container gestoppt und die neue Version des Images gestartet wird.
Obwohl das Vorgehen für viele Applikationen ausreicht, hat es ein großen Nachteil: Die Applikation ist für den Zeitpunkt des Neustarts nicht erreichbar. Je nach Komplexität der Applikation handelt es sich bei diesem Zeitraum um Sekunden oder gar Minuten. 
Aktualisierungsprozesse können fehlschlagen. Bei diesem Setup ist ein geeigneter Rollback-Mechanismus eine Herausforderung. Es kann zum Beispiel passieren, dass die neue Version der Applikation aufgrund ihrer Konfiguration nicht starten kann. Das passiert beispielsweise, wenn die neue Version einer Applikation eine Datenbankverbindung beim Start validiert, die in der vorherigen Version nicht vorhanden war.
Das Ergebnis ist, dass der Prozess zum Ausrollen der neuen Software wie im Bild 1 zwar die alte Applikation gelöscht hat, die neue Version aber nicht gestartet hat. Die Applikation ist also offline. Ab hier muss eine Backup-Strategie greifen, indem eine alte Version des docker-Images gestartet wird und die vorherigen Schritte des Bildes zurückdreht. Diese Deployment-Strategie ist aufgrund des Wegwerfens und Neustarten auch als “Recreate” bekannt.
Glücklicherweise untersützen Container-Orchestratoren wie Kubernetes ist heutzutage verschiedene Ansätze. Anders als im Recreate-Ansatz werden Applikationen, die auf einem Kubernetes Cluster aktualisiert werden werden, standardmäßig mittels einer Rolling-Update-Deployment Strategie aktualisiert.

Im Unterschied zur Recreate-Strategie sind bei einem Rolling-Update-Deployment kurzzeitig zwei Versionen der Applikation aktiv. Bei einem Rolling-Update-Deployment wird pro Laufender Instanz der Applikation eine neue Instanz gestartet.
Jede Instanz muss dazu eine Schnittstelle anbieten — den Health-Check. Dieser kann beispielsweise eine HTTP-Schnittstelle sein, dessen Status Code 200 OK sein muss, wenn die Instanz bereit ist, Anfragen zu akzeptieren. Erst wenn die neue Instanz den programmatischen Health-Check überstanden kann, terminiert der Orchestrartor die Instanzen der alten Version.
Dieser Ansatz hat natürlich eigene Implikationen. So sind kurzzeitig mehrere Applikationsversionen aktiv, die eventuell gegen dieselbe Datenbank verbinden. Das bedeutet für den Entwickler, dass neue Versionen der Applikationen zumindest so lange Abwärtskompatible Schnittstellen (z.B. das Datenschema der Datenbank oder bereitgestellte HTTP-APIs für andere Software-Teams) sein müssen, bis die neue Version vollständig ausgerollt wurde.
In der Praxis kann das beispielsweise bedeuten, dass Applikation V1 in der Datenbank in der Tabelle “Users” eine Spalte “Name” erwartet, während Applikation V2 die Spalte “Name” auf “Firstname” umbenannt wurde, zunächst eine Migration bereitgestellt werden muss, die die Tabelle Users sowohl “Name”, als auch “Firstname” unterstützt. Erst wenn alle Instanzen von V1 abgeschaltet wurden, kann dann entsprechend die Spalte “Name” gelöscht werden.
Anders als bei der Recreate-Strategie ist beim Rolling-Update-Deployment jedoch eines gelöst: Das automatische zurückrollen fehlgeschlagener Releases, da dieser Fall nicht auftreten kann. Angenommen die neue Version ist aufgrund diverser Konfigurationsfaktoren nicht bereit zu starten, so wird diese Instanz auch niemals den Health-Check überstehen. Wenn der Health-Check nicht überstanden wird, dann ersetzt der Orchestrator die alte Version nicht — die alte Version wird also weiter betrieben.

Während die Recreate-Strategie also fehlerhafte Downtimes ermöglicht, müssen bei einem Rolling-Release Deployment entsprechende Vorbereitungen der Entwickler getroffen werden, um Schnittstellen (Datenbankmodelle, HTTP-API) graduell zu aktualisieren um paralleles Betreiben verschiedener Versionen der ermöglichen.
Rolling-Update-Deployments lösen damit das Problem, dass fehlerhafte Deployments eine Downtime verursachen. Es fehlt ein weiteres wichtiges Merkmal: Zwar können viele Fehler durch einen Health-Check abgebildet werden, ein Validieren der neuen Applikations-Version ist allerdings nicht möglich. Das ist deshalb ein Problem, weil verschiedene Fehler erst zur Laufzeit während des Betriebes auftreten. Um diese Fehler zu erkennen, muss die neue Version also ausgerollt werden und bereits Anfragen Bearbeiten können, um zu validieren, ob die neue Version noch korrekt mit dem Produktions-Workload funktioniert. Der Health-Check muss also erfolgreich passieren.
Und hier kommt das Konzept von Canary Releases ins Spiel. Canary Releases sehen es vor, dass zunächst ein kleiner Anteil der Nutzer die neue Version der Applikation bedienen. Gleichzeitig werden gewisse Metriken, z.B. HTTP-Success-Raten oder Request-Response-Dauern, aufgenommen und sind über eine Schnittstelle abfragbar. Erst wenn diese Raten eine gewisse Schwelle erreichen, wird Datenverkehr auf weitere neue Versionen weitergeleitet, bis diese dann vollständig die alte Version ablösen können und diese letztlich ausgeschaltet werden kann.
Eine Applikation V2 kann womöglich eine so komplexe Datenbankabfrage absetzen, die die Performance der Applikation in das bodenlose stürzen lässt. Ein produktives Arbeiten mit der V2 wäre nicht möglich und sollte deshalb nicht an weitere Nutzer ausgeliefert werden.
Implementierung
Anders als Recreate und Rolling-Update, werden Canary Releases werden nicht nativ von Kubernetes unterstützt. Stattdessen gibt es Technologien wie Flagger, die die Kubernetes-API erweitern. Flagger erweitert dazu das Vokabular von Kubernetes über CRD und übernimmt genau die von oben beschrieben Aufgabe. Während die einzelnen Applikationen das Messen und Bereitstellen von Metriken übernehmen, wertet Flagger diese kontinuierlich aus und aktualisiert den Datenverkehr und das Deployment.

Das Canary-Release baut auf den vorheringen Strategien auf. Bei einem Caray Rollout können zwei Fälle eintreten, wenn eine neue Version ausgerollt wird.
- Schlägt das Deployment der neuen Version fehl, so gilt die Applikation nicht als Bereit. Das Deployment ist fehlgeschlagen, ähnlich wie beim Rolling-Update
- Das Deployment ist erfolgreich und muss nun zur Laufzeit validiert werden.
Der erste Fall ist dabei trivial. Der zweite Fall ist in Bild 4 zu erkennen. Nachdem die neue Version erfolgreich deployed wurde, wird der Router (meistens der Ingress-Controller), der den Datenverkehr letztlich an die Instanz weiterleitet von Flagger konfiguriert. Dabei kann ein Gewicht-Wert angegeben werden, der regelmäßig neu evaluiert wird. Dieser kann z.B. beschreiben, dass 5% aller Anfragen gegen die neue Instanz geleitet werden. Von diesen 5% der Anfragen, werden von der Applikation alle Metriken erfasst und an ein Monitoring-System innerhalb der Runtime gesendet. Dieses Monitoring-System wird von Flagger regelmäßig angefragt, um mit Hilfe dieses Wertes die Bestimmung der neuen Gewichtung anzupassen und das Canary Release ggf. zur neuen Version zu ernennen.
Zusammenfassung
Während viele Strategien eine Downtime verhindern, ermöglichen Canary Releases den Deployment-Prozess um Laufzeitmetriken zu erweitern. Dabei werden die Vorteile von Rolling-Update-Deployments ausgenutzt und durch eine weitere Laufzeitkomponente an die Analyse-Anforderungen angepasst. Werkzeuge wie Flagger ermöglichen Datenverkehr Steuerung innerhalb des Kubernetes Clusters um Applikationsversionen graduell für einige Nutzer auszurollen.
Natürlich sollte man sich immer Fragen, ob es die Komplexität Wert ist — aber grade im Continous Delievery Umfeld mit wenigen Umgebungen, ist das Canary Release sicherlich ein guter Pfeil im Köcher.
Links
https://docs.flagger.app/tutorials/nginx-progressive-delivery