Deployment, Continuous

Wie erreichen Entwicklerteams einen Release-Zyklus von nur wenigen Stunden, ohne dabei die Qualitätssicherung (QA) außer Acht zu lassen? Indem jede Code-Änderung von ihrer Entstehung in der Entwicklungsumgebung bis zu ihrem Einsatz im produktiven System automatisiert auf Einhaltung der Qualitätskriterien überprüft wird. Die Prüfung ist in mehrere Phasen unterteilt und wird durch das Konzept der QA-Pipeline modelliert. Jede Phase überprüft bestimmte Qualitätskriterien, neben funktionalen auch nicht-funktionale Anforderungen. Nur wenn die Code-Änderung alle Phasen erfolgreich durchlaufen hat, wird ein neues Software-Release vorbereitet und das Deployment durchgeführt.

Kent Beck nannte es vor Kurzem noch eine verrückte Idee: „Write one line of code. Deploy. [...] Once the deployment is done, write another line of code. Deploy.“ [1] Genau das ist der Stil, der jetzt im Web von vielen Teams gepflegt wird: Dazu zählen Flickr mit ca. 60 Deployments pro Woche, GitHub mit bis zu 40 Deployments am Tag oder Amazon mit einem Deployment alle 11,6 Sekunden [3, 6, 8].

Wie können Entwicklerteams derart kurze Release-Zyklen erreichen, ohne dabei auf die Qualitätssicherung zu verzichten? Die Antwort liegt in einem umfassenden, automatisierten Qualitätssicherungsprozess. In dessen Zentrum steht ein Feedback-Mechanismus, der den gesamten Weg einer Code-Änderung begleitet: von der Entwicklungsumgebung bis zum produktiven System. Continuous Deployment, kurz CD, beschreibt solche Mikro-Release-Zyklen, bei denen jede geänderte Programmzeile zur Auslieferung einer neuen, vollständigen und qualitätsgesicherten Version führt. Der Begriff wurde von Timothy Fitz popularisiert und sorgte für einige Aufregung in der Entwickler-Community [4].

QA-Pipeline

Das wichtigste Element von Continuous Deployment ist die QA-Pipeline: Sie bildet den zentralen Feedback-Mechanismus, der für den Einsatz von CD unerlässlich ist. Das Pipeline-Konzept entsprang der Praxisforderung, die ursprünglich getrennten Build- und Deployment-Vorgänge zu einer Einheit zusammenzuführen. Eingabe in die Pipeline ist dabei eine Code-Änderung, Ausgabe das komplette Anwendungssystem (Abb. 1).

Die Pipeline unterteilt sich in mehrere Phasen, die jeweils Feedback an die Entwickler geben. Mit jeder Phase nimmt die Granularität der Fehlererkennung ab: Unit-Tests prüfen die Funktionalität auf Klassenebene, benötigen dafür wenig Zeit und können die Fehlerquelle genau benennen. Performance-Tests dagegen dauern länger und die Fehlerursache bleibt eher unklar. Im Fehlerfall wird der Build-Prozess abgebrochen. Durch die „Fail-Fast“-Eigenschaft verstreicht keine wertvolle Zeit, nachdem ein Problem aufgedeckt wurde. Die Entwickler bleiben ständig im Bilde, wie es um die Qualität ihrer Software-Änderung steht. Je mehr Feedback-Kanäle dabei bedient werden, desto besser: Mails, RSS-Feeds oder eigene Visualisierungstechniken im Büro [4].

Eine QA-Pipeline gibt den Entwicklern die nötige Zuversicht, um Continuous Deployment anzuwenden. Das ist der entscheidende psychologische Punkt: „Trauen“ sich die Entwickler nicht, jede Software-Änderung in den produktiven Betrieb zu übernehmen, scheitert CD. Hier schließt sich auch die Praktik an, fehlerhafte Builds sofort zu beheben: Nur wenn ein Build intakt ist, kann er im Fehlerfall Rückmeldung geben; dieses Feedback geht sonst dauerhaft verloren. Ohne zuverlässige Rückmeldungen durch so viele Kanäle wie möglich kann CD kaum robust eingesetzt werden.

Commit und User Acceptance Tests

Unit-Tests bilden das Rückgrat eines QA-Prozesses, da sie die Software auf Code-Ebene innerhalb kürzester Zeit funktional prüfen. Sie werden in der ersten Phase direkt nach einem Commit ausgeführt und sollen mögliche Regressionen aufspüren. In der zweiten Phase werden die User Acceptance Tests angestoßen. Sie sollen sicherstellen, dass alle Use Cases oder User Stories auch nach der Änderung weiterhin fehlerfrei funktionieren. Auch diese, in der Regel über die Anwendungsoberfläche ausgeführten Tests sind automatisiert [5].

Nicht-funktionale Anforderungen

Über die Einhaltung aller nicht-funktionalen Anforderungen kümmern sich eigene Phasen: Für Performance-Messungen wird das Anwendungssystem gezielt unter Last gesetzt, um so Probleme bei hohem Benutzer- oder Datenaufkommen schon im Vorfeld erkennen zu können. Die Sicherheit kann durch Fuzzing-Tools analysiert werden, die Crash-Angriffe ausführen (Denial of Service, Buffer Overflow, Memory Leak) und durch Vulnerability Scans, die Sicherheitslücken aufspüren (SQL Injection, Cross-Site Scripting). Sowohl bei Performance- als auch bei Sicherheits-Tests kann ein Fehler-Level definiert werden, bei dessen Überschreitung der Build abgebrochen wird. Über Oberflächentests können zusätzlich Datenschutz-Aspekte der Anwendung getestet und in Form eines aktiven Verfahrensverzeichnisses release-spezifisch veröffentlicht werden. Hierbei wird im Live-System geprüft, wer in welcher Benutzerrolle personenbezogene Daten anderer User sehen kann, siehe das Beispiel der THM-Lernplattform: https://ecollab.thm.de/infos/datenschutz.php. In sehr begrenztem Maße sind außerdem automatisierte Usability-Tests möglich, zum Beispiel die Überprüfung der Tastaturnavigation [2].

Reporting

Software-Metriken müssen eine Konsequenz haben: Programmiervorgaben müssen eingehalten, Komplexitätswerte verringert und vereinbarte Qualitätsziele erreicht werden. Das funktioniert nur über Automatismen, die den Build-Prozess abbrechen, wenn die Metriken (zyklomatische Komplexität, Testabdeckung) zu stark von den Soll-Werten abweichen [7].

Deployment

Vor einem Deployment muss der Migrationsschritt (und genauso ein Rollback) überprüft werden. Das kann etwa durch Test-Deployments in einer produktionsnahen Umgebung geschehen. Das Deployment an sich kann über zwei Mechanismen erfolgen: automatisiert aus der Pipeline heraus oder über ein späteres, manuelles Ein-Klick-Deployment. Immer muss auch ein „Not-Aus“-Schalter vorhanden sein, mit dem ein Deployment wieder rückgängig gemacht werden kann.

Ein Deployment geschieht grundsätzlich in Gänze: Es gibt kein Deployment einzelner Komponenten, selbst wenn sonst nichts anderes verändert wurde. Andernfalls lässt sich die Komplexität nicht mehr beherrschen, da auf Grund möglicher Abhängigkeiten jede erdenkliche Kombination getestet werden müsste. Stattdessen wird ein Abbild der Gesamtanwendung erstellt, welches mittels Test-Deployment und Integrationstests überprüft und schließlich in die Produktion gebracht wird. Jedes Abbild steht somit für eine funktionierende Kombination aller Anwendungsteile. Im Problemfall genügt es daher, ein vorhergehendes Abbild wieder einzuspielen, um auf einen funktionierenden Stand zurückzukehren.

Strategien

Continuous Deployment erfordert, dass spezielle Maßnahmen ergriffen werden, um eine Änderung in den produktiven Betrieb zu übernehmen. Zunächst darf eine Website nicht durch das Deployment einer neuen Version beeinträchtigt werden; sie muss immer erreichbar bleiben. Dann muss geklärt werden, wie die Software auf die verschiedenen Server aufgespielt wird. Hier gibt es zwei Möglichkeiten, die sich nicht zuletzt auch in den Betriebskosten bemerkbar machen:

  •  Amazon verwendet eine komplett gespiegelte Infrastruktur, es ist also alles doppelt vorhanden: Webserver, Datenbank usw. Nur eine Infrastruktur ist aktiv und liefert Daten aus, während auf der anderen ein neues Deployment durchgeführt werden kann. Ist dieses geglückt, wird im Loadbalancer die aktive Infrastruktur gewechselt und die neue Software-Version ist fortan in Betrieb. Die „deaktivierte“ Infrastruktur steht für das nächste Deployment zur Verfügung und dient gleichzeitig als instantane Rollback-Möglichkeit auf die vorhergehende Version [6].
  • Eine kostengünstigere Alternative, da nur eine Infrastruktur benötigt wird, sind Feature-Schalter: Änderungen können selektiv aktiviert werden, etwa für bestimmte Benutzergruppen. So können zum Beispiel „Power-User“ frühzeitig als zusätzliche Tester eingespannt werden. Das Deployment kann dadurch über einen längeren Zeitraum (Rollout-Phase) Schritt für Schritt auf allen Servern durchgeführt werden, ohne dass alle Benutzer davon beeinträchtigt werden. Das wiederum erfordert, dass gleichzeitig mehrere Versionen der gleichen Software in Betrieb sind und diese koexistieren können. So ist es auch möglich, größere Releases durchzuführen: Viele kleine Änderungen wurden über einen längeren Zeitraum in das produktive System übernommen, sie wurden jedoch noch nicht offiziell freigeschaltet und blieben so für die meisten Benutzer unsichtbar [4].

Beide Deployment-Strategien können auch gemeinsam eingesetzt werden, sie schließen sich also nicht aus. Welche Strategie gewählt wird, hängt letztlich von den konkreten Anforderungen der Software ab.

Pipeline-Implementierung

Um ein zügiges Feedback zu ermöglichen, ist die Implementierung der QA-Pipeline entscheidend. Je länger der Durchlauf einer Software-Änderung dauert, desto weniger Deployments sind insgesamt möglich und desto länger dauert es auch, bis aus allen Phasen eine Rückmeldung bei den Entwicklern eingetroffen ist. Wichtig ist daher, die Pipeline so zu konstruieren, dass sowohl die einzelnen Phasen als auch die Pipeline insgesamt so schnell wie möglich durchlaufen werden.

Der Schlüssel liegt darin, so viele Abläufe wie möglich zu parallelisieren. Innerhalb der Phasen betrifft dies zunächst die Ausführung der einzelnen Testfälle, wodurch sich die Phasenlaufzeit verringern lässt. Doch auch die Phasen selbst können parallel gestartet werden; das ist zum Beispiel bei den nicht-funktionalen Anforderungen sinnvoll, da sie besonders lange Laufzeiten aufweisen können. Bei der parallelen Ausführung ist darauf zu achten, dass es bei einem Fehlschlag zum Abbruch der gesamten Pipeline kommt.

Grundsätzlich können immer solche Phasen parallel ablaufen, die unabhängig voneinander sind. Eine Abhängigkeit ergibt sich genau dann, wenn eine Phase genauere Testergebnisse liefert als eine andere, weswegen die Commit-Phase immer vor der UAT-Phase erscheint: Hier werden die funktionalen Aspekte auf verschiedenen Ebenen – vom Code bis zum Feature aus Kundensicht – überprüft. Andererseits steht die Report-Phase in keinem Zusammenhang mit diesen Phasen, weswegen sie parallel gestartet werden könnte.

Private Build

Der QA-Prozess sollte durch so wenige Fehler wie möglich unterbrochen werden. Jeder Fehlschlag bedeutet, dass ein Deployment weniger durchgeführt werden kann. Fehler gilt es daher zu minimieren. Deswegen muss es für die Entwickler eine Möglichkeit geben, den QA-Prozess „im Stillen“ aufzurufen. Das bedeutet, dass die Änderungen in einem Probelauf durch die QA-Pipeline geleitet werden, diese jedoch am Ende kein Deployment durchführt. Die Testergebnisse werden ausschließlich an den Entwickler ausgegeben, der die Ausführung veranlasst hat.

Ein Private Build erfolgt typischerweise auf dem lokalen Entwicklungsrechner. Die Testskripte, die von der QA-Pipeline aufgerufen werden, verstehen sich als Teil der Anwendung und es ist jedem Entwickler möglich, sie lokal auszuführen. Einschränkungen kann es zwar vereinzelt geben, etwa weil die Hardware für ressourcenintensive nicht-funktionale Tests ungenügend ist, aber für eine „Wasserstandsmeldung“ ist dies in der Regel ausreichend.

Hierdurch lassen sich frühzeitig Probleme bei der Zusammenführung von Änderungen verschiedener Entwickler erkennen: Wird vor dem Einchecken der Änderungen (und dem daraus resultierenden Start der QA-Pipeline) der Private Build ausgeführt, werden Probleme minimiert. Nach den alten Grundregeln der Continuous Integration wird so bereits im Vorfeld sichtbar, ob die eigenen Änderungen mit dem aktuellen Entwicklungsstand harmonieren. Dieses Prinzip gilt auch für größere Änderungen: Durch die Integration und spätestens bei Ausführung der Tests wird erkannt, ob Änderungen an Schnittstellen unerwartete Auswirkungen auf andere Programmteile haben.

Weitere Maßnahmen

Der Betrieb einer QA-Pipeline schließt nicht aus, dass noch weitere Maßnahmen zur Qualitätssicherung getroffen werden, sofern diese für den Betrieb nicht essentiell sind. Wenn zum Beispiel die Performance „systemrelevant“ ist, dann muss sie mit der Pipeline sichergestellt werden. Andernfalls ist es durchaus erlaubt, auch außerhalb der Pipeline – beispielsweise im nächtlichen Betrieb – QA-Aspekte zu überprüfen. Des Weiteren kann es bei großen Projekten sinnvoll sein, dass Module oder Komponenten eine eigene QA-Pipeline erhalten, deren Artefakte dann in einer Integrations-Pipeline zusammengeführt werden, die letztlich das Deployment durchführt.

Continuous Deployment sorgt für ein hohes Maß an Variabilität: Features können im Betrieb an- und abgeschaltet werden oder der Rollout ist noch nicht abgeschlossen. Dies erfordert, dass sämtliche öffentlichen Schnittstellen ständig abwärtskompatibel gehalten werden müssen. Andernfalls droht in den genannten Fällen der Systemausfall.

Probleme

  • Unvollständige Continuous-Deployment-Infrastruktur: Die nötigen Testwerkzeuge müssen für den spezifischen Bedarf der Anwendungssoftware ermittelt, orchestriert und schließlich auch gewartet werden. Allen voran die einzelnen Komponenten der Pipeline, aber auch die Infrastruktur für Test-Deployments und Lasttests müssen aufeinander abgestimmt betrieben werden. Hier ist besonders das Konfigurationsmanagement gefragt, um für eine einheitliche Infrastruktur zu sorgen.
  • Wartungsrisiko: Ein fehlerhaftes Wartungs-Update einer auf dem ersten Blick unkritischen Pipeline-Komponente kann dazu führen, dass die gesamte QA-Pipeline nicht mehr ordnungsgemäß funktioniert. Unter Umständen lässt sich dabei die Fehlerursache nicht unmittelbar erkennen.
  • Monitoring: Trotz aller QA-Maßnahmen kann es immer noch vorkommen, dass Fehler in den produktiven Betrieb gelangen. Diese müssen durch ein geeignetes Monitoring der Funktionalität der Anwendung und der vereinbarten Dienstgüten (Service Level Agreements) erkannt werden, bevor sie den Betrieb beeinträchtigen und möglicherweise Schäden verursachen. Auch hier müssen wieder alle Feedback-Kanäle bemüht werden, um die Entwickler auf die Fehler hinzuweisen.

Zusammenfassung

Continuous Deployment beschreibt einen Mikro-Release-Zyklus, bei dem jede Code-Änderung produktiv geschaltet wird. Dabei kommt der automatisierten Qualitätssicherung besondere Bedeutung zu: In mehreren Schritten wird die Anwendung überprüft und die Entwickler dabei über jeden Schritt informiert. 

Eine Live-Demo einer QA-Pipeline, umgesetzt mit dem Continuous-Integration-Server Jenkins, ist zu finden unter: https://scm.thm.de/pipeline.

Literatur

[1] Beck, K.: Continuous Deployment Immersion. 2010, http://www.threeriversinstitute.org/blog/?p=488, letzter Zugriff 23.5.2012

[2] Harty, J.: Finding Usability Bugs with Automated Tests. Commun. ACM 54, 2 (44-49) (2011)

[3] Holman, Z.: How GitHub Uses GitHub to Build GitHub. 2011 http://zachholman.com/talk/how-github-uses-github-to-build-github, letzter Zugriff 23.5.2012

[4] Humble, J.; Farley, D.: Continuous Delivery: Reliable Software Releases through Build, Test, and Deployment Automation. Addison-Wesley Professional, 2010

[5] Jackson, R.: Testmanagement: Professionelles Testen. Inf Spekt 32 (37-41) (2005)

[6] Jenkins, J.: Velocity Culture. O’Reilly Velocity Conference. 2011 http://blip.tv/oreilly-velocity-conference/velocity-2011-jon-jenkins-velocity-culture-5297293, letzter Zugriff 23.5.2012

[7] Thelen, C.: Qualitätssicherung von Software-Altsystemen durch automatisierte Verfahren. Master-Thesis. Technische Hochschule Mittelhessen, 2011

[8] Yahoo!, Hrsg.: code.flickr. http://code.flickr.com (Zugriffsdatum: 23.05.2012)

Autoren und Copyright

 Klaus Quibeldey-Cirkel, E-Mail 

und Christoph Thelen, E-Mail

Technische Hochschule Mittelhessen
Fachbereich Mathematik, Naturwissenschaften und Informatik

Wiesenstr. 14, 35390 Gießen

© Springer-Verlag 2012