Roadmap einer spannenden Reise

Voraussetzungen für Continuous Deployment in Unternehmen

Stephan Kaps

© Mooshny/Shutterstock.com

In Unternehmen existiert häufig eine umfangreiche Anwendungslandschaft. Dennoch besteht meist der Wunsch oder Bedarf, regelmäßig neue Versionen der Produkte produktiv zu setzen, um entweder neue Features auszuliefern oder Sicherheitslücken zu schließen. Welche Voraussetzungen für Continuous Deployment erfüllt sein müssen, wird anhand einer Roadmap in diesem Artikel vorgestellt. Dabei werden die Herausforderungen von Verfügbarkeits-, Sicherheits- und Qualitätsanforderungen angesprochen. Spezielles Augenmerk richten wir auf den Aspekt verteilter Verantwortlichkeiten, unabhängig von einer erfolgreichen Etablierung einer DevOps-Kultur.

Was Continuous Deployment betrifft, gibt es unterschiedliche Auffassungen. Eine der Definitionen besagt, dass Continuous Delivery in einer Vorproduktionsstufe ende, sodass Continuous Deployment die Fähigkeit sei, generell in Produktion deployen zu können, unabhängig davon, ob dies manuell per Knopfdruck oder automatisch geschähe (Abb. 1).

Nach einer alternativen Definition umfasst Continuous Delivery auch die manuelle Bereitstellung in der Produktion, während aber bei Continuous Deployment diese Bereitstellung stets automatisch erfolgt.

In diesem Artikel gehen wir von der zweitgenannten Definition aus, sodass nicht die Frage aufkommt, was „manuell per Knopfdruck“ bedeuten kann.

Abb. 1: Unterschied Continuous Delivery/Deployment

Schritt 1: Continuous Integration

Continuous Integration (CI) ist eine grundlegende Praktik in der (agilen) Softwareentwicklung. Durch immer kürzer werdende Release- und Produktzyklen ist es zwingend erforderlich, die Integration der Entwicklung kontinuierlich – bei jedem Commit – durchzuführen. Dadurch wird sichergestellt, dass der Code kompiliert werden kann und somit zu jedem Zeitpunkt eine funktionierende Software auslieferbar ist – und auftretende Fehler schnell an die Entwickler zurückgemeldet werden (Fast Feedback bzw. Fail Fast). Automatisierte Tests und Analysen helfen dabei, eine kontinuierlich hohe Qualität des Codes und der Anwendung sicherzustellen. Dabei spielen Continuous-Integration-Server wie Jenkins eine entscheidende Rolle.

Mindestens genauso wichtig ist das Branching-Konzept. Dabei hat sich Trunk Based bewährt, also ein zentraler Branch für alle Commits. Feature Branches sollten weitestgehend vermieden werden, um Merge-Konflikte zu verhindern und ständige Integration zu ermöglichen. Alternative Techniken, um länger andauernde Änderungen dennoch zu ermöglichen, sind unter „Schritt 2a“ und „Schritt 2b“ beschrieben.

Zusätzlich sollten Prüfungen auf Verwundbarkeiten von Abhängigkeiten durchgeführt werden. Im besten Fall werden des Weiteren dynamische Securitytests (DAST) durchgeführt, um Schwachstellen in der eigenen Anwendung frühzeitig zu entdecken.

Schritt 2: Continuous Delivery

Continuous Delivery (CD) als Erweiterung von CI verfolgt das Ziel, kleinere Änderungen an der Software schneller und häufiger auszuliefern. Dadurch wird das Risiko, das große Releases mit sich bringen, verringert und das Vertrauen in die eigene Lieferfähigkeit gesteigert.

Für CD gelten die gleichen Voraussetzungen wie für CI, das heißt, es müssen Alternativen für länger andauernde Änderungen eingeführt werden und es müssen automatisierte Tests existieren. Zusätzlich werden für ein automatisiertes Deployment eventuell Skripte benötigt, abhängig von der eingesetzten Technologie und Infrastruktur. Darüber hinaus kommt der Automatisierung von Datenbankänderungen eine wichtige Rolle zu. Dafür existieren Werkzeuge, wie z. B. Flyway, die auch beim Anspruch von Zero-Downtime bei Schemaänderungen durch Konzepte wie Two-phase Migrations unterstützen.

Voraussetzung für CD ist das Entwickeln einer Delivery Pipeline. Welches Werkzeug dafür verwendet wird, hängt von Vorlieben und eventuell anderen Rahmenbedingungen ab. Unabhängig davon ist es unabdingbar, eine geskriptete Pipeline zu haben, die wiederum mit dem Projekt versioniert wird, um eine Reproduzierbarkeit zu ermöglichen. Muss die Entwicklung viele Systeme betreuen, ist eine Einheitlichkeit ebenso wichtig (Unified Deployment). Zu guter Letzt kann eine Einführung von CD organisatorische und kulturelle Herausforderungen mit sich bringen, die deutlich schwieriger zu bewältigen sind als die technischen. Doch diese werden in diesem Artikel nicht weiter betrachtet, da das den Umfang sprengen würde.

Schritt 2a: Branch by Abstraction

Branch by Abstraction betrifft die Entwicklung bzw. die Anwendungspräparation und verfolgt das Ziel, inkrementelle Änderungen an einem bestehenden System durchführen zu können, während die Lieferfähigkeit erhalten bleibt. Diese Methode ist anwendbar, wenn das Team bereits mit Trunk-based Development vertraut ist. Für Änderungen an bestehenden Funktionalitäten wird eine Abstraktionsschicht entwickelt, die zwischen alter und neuer Implementierung agiert. Dadurch können unfertige Implementierungsstände als Dark Deployment einfach ständig mit ausgeliefert werden. Häufig handelt es sich bei diesen Änderungen um crossfunktionale Themen wie Performance, um Framework-Austausch oder größere Refactorings. Reale Tests können beispielsweise mit Hilfe eines Reverse Proxy erfolgen, der im Hintergrund einen Teil der Anfragen an die neue Implementierung leitet (Shadow Traffic). Alternativ wäre auch denkbar, den gesamten Live Traffic sowohl an die bisherige als auch an die neue Implementierung zu leiten.

Dieses Vorgehen ist auch unter den Namen Parallel Change oder Change by Abstraction (Refactoring) bekannt und funktioniert am besten in Kombination mit Feature Toggles, mit denen zwischen alter und neuer Implementierung gewechselt werden kann. Detaillierte Informationen zum Ablauf beim Einsatz dieser Methode stehen bereit.

Schritt 2b: Feature Toggles

Feature Toggles sind eine Technik zur Entkopplung eines Deployments von einem Release und stellen somit eine Alternative zu Feature Branches dar. Diese Technik beschleunigt die Entwicklung, da keine Branches und Merges erforderlich sind. Es können dadurch viele kleine inkrementelle Versionen einer Software bereitgestellt werden, weil es Entwicklern ermöglicht wird, neue oder unvollständige Funktionen zu verstecken, sodass sie nicht in der Benutzeroberfläche erscheinen. Einige nennen das In-Code Branching.

Es gibt Frameworks wie togglz, die eine komfortable Benutzeroberfläche bieten, um alle Schalter zu verwalten, zu aktivieren oder zu deaktivieren – vielleicht verbunden mit einer speziellen Releasestrategie. Dabei ist es möglich, eine Funktion beispielsweise nur für wenige Benutzer bereitzustellen, z. B. ausgewählt nach Name, IP-Adresse, Standort, oder schrittweise mit wenigen Prozenten (siehe auch Canary Deployment). Mit diesem Ansatz kann A/B Testing durchgeführt werden, was bedeutet, dass eine Funktion nur für eine Teilmenge der Benutzer zum Ausprobieren aktiviert wird, um zu prüfen, ob die Änderung akzeptiert wird. Wenn ein Feature Fehler verursacht oder einfach nicht so funktioniert, wie es sollte, wird der Schalter deaktiviert und die Entwickler können an einem neuen Inkrement für die Bereitstellung arbeiten. Ein solches Verhalten wird als fix forward bezeichnet und bedeutet, dass es keine Notwendigkeit mehr für Rollbacks gibt. Das kann viel Zeit sparen, denn es kann viel Arbeit bei der Vorbereitung von Datenbankskripten bedeuten, jederzeit zu ermöglichen, auf die letzte Version zurückgehen. Schließlich können Funktionsschalter dafür verwendet werden, eine Anwendung in den Wartungsmodus zu versetzen.

Entwickler müssen den Einstiegspunkt eines Features mit einem if-else-Konstrukt in ihrem Code (Listing 1) umschließen.

if (Features.ENABLE_IMPORT.isActive()) {
  LOG.info("IMPORT gestartet");
  try {
    Future<Boolean> future = importService.holeDatenVonOnlineAntrag();
  ...
} else {
  LOG.info("IMPORT nicht möglich");
}

Ein Beispiel dafür, wie in JSF ein Pie Chart auf einem Dashboard per Toggle gerendert wird, ist nachfolgend zu sehen:

<div class="Container50 Responsive50">
  <p:chart rendered="#{features['VORGAENGE_EINBLENDEN']}" id="pieVorgangsstatus" type="pie"
    model="#{dashboardBean.pieVorgangsstatus}" responsive="true" />
</div>

Ein Codeblock, der per Schalter ausgeschaltet wurde, ist vergleichbar mit auskommentiertem Code.

Es ist wichtig, darauf zu achten, die Schalter wieder auszubauen, nachdem sie nicht mehr benötigt werden, da ansonsten toter Code entsteht, der zu neuen technischen Schulden führt. Feature Toggles können in einer Datenbank oder als Eigenschaft in einer Konfigurationsdatei gespeichert werden.

Schritt 3: Zero-Downtime

Die folgenden Praktiken gehören zur Kategorie Releasestrategien und verfolgen das Ziel, Deployments ohne Ausfallzeiten zu ermöglichen.

Schritt 3a: Blue-Green Deployment

Blue-Green Deployment (Abb. 2) ist eine Releasepraxis, bei der zwischen zwei Produktionsumgebungen gewechselt wird, die blau und grün heißen. Auf einer zweiten Umgebung wird eine neue Version einer Software installiert und getestet, ohne dass die Anwender davon etwas mitbekommen. Wurde die neue Version erfolgreich getestet, kann der Router umkonfiguriert werden, sodass ab jetzt alle Anfragen an die Umgebung mit der neuen Softwareversion weitergeleitet werden. Die erste Umgebung wird im Anschluss aus dem Routing herausgenommen. Geht etwas schief, kann auf die grüne Umgebung gewechselt werden, in der die letzte stabile Version läuft. Das Hauptziel dieses Ansatzes ist die Risikominderung. Es handelt sich dabei normalerweise um eine Alles-oder-Nichts-Freigabestrategie für neue Versionen.

Die Hauptherausforderung dieses Ansatzes ist die Notwendigkeit, zwei komplette Produktionsumgebungen zu betreiben, was in Bezug auf Kosten und Aufwand sehr teuer werden kann. Dieses Verfahren ist für kleine Änderungen, insbesondere wenn sie mehrmals pro Woche oder pro Tag releast werden sollen, eher schwierig und übertrieben. In diesem Fall können eventuell Feature Toggles ausreichen. Dennoch kann es sinnvoll sein, Blue-Green Deployments mit Feature Toggles zu kombinieren, um Major-Versionen einer Anwendung zu veröffentlichen. Schließlich ist das in erster Linie eine Releasestrategie für den IT-Betrieb, während Feature Toggles die Kontrolle über die Freigabe für die Entwicklung oder den Product Owner ermöglichen.

Abb. 2: Blue-Green Deployment

Schritt 3b: Canary Deployment

Mit Canary Deployments (Abb. 3) kann eine neue Version einer Anwendung für eine Teilmenge der Benutzer bereitgestellt werden, während das Verhalten überwacht wird. Wenn etwas schiefgeht, können diese Benutzer wieder auf die alte Version umgeleitet werden. Wenn die neuen Änderungen wie erwartet funktionieren, werden die Änderungen auf den Rest der Benutzer ausgerollt.

Der Name der Strategie kommt von der Praxis, Kanarienvögel in Kohlebergwerken einzusetzen, um Kohlenmonoxid und andere giftige Gase zu erkennen, bevor sie Menschen gefährden.

Diese Strategie kann mit Load Balancern umgesetzt werden, indem beispielsweise 95 Prozent des Datenverkehrs in die Umgebung A und fünf Prozent in die Umgebung B geleitet werden. Während eine Umgebung aktualisiert wird, wird sie aus der Rotation genommen.

Ähnlich wie bei Blue-Green Deployments besteht Bedarf an einer duplizierten Infrastruktur – mit dem Unterschied, dass sie bei Canary nur temporär genutzt wird. Die neue Infrastruktur muss für die neue Version bereitgestellt werden, während die Infrastruktur der alten Version stillgelegt werden kann, nachdem der gesamte Verkehr auf die neue Version umgeleitet wurde.

Manchmal wird dieser Ansatz als schrittweiser oder inkrementeller Rollout bezeichnet. Zusätzlich ist damit ein A/B Testing möglich, bei dem zwei verschiedene Varianten einer Funktionalität ausgerollt werden, um zu überprüfen, ob die Version B im Vergleich zur Version A z. B. höhere Klickraten, eine längere Verweildauer auf der Webseite oder gar höhere Verkaufszahlen generiert. Möglicherweise muss der Ansatz der parallelen Änderung verwendet werden, um mehrere Versionen der Software parallel zu unterstützen, insbesondere bei Datenbankänderungen.

Abb. 3: Canary Deployment

Schritt 4: Secrets Management

Bei einem klassischen Vorgehen mit geteilten Verantwortlichkeiten werden in der Regel vor dem Deployment die entsprechenden Zugangsdaten zu produktiven Datenbanken, Servern oder anderen Systemen manuell von IT-Betriebsmitarbeitern an die entsprechenden Stellen in den Konfigurationsdateien gesetzt. Um den Weg in Richtung DevOps einschlagen zu können, muss das Ziel sein, Zugangsdaten zu verstecken – sowohl vor den Entwicklern, die jetzt Anwendungen produktiv setzen können, als auch generell vor System- und Datenbankadministratoren aufgrund erhöhter Sicherheitsanforderungen.

Für Secrets Management in dynamischen Infrastrukturen gibt es inzwischen sehr gute Werkzeugunterstützung wie Hashicorp Vault. Dabei handelt es sich um einen digitalen Schlüsselkasten, geschrieben in Go mit vielen weiteren Funktionalitäten. Allgemein geht es um die geheime Aufbewahrung und den sicheren Zugriff auf Secrets. Dabei kann es sich um die Ablage von Schlüsselmaterial für die Inhaltsverschlüsselung von gesendeten und empfangenen Daten handeln, um vertrauliche Umgebungsvariablen, Zertifikate, Datenbankzugriffsdaten oder API Keys.

Die Geheimnisse werden über ein API abgefragt und die Klartextversion verlässt dabei nie den Vault. Der Zugriff ist nur für autorisierte und authentifizierte User oder Applikationen möglich, was wiederum über Policies konfiguriert wird. Jeder Zugriff auf Geheimnisse wird zusätzlich aufgezeichnet (Audit). Die Schlüssel haben eine begrenzte Gültigkeit und können automatisch erneuert werden, sogenanntes Key Rolling. Für Datenbanken beispielsweise können dynamische Secrets erzeugt werden, die nur für den einen Zugriff gültig sind. Verwendet man Vault für die Ablage von Secrets vieler Systeme, wird Vault selbst zu einem kritischen System bezüglich der Verfügbarkeit. Vault selbst wird mit mindestens drei Instanzen betrieben und kann als Backend ein Consul Cluster verwenden, um die Hochverfügbarkeit sicherzustellen. In einer zukünftigen Version bringt Vault sein eigenes Backend mit und löst dadurch diese Abhängigkeit auf, was den Betrieb und Updates weiter vereinfacht.

Durch den Einsatz von Vault werden weitere bekannte Schwachstellen behoben, wie z. B.:

  • Passwörter werden selten oder nie geändert
  • Manuelles Rotieren von Passwörtern und manuelles Austauschen an den hartcodierten Stellen
  • Niemand weiß, wer Zugriff auf was hat und wann
  • Zertifikate haben eine lange Lebensdauer

Vault bietet weitere Funktionen wie Encryption as a Service, wodurch Verschlüsselung und Entschlüsselung von Daten über das API durchgeführt werden, sodass die notwendigen Keys den Tresor niemals verlassen müssen.

Lesen Sie auch: Frühjahrsputz: Tipps und Tricks für mehr Struktur im Deployment-Prozess

Schritt 5: Security und Compliance

Die folgenden Praktiken haben zum Ziel, eine erhöhte Sicherheit für bereits containerisierte Anwendungen bzw. Microservices-Architekturen automatisiert zu gewährleisten.

Schritt 5a: Container Security

Da nun kontinuierlich Systeme ohne jegliche manuelle Kontrolle produktiv gehen können, sind weitere Sicherheits- und Compliancemaßnahmen erforderlich. In dynamischen bzw. verteilten Infrastrukturen kommen in der Regel Container zum Einsatz. Es muss sichergestellt werden, dass nicht ungewollt fremde Container aus unbekannten Quellen oder manipulierte Images mit Malware den Weg in die Produktion schaffen. Des Weiteren sollen keine Container betrieben werden, deren Images bekannte Verwundbarkeiten beinhalten. Container-Images nach Verwundbarkeiten zu durchsuchen, ist mit dem Vorgehen bei Third-Party Libraries in Java vergleichbar, die beispielsweise mit Hilfe des OWASP Dependency Check analysiert werden können. Mit Clair existiert ein äquivalentes Open-Source-Werkzeug, um Container-Images gegen die Einträge der NIST-Datenbank zu verifizieren.

Eine Compliance-Richtlinie sollte sein, nur offizielle bzw. signierte Images zu verwenden. Die offiziellen Produkt-Images der Hersteller im Docker Hub sind signiert, wie z. B. Images von NGINX, Couchbase, Redis oder alpine. Durch diese Richtlinie ist es allerdings erforderlich, die eigenen Images und unbekannte, heruntergeladene, aber manuell geprüfte Images ebenfalls zu signieren, um deren vertrauenswürdige, sichere Herkunft zu signalisieren. Diese Maßnahme nennt sich Content Trust und wird durch Werkzeuge wie Docker Notary tooltechnisch unterstützt. Durch ein mehrfaches Schlüsselverfahren werden Autoren wie Entwickler oder Build-Systeme in die Lage versetzt, Images zu signieren. Diesen als Publisher bekannten Autoren wird wiederum vertraut.

Projekt Harbor [9] ist eine Open-Source-Cloud-native-Container-Registry, die on premise betrieben werden kann und Vulnerability Scans mit Clair und Content Trust per Docker Notary integriert. Das wiederum vereinfacht erheblich den Betrieb, da auch mit Hilfe eines einzelnen Docker Compose Files alle Komponenten (in der Summe 13) in einzelnen Containern gestartet werden. Über eine komfortable Benutzeroberfläche kann konfigurativ verhindert werden, dass Images gepullt werden können, die Verwundbarkeiten enthalten und/oder nicht signiert sind.

Im Rahmen eines umfassenderen Standards für Containersicherheit ist eine solche Registry mit integriertem Schwachstellenscan nur ein Baustein, wenn auch ein sehr wichtiger. Bei der OWASP ist inzwischen ein Container-Security-Verification-Standard (CSVS) mit insgesamt 106 Empfehlungen, unterteilt in 12 Kategorien und jeweils drei Stufen, in Version 1.0 veröffentlicht worden, die man als Einsteiger-, Fortgeschritten- und Expertenlevel beschreiben kann. Dieser Standard eignet sich sehr gut als Checkliste, um bewährte Sicherheitsmaßnahmen zu etablieren.

Schritt 5b: Inter-Service-Kommunikation

In klassischen Anwendungslandschaften konnten IP-Adressen und Ports in Firewalls einmalig freigeschaltet werden. Load-Balancer-Konfigurationen und Routen im DNS waren statisch. In Cloudinfrastrukturen, egal ob private, public oder on premise, wird diese statische Infrastruktur zu einer dynamischen. Services werden nach einem Ausfall oder aufgrund von Skalierungsanforderungen an diversen Lokationen mit vorher unbekannten IPs neu gestartet.

Ein manuelles Ändern von Firewall-Regeln oder Loadbalancer-Einstellungen infolge eines Change Request ist dann nicht mehr angemessen. Die Wartezeiten wären zu lang und die Häufigkeit der Änderungen zu hoch. Aufgrund der steigenden Zahl von Services in einer Microservices-Architektur steigt auch die Zahl der Regeln und Routen, was nach kurzer Zeit zu unübersichtlichen und nicht mehr wartbaren Regelwerken und Konfigurationen führen würde. Darüber hinaus kommunizieren die Services auch untereinander, sodass neben einer Nord-Süd- auch eine Ost-West-Kommunikation abgesichert werden muss. In einem einfachen Szenario (Abb. 4), bestehend aus zwei Containern (wobei im ersten Container die Webanwendung läuft und im zweiten eine Datenbank), soll sichergestellt werden, dass der Container inklusive Webanwendung mit dem Container kommunizieren darf, in dem die Datenbank betrieben wird, aber nicht umgekehrt.

Eventuell ist bereits HashiCorp Consul im Einsatz für Service Discovery und Service-Konfiguration. HashiCorp baut darauf auf und bietet nun seit Version 1.4 erweiterte Funktionalitäten (Connect), um bestehende Installationen in Richtung Service Mesh zu entwickeln. Eine dieser Funktionalitäten sind Intention Policies, durch die geregelt wird, dass beispielsweise Container der Gruppe webapp auf Container der Gruppe webdb zugreifen dürfen, jedoch nicht umgekehrt (Abb. 4):

$ consul intention create -deny webdb webapp
Created: webdb => webapp (deny)

Die Umsetzung erfolgt dabei mit automatisch gestarteten Envoy-Containern, die als Service Proxy agieren.

Zusätzlich können diese Kommunikationsbeziehungen mit Mutual TLS verschlüsselt werden. Die dafür notwendigen Zertifikate werden automatisch ausgestellt, entweder von Consul selbst oder, bei Anbindung an Vault, von der dort hinterlegten firmeneigenen PKI.

Abb. 4: Inter-Service-Kommunikation

Final Destination: No Test Environments

Besteht der Wunsch, die Geschwindigkeit beim Deployment zu erhöhen, müssen bisher durchgeführte Aktivitäten wegfallen, beispielsweise die Installation und Bereitstellung von Testumgebungen. Durch die Einführung von Feature Toggles kann jederzeit in die Produktion deployt werden, die Features werden aber erst später oder nur für wenige Testuser freigeschaltet. Eventuell ist es dann lediglich notwendig, eine Umgebung zum Testen des Deployments selbst zu pflegen.

Werden weiterhin explizite Freigaben für Features verlangt, ist eine Alternative, automatisch neue Umgebungen hochzufahren, in denen dann das jeweilige Feature zur Abnahme bereitgestellt wird. Dadurch wird eine weitere Unabhängigkeit erreicht und selbst in einem Umfeld, in dem Freigaben noch manuell erfolgen, eine hohe Releasegeschwindigkeit ermöglicht.

Fazit

In der in diesem Artikel aufgeführten Roadmap wurden einige Punkte betrachtet, die für die Einführung von Continuous Deployment in einem Unternehmen relevant sind. Auf eine Beschreibung von weiteren Aspekten wie beispielsweise Infrastructure as Code, um auch die notwendige Infrastruktur jederzeit automatisiert provisionieren zu können, wurde aus Platzgründen verzichtet. Darüber hinaus sind in speziellen Betriebsumfeldern wahrscheinlich zusätzlich andere Maßnahmen erforderlich.

Es existieren bereits diverse Continuous-Delivery-Reifegradmodelle, wie beispielsweise hier, die Anhaltspunkte für den eigenen Status und die noch durchzuführenden Aktivitäten beinhalten.

Die Etablierung von Continuous Deployment in einer gewachsenen Organisation ist eine Herausforderung. Am Ende wird man aber mit einer massiv erhöhten Produktivität und einem enorm gesteigerten Vertrauen in das eigene Produkt und die Lieferkette belohnt. Das wiederum führt zu sinkenden Risiken und somit zu weniger Stress und mehr Zufriedenheit bei allen Projektbeteiligten.

Ich wünsche viel Erfolg auf dieser spannenden Reise.

Geschrieben von
Stephan Kaps
Stephan Kaps
Stephan Kaps leitet die Softwareentwicklung im Bundesversicherungsamt und ist Gründer der Java User Group Bonn. Als Softwarearchitekt und Entwickler hat er seit 2002 mit Java zu tun.
Kommentare

Hinterlasse einen Kommentar

avatar
4000
  Subscribe  
Benachrichtige mich zu: