Continuous Delivery mit Blue-Green Deployments

Eines der Ziele, die wir uns für die Lhotse Plattform gesetzt haben ist Continous Delivery: Änderungen sollen nicht nur kontinuierlich integriert, sondern auch rasch in Produktion genommen werden, sobald die Abnahme erfolgt ist. Wie wir das in der Praxis erreichen wollen, ist ein anderes Thema. Damit wir aber prinzipiell in der Lage sind, jederzeit eine neue Version live zu stellen, muss das System in der Lage sein, ohne Unterbrechung auf ein neues Release umzuschalten.
Wir haben den Livestellungsprozess mittlerweile über Jenkins Pipelines vollständig automatisiert. Über verschiedene Stages werden Änderungen permanent integriert und automatisiert getestet:

  1. Integration Stage: Alle Commits der letzten Minute werden gebaut, alle BDD-, Komponenten- und Unit-Tests werden ausgeführt, anschliessend werden die automatisierten Acceptance Tests (unter anderem Selenium Tests) ausgeführt. Ein Fehlschlag eines Tests führt zu einem „roten“ Build, also Abbruch und Fehlermeldung.
  2. Testing Stage: Im Stundentakt wird der letzte erfolgreiche CI-Build automatisch in der QA-Umgebung eingespielt. Hier wird das Deployment im Zusammenspiel mit anderen Teilsystemen automatisiert getestet. Sind diese Tests erfolgreich, erfolgt in dieser Umgebung auch die Abnahme von Stories durch die QA und die Product Owner.
  3. Prelive und Live Stage: QA und Product Owner wählen aus getesteten Deployments eines aus. Per „Knopfdruck“ wird dieses Deployment nach einem kurzen Zwischenstopp in einer „Prelive“ Umgebung Live gestellt. Die Prelive Stage ist eine Kopie der Live-Umgebung, auf der die exakte Kombination der Deployments ein letztes mal zusammen getestet werden kann.

Da wir einerseits jederzeit deployen können wollen, andererseits aber auch Tester oder Kunden unterwegs sind, haben wir für diese Stages ein unterbrechungsfreies „Blue-Green“ Deployment realisiert. Die Umgebungen sind dafür jeweils doppelt ausgelegt: in jeder Stage gibt es also aktive Server, die gerade in Verwendung sind, und passive Server, auf die das nächste Deployment eingespielt wird. Nach dem Deployment können Tests auf der passiven Hälfte ausgeführt werden, bevor der Load Balancer auf die neue Version umschaltet.

Blue-Green Deployment

Die beiden Hälften werden als „Blue“ und „Green“ bezeichnet. Ist beispielsweise zu irgendeinem Zeitpunkt „Blue“ aktiv, wird mit dem nächsten Deployment auf „Green“ umgeschaltet.

Die Umschaltung erfolgt in unserem Fall über einen vorgeschalteten Varnish, der sowohl Reverse Proxy als auch Load Balancing Funktionalitäten bietet. Varnish eignet sich für das Blue-Green Deployment ausgezeichnet, da man ihn über einen Befehl zwischen zwei Konfigurationen umschalten kann. Die eine Konfiguration enthält die „Blue“ Server, die andere die „Green“ Server, an die neue Requests verteilt werden. Sind bei der Umschaltung noch Requests in Bearbeitung, ist auch das kein Problem: sie werden vom Varnish mit der „alten“ Konfiguration verarbeitet.

Einer der großen Vorteile ist der unmittelbare switch des gesamten Clusters auf die neue Version: Zu keinem Zeitpunkt sind sowohl alte als auch neue Versionen im Cluster aktiv, so dass unsere Software auch nicht damit umgehen können muss, dass ein neues Feature auf der Hälfte der Clusterknoten bereits ausgerollt ist, auf der anderen Hälfte aber noch nicht.

Bei uns verwenden beide Hälften dieselbe Datenbank, so dass während der Umschaltung keine weiteren Maßnahmen zur Synchronisierung von Daten notwendig sind. Über den Umgang mit eventuellen Schemaänderungen der DB werden wir noch einen separaten Blog-Post schreiben – nur soviel: Schemafreie Document Stores sind hier eine sehr große Hilfe.

Hat man das Problem mit eventuellen Schemaänderungen gelöst, bekommt man ein weiteres Feature geschenkt: Die Möglichkeit, das Release auf den vorigen Stand zurückzusetzen. Da beide Releases noch auf „ihren“ Servern zur Verfügung stehen, kann man einfach und schnell wieder umschalten – was wir bisher allerdings noch nicht tun mussten.

Einer der Nachteile dieser Vorgehensweise ist recht offensichtlich: Wir benötigen doppelt so viele Server. Zur Zeit akzeptieren wir diesen Nachteil, diskutieren aber auch Alternativen, mit denen sich diese Mehrkosten teilweise vermeiden lassen:

  • In unserer Systemumgebung sind alle Server virtualisiert. Doppelte Server bedeuten dabei nicht zwangsweise doppelte Hardware: Da die passiven Server nur wenig Systemressourcen verbrauchen, lässt sich einiges einsparen wenn beispielsweise Hauptspeicher und CPUs „overcommitted“ werden. Da eine solche Konfiguration „unschön“ und schwierig zu verwalten ist, haben wir davon bisher abgesehen.
  • Aufgrund der Größe unserer Systemumgebung könnten wir auch einen Pool einrichten: Vor einem Deployment werden VMs aus dem Pool reserviert, dann erfolgt das Deployment. Nach einer gewissen Zeit wird die alte Umgebung dann wieder in den Pool zurückgegeben. Auch hier dürfte die Konfiguration recht aufwändig werden und die Frage ist, ob es nicht billiger wäre, die extra Hardware vorzuhalten. In Cloud-Umgebungen könnte dieser Ansatz allerdings gut funktionieren.
  • Etwas ernsthafter haben wir die Variante diskutiert, auf jedem Server zwei Application Server (in unserem Fall Tomcat) auf unterschiedliche Ports konfigurieren: ein Port wäre dann „Blue“, der andere „Green“. Auf diese Weise benötigt man nur etwas mehr Hauptspeicher pro Server – verliert aber die Möglichkeit, „mal eben“ eine neue Betriebssystemversion einzuspielen oder sonstige größere Änderungen vorzunehmen.

Insgesamt haben wir mit Blue-Green Deployments bisher sehr gute Erfahrungen gemacht. Die Deployments funktionieren reibungslos, es sind keinerlei Anpassungen in der Software notwendig (von dem Umgang mit DB-Schemaänderungen einmal abgesehen – das Problem muss man aber ohnehin lösen) und wir sind tatsächlich in der Lage, vollautomatisiert morgens um zehn in Produktion zu gehen – wenn alle Entwickler am Platz sind. Auch die Möglichkeit, jederzeit ein Rollback durchzuführen, ist eine vertrauensbildende Maßnahme:  Durch die Möglichkeit, sofort auf das vorige Release zurückzuschalten, traut man sich eher auch in einer heißen Phase zu deployen – was insgesamt zu vielen kleinen Deployments führt, von denen jedes einzelne sehr viel risikoärmer ist, als die großen, seltenen Deployments von „Major Releases“ – nächtlich durchgeführt und durch einen umfangreichen Prozess gesteuert.