Twelve-Factor-Apps und Infrastructure-as-Code auf Amazon Web Services (AWS)

In der Softwareentwicklung gibt es Sammlungen an Methoden und Entwurfsmustern, um immer wiederkehrende Problemstellungen auf eine reproduzierbare und effiziente Weise zu lösen. Ein Beispiel ist die "Twelve-Factor-App"-Methode zum Erstellen von resilienten und skalierbaren Web-Anwendungen. Verglichen mit der Softwareentwicklung ist das Konzept von Infrastructure-as-Code noch relativ neu. Mit diesem Ansatz wird der Technologie-Stack oder das Rechenzentrum im Allgemeinen, als Code abgebildet. Die Infrastruktur wird programmierbar.
In diesem Artikel wenden wir die Prinzipien der Twelve-Factor-App auf Infrastructure-as-Code am Beispiel von Amazon Web Services (AWS) an. Dabei stellen wir fest, dass die Best Practices aus der Softwareentwicklung sich weitestgehend auf Infrastructure-as-Code anwenden lassen. Das Ergebnis ist eine Sammlung von empfohlenen Vorgehensweisen bei der Programmierung der IT-Infrastruktur um zu resilienten, skalierbaren und wartbaren Lösungen zu gelangen.
Computer und Computerprogramme halten Einzug in immer mehr Lebensbereiche. Server, Laptops, Smartphones und zunehmend auch Smarte Devices und Wearables verfügen über Microchips, denen mithilfe von Software Leben eingehaucht wird. Es gibt also immer mehr Software, deren Komplexität zudem stetig zunimmt. Gleichzeitig wirken wirtschaftliche Faktoren wie Time to Market und damit verbundene Agilität auf den Softwareentwicklungsprozess ein. In diesem Spannungsgebiet der Softwareentwicklung haben sich Best Practices entwickelt, um auf immer schneller wechselnde Marktanforderungen zu reagieren, ohne dabei Eigenschaften der Software wie Sicherheit, Wartbarkeit oder Fehlertoleranz zu vernachlässigen. Auf der einen Seite gibt es agile Prozessmodelle wie Extreme Programming oder Scrum, welche Vorschläge zum Entwicklungsprozess machen. Auf der anderen Seite gibt es Best Practices oder auch Architekturen, wie die Software selbst aufgebaut sein sollte. Das aktuell wohl populärste Beispiel einer solchen Architektur stellen Microservices dar. Anwendungen werden in sinnvolle Teile aufgetrennt, welche in sich konsistent und leichter zu warten und entwickeln sind. Die 12-Factor-App-Methodik ist eine Konkretisierung dieser Microservice-Architektur für Web-Applikationen und beschreibt in zwölf Prinzipien, wie moderne Software für das Web aufgebaut sein sollte [1].
Parallel zur Entwicklung der Software-Methodiken kann man in Rechenzentren einen Trend zur Virtualisierung und Programmierbarkeit von Infrastruktur beobachten. In diesem Zusammenhang sind Software defined networking, -Storage oder auch -Datacenter geläufige Begriffe. Cloudanbieter wie AWS, welche eine Programmierschnittstelle (API) zur Verwaltung solcher Ressourcen als integralen Bestandteil des Dienstes anbieten, haben diesen Trend noch beschleunigt. Infrastructure-as-Code-Technologien (IaC) vereinfachen die Programmierung der Infrastruktur. Dies geschieht durch Abstraktionen der APIs oder das Konzept der deklarativen Beschreibung der gewünschten Zielinfrastruktur. So kann beispielsweise in IaC deklariert werden, dass ein virtueller Server benötigt wird. In welcher Reihenfolge und welche Programmierschnittstellen genau aufgerufen werden müssen, ist dann aber Sache der IaC-Technologie. Der Einsatz von Infrastructure-as-Code bietet viele Vorteile: der Aufbau der Infrastruktur wird reproduzierbar, da derselbe Code mehrfach ausgeführt werden kann. Ebenso leicht lassen sich ausgerollte Infrastrukturen auch wieder löschen. Damit steigt die Agilität, Experimente werden einfacher und Konzepte können schneller validiert werden.
Große Cloudanbieter bieten jeweils eigene IaC-Technologien an, welche spezifisch an die eigenen Plattformen angepasst. Bei AWS ist dies AWS CloudFormation [2], welches Infrastrukturprojekte im JSON- oder YAML-Format definiert und als Stacks ausrollt. Ein Kritikpunkt an CloudFormation ist, dass die Ressourcen explizit definiert werden müssen, was die CloudFormation-Templates recht groß werden lässt. Ein anderer Ansatz stellt das AWS Cloud Development Kit (AWS CDK) [3] dar, welches die Definition der Ressourcen mit gängigen Programmiersprachen wie Java, TypeScript oder Python erlaubt und den Code zu CloudFormation-Templates umwandelt. Ein wichtiger Vorteil des AWS CDKs sind die Contructs: diese fassen Low-level-Komponenten zusammen und verringern die Komplexität durch sinnvolle Standardeinstellungen. Eine weitere sehr verbreitete IaC-Technologie stellt Terraform dar. Terraform existiert als kommandozeilenbasierte Open-Source-Version, kann jedoch auch als Enterprise-Version via Web-Interface verwendet werden. Das Besondere an Terraform ist, dass Resource Provider für die verschiedenen Cloud-Anbieter existieren. Zwar können Terraform-Projekte nicht einfach zwischen den Providern migriert werden, jedoch kann mit allen Providern dasselbe Kommandozeilen-Interface verwendet werden, sodass nur ein Werkzeug erlernt werden muss.
Da wir nun unser Rechenzentrum mit Code, also als Software definieren können, liegt es nahe, Best Practices aus der Softwareentwickung zu nehmen und zu beleuchten, inwiefern diese auf IaC angewendet werden können. Als Grundlage dient uns die 12-Factor-App-Methodik – dieses Gedankenexperiment kann und sollte aber auch mit anderen Best Practices aus der Softwareentwicklung durchgeführt werden.
1. Codebase
Der erste Vorschlag im 12-Factor-Manifest bezieht sich auf die Verwendung eines Versionsverwaltungssystems wie beispielsweise Git und die Verwaltung des gesamten IaC-Projektes in einem Repository. Damit können Änderungen am Code nachvollzogen werden, parallele Entwicklungsstränge verfolgt und bestimmte Versionen explizit mit Bezeichnungen (Tags) versehen werden. Über Commit Messages können Änderungen mit Titeln und Beschreibungen versehen werden, sodass auch nach längerer Zeit nachvollziehbar ist, welchen Zweck die Änderung hatte. Auch Fragen wie "Wie sah meine Infrastruktur vor vier Monaten aus?" lassen sich durch einen Blick in die entsprechende Code-Version beantworten. Hat man früher versucht, mit Configuration Management Databases (CMDB) einen Überblick über seine Ressourcen im Rechenzentrum zu behalten – und ist oft aufgrund des zu hohen Aufwands zur Pflege gescheitert – stellt der Code im IaC-Projekt eine verlässlichere Informationsquelle dar.
Code Reviews werden häufig als Teil des Entwicklungsprozesses eingesetzt um Fehler aufzuspüren und insgesamt die Qualität des Codes zu verbessern. Gleichzeitig sind Code Reviews ein tolles Instrument für den Wissensaustausch im Team. So können erfahrene Entwickler:innen Tipps und Best Practices an die Kolleg:innen weitergeben. Neben manueller Code Reviews gibt es auch automatisierte Werkzeuge (Linter), welche den Code auf Einhaltung von definierten Regelwerken überprüfen. Ergebnisse aus manuellen und automatischen Reviews können in Ticketing Systemen nachverfolgt, priorisiert und bearbeitet werden.
Parallel zur Forderung, dass eine Applikation aus nur einer Codebase bestehen soll, die aber durchaus mehrfach instanziiert werden kann, können mit IaC Infrastrukturmodule unabhängig voneinander erstellt werden. Diese können mehrfach instanziiert werden um z. B. eine Staged-Deployment-Pipeline zu implementieren oder Infrastukturkomponenten wiederzuverwenden.
2. Abhängigkeiten
Der zweite Faktor ist das explizite Definieren von Abhängigkeiten, um Inkompatibilitäten zu verhindern und ein reproduzierbares Deployment zu ermöglichen. Bei der Softwareentwicklung werden häufig verwendete Funktionen und Algorithmen als Bibliotheken gebündelt und können anschließend in anderen Anwendungen eingebunden werden. Wenn man diese Abhängigkeiten explizit definiert, verhindert man unerwartete Überraschungen bei System-Updates oder bei Änderungen in der Laufzeitumgebung.
Vergleichbare Abhängigkeiten zu externen Komponenten gibt es auch bei IaC. Einerseits gibt es die Programmierschnittstelle der jeweiligen Cloud, die versioniert ist. Hier sollten explizite API-Versionen angegeben werden. Darüber hinaus verfügen IaC-Technologien über die Möglichkeit, häufig verwendete Teile in Code-Bausteine auszulagern, um ständige Wiederholungen zu vermeiden. Bei AWS CloudFormation verwendet man nested templates. In Terraform gibt es hierfür Modules. Beim AWS Cloud Development Kit existiert das Konzept der Constructs. Diese externalisierten Module sollten mit expliziten, unveränderlichen Versionen versehen sein. Bindet man nun in seinem IaC-Projekt eine bestimmte Version eines solchen Bausteins ein, kann man sich sicher sein, dass diese Abhängigkeit auch nach einiger Zeit weiterhin funktioniert. Ebenso wichtig ist es, wirklich alle Abhängigkeiten zu externen Ressourcen abzubilden. Sonst läuft man Gefahr, dass die Instanziierung eines IaC-Projektes in einer neuen Umgebung fehlschlägt, da implizite Annahmen nicht mehr zutreffen.
3. Konfiguration
Faktor drei stellt die strikte Trennung der Konfiguration von der eigentlichen Anwendung dar. Um Konfigurationsparameter zu ermitteln, kann man sich fragen, welche der Einstellungen einer Anwendung geändert werden müssen, wenn diese Anwendung beispielsweise für Testzwecke in einer neuen Umgebung bereitgestellt werden soll. Darunter befinden sich häufig URLs, Verbindungsstrings sowie Zugangsdaten zu Drittsystemen oder Datenbanken. Solch eine Parametrisierung des IaC-Projektes sorgt dafür, dass neue Umgebungen schnell und mit identischem Aufbau innerhalb kürzester Zeit aufgesetzt werden können. Doch wohin mit der Konfiguration, wenn sie nicht im Code des IaC-Projektes gehalten werden soll? Bei 12-Factor-Anwendungen werden häufig Umgebungsvariablen des Betriebssystems benutzt, welche von der Anwendung referenziert werden. Das Äquivalent hierzu stellt der AWS Systems Manager [4] Parameter Store dar, welcher es erlaubt, Konfigurationen für Anwendungen und IaC-Projekte innerhalb von AWS zentral und sicher zu verwalten. Gibt es für das Handling von Passwörtern und anderen vertraulichen Konfigurationsparametern prozessuelle Anforderungen, wie z. B. ein regelmäßiger Passwortwechsel, kann AWS Secrets Manager [5] eingesetzt werden. Im IaC-Projekt werden anschließend nur noch Referenzen auf die Konfiguration hinterlegt.
4. Unterstützende Dienste
Das vierte Prinzip verlangt, dass eine 12-Factor-Applikation alle seine unterstützenden Dienste als angehängte Ressourcen behandelt. Doch was genau ist ein unterstützender Dienst? Im 12-Factor-Verständnis sind dies beispielsweise Datenbanken, externe APIs oder auch SMTP Server. Diese Dienste werden zur Erbringung der Applikation benötigt. Sie sind jedoch nicht Bestandteil der Applikation. Sie werden über Parameter in der Konfiguration adressiert und sind somit nur lose gekoppelt und austauschbar. Dies ist beispielsweise dann hilfreich, wenn eine Datenbank durch eine leistungsfähigere Instanz ausgetauscht werden soll.
Zunächst einmal kann man diese Forderung direkt für den Aufbau seines IaC-Projektes übernehmen: die Kernanwendung und unterstützende Dienste werden separat definiert und über Referenzen miteinander verbunden. Die Definition der unterstützenden Dienste sollte parametrisiert werden, damit beispielsweise die Größe einer Datenbank später geändert werden kann. Gleichzeitig erlaubt dies auch eine kosteneffizientere Entwicklung des Projekts durch den Einsatz kostengünstigerer Varianten einer Ressource in Entwicklungs- und Staging-Umgebungen.
Es muss jedoch klar sein, dass lose Kopplung und Parametrisierung meist mit höheren Aufwänden einhergeht. Ebenfalls kann dadurch die Wartbarkeit des Codes leiden, da zusätzliche Indirektionen eingebaut werden. In diesem Zusammenhang sei das Konzept der "losen Kopplung und starken Kohäsion" aus der Softwareentwicklung genannt: innerhalb eines Moduls existiert eine starke Bindung zwischen den Komponenten, während die Verbindung zu anderen Modulen lose ist und häufig über wohldefinierte Schnittstellen abgebildet wird. Dies ermöglicht die Rekombination verschiedener Module und erhöht die Flexibilität des Systems.
5. Build, release, run
Das fünfte Prinzip sieht vor, dass die Codebase eines IaC-Projekts durch drei Phasen in eine laufende Instanz transformiert wird: in der Build-Phase wird Code kompiliert und zusammen mit weiteren notwendigen Artefakten zu einem Auslieferungspaket, dem sogenannten Build, zusammengefasst. Anschließend wird dieses Build mit einer Konfiguration für die Zielumgebung kombiniert, wodurch ein Release entsteht. In der finalen Run-Phase wird das Release in der Zielumgebung ausgeführt. Um neuen Code in der Zielumgebung auszuführen, müssen immer diese Schritte durchlaufen werden. Eine direkte Codeänderung in der Laufzeitumgebung ist nicht zulässig. Dies garantiert eine Nachverfolgbarkeit der durchgeführten Änderungen. Gleichzeitig vermeidet man einen Drift zwischen den unterschiedlichen Stages, da manuelle Änderungen in der Stage ausgeschlossen werden. Dieses formalisierte Vorgehen führt jedoch zu höheren Aufwänden, wenn man sie denn manuell durchführt. Glücklicherweise kann man die drei Phasen automatisieren, was unter dem Stichwort Continuous Integration (CI) und Continuous Delivery (CD) verstanden wird. Über Pipelines lassen sich Änderungen im Code automatisiert durch die verschiedenen Phasen in lauffähige Umgebungen deployen.
Dieses Vorgehen lässt sich komplett auf die Arbeit mit IaC-Technologien übertragen: Änderungen an der Infrastruktur dürfen nur durch Änderungen am Code durchgeführt werden. So vermeidet man Unterschiede zwischen den Stages und schafft gleichzeitig bei Vorhandensein von Backups der Daten auch die Voraussetzungen, um im Rahmen eines Disaster-Recovery-Konzeptes das IaC-Projekt in einer anderen Region zu deployen. Führt man diese Idee weiter, kann man auch von einer unveränderlichen Infrastruktur (Immutable Infrastructure) sprechen. Möchte man das Betriebssystem eines Servers aktualisieren, so führt man nicht mehr ein Update auf Systemebene durch, sondern erstellt ein neues Systemimage (diese werden als "Golden-Images" bezeichnet) und tauscht die aktuellen Server durch neue aus. Dieses Ziel ist aber nur mit viel Automatisierung realistisch umsetzbar, da die Aufwände für eine manuelle Pflege auf Dauer sehr hoch sind.
Für die Verwendung von CI/CD mit AWS CloudFormation existiert mit AWS TaskCat [6] eine Quick-Start-Implementierung, welche das Testen von CloudFormation-Stacks vereinfacht. Es führt ein Deployment in mehreren AWS-Regionen durch und erstellt einen Bericht über die Deployment-Ergebnisse. Arbeitet man mit dem AWS CDK, empfiehlt sich der Einsatz des AWS-CDK-Pipelines-Konstruktes. Es automatisiert die Build-, Test- und Deploy-Phasen eines AWS-CDK-Projektes und kann mit gängigen Git-Hosting-Plattformen verbunden werden.
Neben der automatisierten Erstellung von Golden-Images für das Patch-Management seiner Server ist auch der Einsatz vom AWS Systems Manager Patchmanager denkbar. Dieser Service automatisiert das Ausrollen von Updates in der Serverflotte und minimiert somit die Aufwände. Eine dritte Alternative ist der Verzicht auf eigene Server und der Einsatz von Serverless-Technologien wie AWS Lambda oder AWS Fargate. Bei diesen Technologien übernimmt AWS die Verwaltung der darunterliegenden Server im Rahmen des Shared Responsibility Models [7].
6. Prozesse
12-Factor-Apps bestehen aus zustandslosen Computerprozessen, damit diese horizontal skaliert und im Fehlerfall einfach ersetzt werden können. Das heißt, dass Daten für die Verarbeitung temporär zwar im Speicher oder auf dem Datenträger des Prozesses zwischengespeichert werden können, jedoch nicht zwingend notwendig für die Verarbeitung sind. Wird also der Prozess neu gestartet, müssen die Daten aus einer externen Datenquelle, wie einer Datenbank, vollständig abrufbar sein. Somit wird die Auswirkung des Ausfalls eines Prozesses minimiert, da dieser leicht durch einen neuen ersetzt werden kann.
Auf die Cloud übertragen bedeutet dies eine Trennung zwischen Rechenkapazität und Datenhaltung. Diese Trennung ermöglicht es, die Rechenkapazität ohne Seiteneffekte zu skalieren und damit dem jeweiligen Bedarf anzupassen. Gleichzeitig erhält man eine hohe Resilienz gegenüber Ausfällen, da fehlerhafte Server einfach ausgetauscht werden können. Natürlich kann und soll man weiterhin Daten durch den Einsatz von Caches "näher an die Berechnung" heranführen, jedoch dürfen diese Caches nicht zur primären Datenquelle werden.
Doch was ist ein "Cloud-Prozess"? Dafür sollten wir uns die Eigenschaften von klassischen Prozessen im Kontext von Computeranwendungen anschauen: ein Prozess stellt ein Computerprogramm während der Ausführung dar. Betriebssysteme verwalten Prozesse, weisen diesen abgeschottete Speicherbereiche und Ressourcen zu. Innerhalb von Prozessen können mehrere Ausführungsstränge (Threads) zur Verarbeitung der Daten verwendet werden. Diese Eigenschaften ähneln denen von CloudFormation-Stacks: in diesem Vergleich übernimmt AWS die Rolle des Betriebssystems und verwaltet verschiedene Stacks, weist diesen die angeforderten Ressourcen zu und kann diese auch wieder beenden.
Doch die Trennung zwischen mehreren CloudFormation-Stacks innerhalb eines AWS-Accounts ist nicht so strikt wie bei Computerprozessen: die Ressourcen innerhalb eines CloudFormation-Stacks können durchaus auf Ressourcen anderer Stacks zugreifen. Möchte man dies verhindern, kann man in AWS Identity and Access Management (IAM) Policies definieren, die den Zugriff auf Ressourcen eines bestimmten Stacks einschränken. Dazu können den Ressourcen eines Stacks Tags vergeben werden, welche in den IAM Policies abgefragt werden. Dieser Ansatz setzt jedoch detaillierte IAM-Kenntnisse voraus, da im Falle von fehlerhaften Policies Zugriffe entweder blockiert werden oder womöglich zu weitreichende Berechtigungen vergeben werden.
Neben Berechtigungen beeinflussen auch Limits, auch als Quotas bekannt, das Deployment mehrerer CloudFormation-Stacks. Während manche Limits durch den Support angepasst werden können, sind andere Limits fest. Möchte man also mehrere CloudFormation-Stacks betreiben und dabei die Isolation maximieren, kann man mehrere AWS-Accounts einsetzen. Um die Administration mehrerer AWS-Accounts zu vereinfachen existiert mit AWS Organizations [8] ein Werkzeug zur Verwaltung von AWS-Accounts. Für einen einfachen Einstieg in ein sicheres und skalierbares Multi-Account-Setup kann AWS Control Tower [9] eingesetzt werden.
7. Bindung an Ports
Anwendungen, die dem 12-Factor-Schema folgen, sind vollständig und eigenständig. Sie bringen ihre eigene Laufzeitumgebung mit und stellen ihren Dienst über eine definierte, öffentliche Schnittstelle in Form eines Netzwerkports zur Verfügung. Bereitstellungen als Teil eines separaten (Applikations-)Servers wie Apache httpd oder Apache Tomcat sollen eher vermieden werden, da sonst Seiteneffekte für einen unruhigen Betrieb sorgen können. Dieses Konzept wird genauso auch von Docker verwendet: Anwendungen werden in einem abgeschotteten Docker-Container betrieben, können jedoch über Port-Freigaben Dienste der Außenwelt zur Verfügung stellen. Gleichzeitig stellt der Port auch die primäre bzw. die einzige Schnittstelle zwischen Docker-Container und Außenwelt dar. Der Aufbau innerhalb des Docker-Containers muss der Außenwelt nicht bekannt sein, um den Dienst zu konsumieren.
Bei Cloud-Workloads können Load Balancer die Aufgabe solcher Netzwerkports übernehmen. Sie stellen die zentrale Schnittstelle zwischen Außenwelt und Workload dar. Diese Außenwelt kann sowohl das Internet, als auch das Corporate WAN oder LAN eines Unternehmens sein. Der Load Balancer fungiert als Fassade und abstrahiert die Netzwerktopologie des Workloads. Dies adressiert ein weiteres Problem vieler Cloudnutzer: bei der Migration in die Cloud versuchen Unternehmen häufig, ihre bekannten Netzwerktopologien möglichst unverändert in die Cloud zu übertragen. Dies bedeutet, dass alle Netze über eindeutige IPv4-Adressbereiche verfügen, um die Kommunikation zwischen ebendiesen zu ermöglichen. Die begrenzte Verfügbarkeit an IPv4-Adressen resultiert oft in zu kleinen Subnetzen, hoher Komplexität und letztendlich mehr Wartungsaufwand.
Bei der Nutzung von IaC kann man aus verschiedenen Technologien zur Bereitstellung der öffentlichen Schnittstelle wählen. Neben der oben bereits diskutierten Option eines dedizierten Load Balancers kann man auch ein dediziertes API Gateway instanzieren, das die öffentliche Schnittstelle der Anwendung bereitstellt. Dies kann beispielsweise eine gesicherte REST API sein.
Ein häufiges Szenario in der Cloud ist, dass ein verteiltes System aus Ressourcen in verschiedenen Accounts und virtuellen Netzwerken besteht. Hier ist die Nutzung privater Endpunkte, wie z. B. durch AWS PrivateLink [10] zur Verfügung gestellt, sinnvoll. Diese ermöglichen eine weiterreichende Kontrolle der Kommunikation zwischen privaten Netzen. Der Datenverkehr wird dabei nicht durch das Internet geleitet, sondern verbleibt in den Netzen des Cloudanbieters.
8. Nebenläufigkeit
In einer 12-Factor-App sind Prozesse First Class Citizens. Dieses Konzept ermöglicht es Applikationen, in mehreren Prozessen zu skalieren, die über die Grenzen von physikalischen Systemen hinweg verteilt werden können. Damit wird eine horizontale Skalierbarkeit erreicht und die Skalierung ist nicht durch die Leistungsfähigkeit einer physikalischen Komponente begrenzt.
Wenn wir dieses Konzept auf IaC übertragen, erreichen wir die ähnliche, horizontale Skalierbarkeit der Infrastruktur. Wie wir bereits herausgearbeitet haben können wir Prozesse in der Cloud auf Konzepte wie Accounts und CloudFormation-Stacks projizieren. Eine weitere wichtige Rolle bei der Analogie für die Nebenläufigkeit spielen die bereits eingeführten Regionen, die eine breite geographische Verteilung erlauben.
CloudFormation-Stacks können, ähnlich wie Betriebssystemprozesse, mehrfach instanziiert werden und bringen bei geeigneter Implementierung eine Isolierung der im Stack enthaltenen Applikationen von anderen Applikationen. Diese Isolierung kann beispielsweise auf Netzwerkebene erreicht werden, indem jeder Stack sein eigenen logisch isolierten Bereich definiert. Dieses Konzept ist auf AWS als Amazon Virtual Private Cloud (VPC) implementiert. Sollte Kommunikation zwischen verschiedenen VPCs explizit erwünscht sein, kann diese durch geeignete Konfiguration ermöglicht werden. Eine Instanzierung eines Cloudformation-Stacks ist stets durch einen Account und eine Region beschränkt.
Wie wir bereits gesehen haben, können Accounts in logisch getrennte Bereich aufgeteilt werden. Diese teilen sich dennoch einen gemeinsamen logischen Ressourcenpool, der durch Service Quotas begrenzt wird. Ein Skalierung über diese Quotas hinaus ist möglich, wenn man die Möglichkeit nutzt, CloudFormation-Stacks in mehreren Accounts zu instanziieren. Neben der erweiterten Skalierung resultiert dieser Ansatz auch in einer verschärften Isolation, da die Accounts völlig voneinander isoliert werden können. Häufig wird dieses Konzept umgesetzt um gleichartige, aber völlig isolierte und typischerweise unterschiedlich dimensionierte Umgebungen für unterschiedliche Stages in der Entwicklung (z. B. Entwicklung, Test und Produktion) zu erstellen.
Eine weitere Komponente der Skalierung ist die Nutzung mehrerer Regionen. In den allermeisten Fällen spielt die Ressourcenverfügbarkeit in einer Region dabei keine Rolle. Wichtiger ist der Bedarf einer größeren Nähe zu regional stark verteilten Nutzern eines Systems herzustellen, die dann häufig mit verbesserter Nutzererfahrung einhergeht. Ein weiteres Anwendungsfeld sind Disaster-Recovery-Implementierungen.
Zusammenfassend stellen wir fest, dass dass Prinzip der Nebenläufigkeit und die damit verbundene horizontale Skalierung von 12-Faktor-Applikationen sich gut auf IaC abbilden lassen und wir mannigfaltige Optionen haben, diese Analogie in der Cloud umzusetzen.
9. Einweggebrauch
Damit eine 12-Factor-Applikation robust ist, sollen die Prozesse einer Applikation verwerfbar sein. Diese Eigenschaft wird durch drei Anforderungen umgesetzt: eine geringe Startzeit, ein sauberes Herunterfahren und Widerstandsfähigkeit gegenüber "plötzlichem Tod". Damit lassen sich fehlerhafte Prozesse schnell und ohne große Auswirkungen durch neue ersetzen.
Der Einsatz von IaC in der Cloud erlaubt eine schnelle Startzeit: verglichen mit dem Bereitstellen neuer Server im eigenen Rechenzentrum ist das Provisionieren eines Servers in der Cloud durch IaC im Bruchteil der Zeit möglich. Auch wenn man die Zeit der IaC-Provisionierung mit einer händischen Konfiguration über grafische Benutzeroberflächen wie der AWS Console vergleicht ist IaC der klare Gewinner.
Das ordnungsgemäße Herunterfahren von IaC-Projekten wird üblicherweise von der jeweiligen IaC-Technologie implementiert, sodass es zunächst scheint, als wäre diese Anforderung auch erfüllt. Hier sollten wir jedoch hinterfragen, was der Kern dieser Anforderung ist: wenn wir Transaktionen oder Datenverarbeitungen haben, die bearbeitet werden, während das System heruntergefahren werden soll, müssen Dateninkonsistenzen vermieden werden. Dies kann erreicht werden, indem man die temporären Ergebnisse verwirft und die Verarbeitung später von neuem startet. Dies funktioniert aber nur, sofern keine Seiteneffekte bei der Verarbeitung ausgelöst wurden. Ein möglicher Seiteneffekt ist Kommunikation mit externen Systemen: wurde für eine Bestellung in einem Webshop bereits eine E-Mail an den Kunden versendet, sollten wir die Bestellung nicht einfach abbrechen. Ein zweiter Ansatz zur Behandlung von Abbrüchen stellen Zwischenstände (checkpoints) bei der Verarbeitung dar. Diese bilden die Datengrundlage beim Wiederanlaufen. Eine weitere Option ist das Warten, bis die Verarbeitung komplett abgeschlossen ist. Für diesen Ansatz sollte die Verarbeitung aber in absehbarer Zeit abgeschlossen sein.
Versteht man das Herunterfahren des IaC-Projekts als Deprovisionierung des gesamten Projekts, haben wir beim Wiederanfahren ein Problem: wird die Datenhaltung nur durch Ressourcen innerhalb des IaC-Projektes realisiert, stehen die Daten beim erneuten Starten nicht mehr zur Verfügung. Hieraus ergibt sich erneut die Trennung von Verarbeitung und Datenhaltung: der Zustand des IaC-Projektes, welcher die Inhalte der Datenbanken und Dateisysteme umfasst, sollte regelmäßig oder aber spätestens beim Herunterfahren des Projektes an eine externe Stelle kopiert werden. Am einfachsten kann dies mit Backups realisiert werden.
Kommen wir zur dritten Anforderung: dem "plötzlichen Tod". Welche Entsprechung gibt es hierfür bei IaC in einer Cloud? Denkbar ist der Ausfall eines Rechenzentrums. Ebenfalls vorstellbar ist die mutwillige Löschung eines IaC-Projekts durch einen unzufriedenen Mitarbeiter oder kompromittierte Zugangsdaten. Für den ersten Fall bietet AWS verschiedene Availability Zones innerhalb einer geographischen Region an. Dabei handelt es sich um unabhängige Verbünde von Rechenzentren, welche jeweils über eigene, redundante Stromversorgungen und Netzwerkanbindungen verfügen. Verteilt man seine Rechenkapazität also über mehrere Availability Zones, kann man den Ausfall eines ganzen Rechenzentrums verkraften. Möchte man sich auch vor Katastrophen in einer geographischen Region absichern, kann man auf andere Regionen ausweichen. Diese Überlegungen sollten im Rahmen eines Business-Continuity-/ Disaster-Recovery-Konzeptes festgehalten werden.
Sie sehen also, dass der Einweggebrauch und die damit verbundene Trennung von Rechenkapazitäten und Datenhaltung Vorteile sowohl bei Anwendungen, als auch Cloud-Infrastruktur bringt.
10. Dev-Prod-Vergleichbarkeit
Die Autoren des 12-Factor-Manifests sehen Unterschiede zwischen den Stages einer Anwendung als Problem an. So sind die Zeit bis zur Auslieferung, unterschiedliche Rollen in Entwicklung und Produktion, sowie abweichende Technologien in den verschiedenen Stages eine Herausforderung. Ein langer Entwicklungszyklus kann dazu führen, dass in den verschiedenen Stages stark abweichende Versionen einer Applikation laufen. Wenn Entwicklung und Betrieb auf verschiedene Personenkreise oder Teams aufgeteilt ist, stellt der Wissenstransfer zwischen diesen Rollen häufig einen Flaschenhals dar, weshalb der Betrieb nicht alle notwendigen Informationen erhält. Zuletzt führt der Einsatz verschiedener Technologien in den Stages zu Problemen: während in der Produktion ein mächtiger Datenbank-Cluster für die Speicherung der Daten verwendet wird, läuft beim Entwickler nur eine winzige In-Memory-Datenbank, welche zwar das Entwickeln vereinfacht, jedoch das Nachvollziehen von Produktionsproblemen erschwert.
Deshalb ist das nachfolgend beschriebene Szenario gar nicht so unwahrscheinlich: neue Funktionalitäten werden nach langer Entwicklungszeit, ohne ausreichend Informationen an den Betrieb zu übergeben, in die Produktion gebracht. Dort verhalten sich die Änderungen aufgrund unterschiedlicher Technologien und Konfiguration unerwartet und führen zu Fehlern, welche nur schwer nachvollziehbar sind. Am Ende fehlen dem Betrieb essentielle Informationen, die Entwicklung kann die Fehler nicht nachvollziehen und der Kunde ist aufgrund der Fehlersituation unzufrieden.
Was sind also Maßnahmen, um die Unterschiede möglichst gering zu halten?
Der Einsatz von Continuous Integration/ Continuous Delivery soll dafür sorgen, dass Funktionalitäten schneller entwickelt und ausgeliefert werden können. Schneidet man die Änderungen möglichst klein, so können häufige, kleine Auslieferungen das Risiko minimieren. Um die Personallücke zu schließen, sollen Entwickler:innen an Auslieferung und Überwachung der Produktion beteiligt werden. Zur Werkzeug-Lücke merken die 12-Factor-App- Autoren nur an, dass diese möglichst vermieden werden sollen. Paketierungs- und Virtualisierungswerkzeuge seien mittlerweile so mächtig, dass dieselben Werkzeuge in Produktion und Entwicklung eingesetzt werden können. So leicht ist die Werkzeug-Lücke aber meist nicht zu lösen, da einerseits der Entwicklercomputer ein limitierender Faktor ist, auf der anderen Seite aber auch abweichende Konfigurationen wie z. B. ein Clustering nur mit sehr hohen Aufwänden lokal zur Verfügung gestellt werden können.
An dieser Stelle bringt IaC in einer Cloud-Umgebung einen gewaltigen Mehrwert: dieselbe IaC-Codebasis kann für alle Stages verwendet werden. Dabei ist es äußert leicht möglich, jedem/-r Mitarbeiter:in seine eigene(n) Umgebung(en) zur Verfügung zu stellen. Beachtet man die Faktoren Einweggebrauch, Nebenläufigkeit und Prozesse, so wäre ein denkbares Szenario, dass Mitarbeiter:innen eigene AWS-Accounts und Budgets erhalten, in denen sie je nach Bedarf ihre IaC-Projekte instanziieren und auch wieder löschen können. Ein wichtiger Aspekt hierbei ist, dass Mitarbeiter:innen in ihren eigenen Accounts umfassende Berechtigungen erhalten, um neue Services auszuprobieren und rasche Innovationen zu ermöglichen.
11. Logs
In einer 12-Faktor-App stellen Logs einen Eventstream dar, in dem alle relevanten Ereignisse mit einem Zeitstempel versehen geschrieben werden. Jeder Prozess ist dafür zuständig, seine eigenen Ereignisse in den Logs zu protokollieren. Damit endet die Verantwortlichkeit eines Prozesses. Um das eigentliche Ziel dieses Aspekts zu erreichen, das Verhalten und den Zustand einer Anwendung beobachtbar zu machen, müssen diese einzelnen Logstreams zusammengefasst, persistiert und analysierbar gemacht werden. Diese Zuständigkeiten werden an externe, spezialisierte Systeme delegiert.
Wenn wir dies nun in die Cloud und auf IaC übertragen, finden wir diese Konzepte in der gleichen Zweiteilung der Verantwortlichkeiten wieder. Während eine 12-Faktor-App aus vielen verschiedenen Prozessen bestehen kann, besteht ein typisches IaC-Projekt aus vielen verschiedenen, unabhängigen Infrastrukturkomponenten, welche als Gesamtheit zu Verhalten und Zustand des Systems beitragen. Hier ist es elementar, dass alle Komponenten in der Lage sind, die relevanten Ereignisse in Logstreams zu protokollieren und einheitlich zugänglich machen. In der AWS Cloud sind alle Services und damit alle Infrastrukturkomponenten so gebaut, dass sie Log Events über eine einheitliche Schnittstelle emittieren, die durch Amazon CloudWatch [11] bereitgestellt wird. Damit haben wir den ersten Aspekt, die einheitliche und vollständige Log-Generierung, bereits abgedeckt. Der zweite Aspekt kann ebenfalls auf dieser Basis erfüllt werden. CloudWatch ermöglicht es, verschiedene Log-Streams zu aggregieren und bietet eine Benutzeroberfläche für die Erstellung von Dashboards und Analysen an. Best Practice ist hier, die verschiedenen Log-Streams nachhaltig an einer zentralen Stelle, beispielsweise in einem Objectstore wie Amazon Simple Storage Service (Amazon S3) [12] in einem dedizierten Account zu speichern. Diese Daten können dann auch von anderen Tools zugegriffen und analysiert werden. Die Offenheit der Schnittstellen und Daten ermöglicht hier eine sehr flexible Optimierung auf den jeweiligen Anwendungsfall oder Teampräferenzen bei der Auswahl des Werkzeugs.
Ein wichtiger Aspekt über die reine IaC-Sicht, ist, dass Infrastruktur und Applikation als Einheit betrachtet werden sollten, um ein allumfassendes Verständnis des Systems zu erlangen. Hier bietet es sich an, die gleichen Technologien, die wir zum Loggen der Infrastrukturereignisse verwenden, auch für die Applikationsereignisse zu nutzen. Amazon Cloudwatch erlaubt es nicht nur, AWS Services Events zu protokollieren, sondern ist auch kompatibel zu den von 12-Factor-Applications implementierten Mechanismen. Applikationsevents können direkt nach CloudWatch geschrieben werden. Die Anbindung an CloudWatch funktioniert unabhängig von der Laufzeitumgebung der Applikation. Das Logging funktioniert in Applikationsservern in einer virtuellen Maschine, in einem Container und auch in einer Function-as-a-Service-Umgebung gleichermaßen.
12. Admin-Prozesse
Das letzte Prinzip der 12-Factor-Apps betrifft administrative Prozesse einer Anwendung. Neben den fachlichen Prozessen, die zur Erbringung der eigentlichen Funktionalität notwendig sind, gibt es häufig auch administrative Prozesse, wie Migrationen von Datenbankschemata, oder die Verwaltung von Daten. Es wird vorgeschlagen, dass diese administrativen Aufgaben einerseits als eigene Prozesse implementiert werden, gleichzeitig aber in derselben Umgebung laufen, wie die anderen Prozesse der Anwendung. Ebenfalls ist es wichtig, dass der Code für die administrativen Prozesse im selben Repository gepflegt wird wie die Hauptanwendung, um einen Drift zu verhindern.
Wie lässt sich dies auf IaC und Cloud übertragen? Dies fällt in der Tat schwer. Einer der Schwerpunkte, auf denen IaC basiert, ist die vollständige Automatisierung und damit die Reproduzierbarkeit der Infrastruktur. Dieses Ziel erreicht man nur, wenn tatsächlich sämtliche Aspekte automatisiert sind. Damit treten Administrationsdienste, bei denen Administratoren sich direkt mit den Diensten verbinden können um manuelle administrative Tätigkeiten vornehmen können in den Hintergrund, während Automatisierung auf Basis der Bereitstellung von Administrations-APIs in den Vordergrund treten. Diese sollten wiederum, wie bei den 12-Factor-Apps gefordert, gemeinsam mit der eigentlichen Applikation verwaltet und instanziiert werden. Sind Infrastrukturcode und Applikationscode vollständig und können durch einen CI/CD-Prozess instanziiert werden, erreicht man das Ziel vollständig automatisierter Anwendungen, die gemeinsam mit der benötigten Infrastruktur reproduziert werden können. Anwendung und Infrastruktur wachsen zusammen.
Zusammenfassung
Nach Untersuchung der verschiedenen Aspekte von 12-Factor-Apps kann man feststellen, dass diese sich zum Großteil relativ direkt auf die Domäne IaC abbilden lassen. Während dies auch für die Autoren zunächst in Teilen überraschend ist, stellt man fest, dass die grundlegenden Konzepte der Best Practices universell genug sind, um in verschiedenen Domänen eine sinnvolle Anwendung zu finden. Erwartungsgemäß gibt es auch Aspekte, die sich nicht unmittelbar abbilden lassen, aber auch hier erweist sich das Gedankenexperiment als sehr lehrreich. Bringt es doch Konzepte ans Tageslicht, die gegebenenfalls noch in anderen Domänen geeignet umgesetzt werden sollten. Sicherlich ist es eine gute Übung für den Leser, die Gedanken hier ein wenig schweifen zu lassen, um weitere Best Practices zu entdecken, die beispielsweise in IaC umsetzbar sind. Neben der hier analysierten Konzepte der 12-Factor-Apps gibt es noch viele weitere Sammlungen von Best Practices, die für ein solches Gedankenexperiment geeignet sind. Es gibt noch viele Schätze zu heben!