Hand aufs Herz: Wo schaust du zuerst nach, wenn du wissen möchtest, welche Anwendungen konkret in einem Environment deployt sind? Vielleicht findest du dich in einer der folgenden Antworten wieder:
GitOps kann dich in die Lage versetzen, die letzte der drei Antworten zu geben – und zwar mit sehr großer Zuversicht. Diese psychologische Sicherheit ist nur eines der Resultate von einem Deployment-Workflow auf GitOps-Basis.
In diesem Kapitel stellen wir GitOps ganz grundlegend vor. Dabei starten wir mit einer vagen Gegenüberstellung, um eine grundsätzliche Vorstellung von GitOps zu haben. Dann wollen wir die Hintergründe und Herausforderungen verstehen, aus denen GitOps entstand. Anschließend wenden wir uns den vier Prinzipien von OpenGitOps zu, die GitOps im Kern definieren.
CIOps: Der CI-Server deployt in den Cluster.
Bei einer »klassisch« umgesetzten Pipeline für Continuous Deployment (CD) führt der CI-Server aktiv das Deployment in die Zielumgebung durch (Push-Prinzip). In Abb. 1–1 sehen wir ein Beispiel davon: Der CI-Server, in diesem Fall GitLab CI, verbindet sich mit einem Source Code Management (SCM), zum Beispiel GitHub. GitLab CI lädt ein Git-Repository herunter, das beispielsweise Kubernetes-Manifeste enthält. Anschließend verbindet es sich mit einem Kubernetes-Cluster (beispielsweise über eine Kubeconfig-Datei) und rollt diese Manifeste aus (beispielsweise mit einem kubectl apply). Dieses Verfahren bezeichnen wir als »CIOps1«, weil ausschließlich der CI-Server operativ tätig ist.
Punktuelle Rollouts ermöglichen massiven Drift.
CIOps hat sich jahrelang in der Praxis bewährt – aber es weist an kritischen Stellen entscheidende Mängel auf: Der Rollout wird nur punktuell ausgeführt. Dadurch entsteht ab der ersten Sekunde nach dem CI-geführten Rollout etwas, das alle Infrastruktur-Menschen fürchten: Drift – die Realität entfernt sich mit jedem Moment mehr vom ursprünglich definierten Wunschzustand. Danach manuell ausgeführte Änderungen bleiben intransparent bestehen. Solche Änderungen können aus Versehen passieren oder absichtlich, etwa durch Angreifende. Und genau dadurch entsteht das Problem, das wir am Anfang des Kapitels angerissen haben: Wer eine Woche lang keinen Commit auf das Repo macht, von dem aus die Pipeline getriggert wird, kann eine Woche lang manuelle Änderungen im Cluster machen, und es wird keinerlei automatisches Zurücksetzen auf den in Git definierten Zustand (Reconciliation) geschehen.
Abb. 1–1
Die »klassische« CIOps Pipeline
Der CI-Server braucht hochprivilegierten Zugriff auf den Cluster.
Außerdem haben wir gravierende Sicherheitsrisiken: Der CI-Server braucht privilegierten Zugriff auf die Zielumgebung. Meistens bekommt der CI-Server direkt administrativen Zugriff und kann dadurch in den falschen Händen viel Schaden anrichten. Das öffnet sehr gefährliche Einfallstore für Angreifer.
Der CI-Server braucht Sichtkontakt zum Cluster.
Ebenso ist ein Rollout nur dann möglich, wenn eine Netzwerkverbindung vom CI-Server zum Zielsystem besteht. Selbst wenn wir die Rollout-Pipeline im CI-Server automatisch alle 5 Minuten ausführen lassen, wird die Angleichung nur dann passieren, wenn der CI-Server den Cluster erreichen kann.
Außerdem ist in Enterprise-Umgebungen diese Verbindung zwischen Entwicklungsumgebung (CI-Server) und Betriebsumgebung (Kubernetes) aufgrund unterschiedlicher Sicherheitszonen oft eine Herausforderung. Dies kann die Verbindung unmöglich machen oder durch die Notwendigkeit von Firewall-Freischaltungen deutlich erschweren. Zwar unterliegen die umgekehrten Netzwerkverbindungen von der Betriebsumgebung auf die Entwicklungsumgebung (SCM) gegebenenfalls ähnlichen Problematiken. Unserer Erfahrung nach ist es aber meist einfacher, aus der produktiven Betriebsumgebung heraus, als in sie hineinzukommen. Mit GitOps ließe sich selbst dieses Problem lösen: Der GitOps-Operator könnte die Manifeste auch aus einer Open Container Initiative (OCI) Registry lesen, auf die Kubernetes aufgrund der Images ohnehin Zugriff braucht (siehe Abschnitt 4.13 auf Seite 90).
Kein Git-basiertes Löschen
Weiterhin sind wir ziemlich eingeschränkt, weil wir über das Git-Repo, von dem aus der CI-Server deployt, nur bestehende Ressourcen aktualisieren oder neue Ressourcen deployen können. Das Löschen von Ressourcen hingegen ist nicht von Haus aus über Commits möglich (beispielsweise durch das Löschen einer Manifest-Datei), nur über manuelles Eingreifen oder zusätzliche Pipelines.
Geringe Auditierbarkeit durch imperative Änderungen
Die Pipelines des CI-Servers ermöglichen imperative Änderungen an der Config. Typischerweise schreibt man hier den Tag des aktuellen Images in die Config. Gängig ist aber auch das Einfügen von Secrets aus dem Credentials-Store des CI-Servers, das Spezifizieren von Helm-Charts oder das Setzen von umgebungsspezifischen Parametern. Dies führt dazu, dass die tatsächlich an den Cluster übertragene Config nur transient auf dem CI-Server besteht. Damit sind Änderungen nicht einfach nachvollziehbar und Fehler schwerer zu finden.
GitOps: Der Operator im Cluster deployt.
Wie können wir diesen Problematiken begegnen? Kann es überhaupt einen anderen Weg geben? Mit GitOps können wir ganz anders vorgehen (siehe Abb. 1–2): Der CI-Server verschwindet bei einem Rollout grundsätzlich komplett aus dem Bild. Stattdessen gibt es einen Prozess innerhalb des Zielsystems, der ununterbrochen das relevante Git-Repository pullt und auf Änderungen überprüft (Pull-Prinzip). Dieser Prozess wird »GitOps-Operator« genannt; im Diagramm ist es Argo CD.
Abb. 1–2
Einfaches Deployment mittels GitOps
Damit können wir den eben genannten Schwierigkeiten bei CIOps sehr gut begegnen:
Selbstheilung durch Continuous Operations
Außerdem zwingt uns die deklarative Natur von GitOps dazu dedizierte Werkzeuge zum Secrets Management zu verwenden, statt diese im CI-Server zu verwalten (siehe Kapitel 5 auf Seite 97). Dies reduziert das Sicherheitsrisiko, das CI-Server darstellen.
Weitere Vorteile von GitOps-Operatoren
Über die Behebung der Schwierigkeiten von CIOps hinaus bieten GitOps-Operatoren noch weitere Vorteile:
Außerdem können grafische Oberflächen wie die von Argo CD (siehe Kapitel 3 auf Seite 47) die Developer Experience erhöhen, den Einsteig in Kubernetes vereinfachen und manuelle Interaktion mit dem Cluster verringern.
Mit Preview Environments können wir einfacher mehrere Features gleichzeitig in produktionsnahe Umgebungen bringen (siehe Abschnitt 6.5.2 auf Seite 157).
Den User, der in beiden Bildern nur am Rand auftaucht, haben wir bisher noch komplett außen vor gelassen. In beiden Bildern ist er nur als jemand abgebildet, der Pushes in ein Repo ausführt. Wir werden uns in Kapitel 2 auf Seite 25 genauer damit auseinandersetzen, welche Auswirkungen die Umstellung auf GitOps für die Menschen bedeutet, die damit arbeiten.
Die vier Prinzipien
Wir stellen jetzt direkt die offiziellen vier GitOps-Prinzipien vor, damit wir sie bereits einmal gesehen haben. Sie werden uns durch das ganze Buch begleiten, und wir werden sie in Abschnitt 1.3 auf Seite 16 im Detail beleuchten. Dies sind die GitOps-Prinzipien:
Diese GitOps-Prinzipien klingen anfangs sehr abstrakt; sie sind aber keinesfalls in einem rein akademischen Setting entstanden. Wir befassen uns jetzt mit dem Kontext, in dem GitOps entstand.
In den Wasserfall-Methoden konservativer Unternehmen war es üblich, dass Entwicklungsteams, Quality Assurance (QA) Engineers und Betriebsteams strikt voneinander getrennt arbeiteten (siehe Abb. 1–3). Leitende geben einen starren Zeitplan vor, in dem diese drei separierten Teams gemeinsam Software mit geschäftskritischen Features ausliefern müssen – allerdings ohne wirklich miteinander kollaborieren zu können: Entwickelnde bekommen Vorgaben und implementieren diese als Software. Danach übergeben sie den Staffelstab an das QA-Team, das die Software ausführlich testet. Anschließend findet die finale Übergabe an das Betriebsteam statt, die schließlich eine Software ausrollen, die für sie eine völlige Blackbox ist.
Abb. 1–3
Drei getrennte Teams: Entwicklung, QA und Betrieb
Nach Funktionen separierte Teams liefern langsam, fehleranfällig und ineffizient.
Dieser Prozess war grundsätzlich sehr langsam und hochgradig anfällig für Fehler, weil Kommunikation und Kollaboration zwischen den Teams nicht gefördert wurde. Da jede Phase dieses Entwicklungsvorgangs oft viele Wochen lang dauerte, war ein schnelles Beheben von Problemen in Produktion kaum möglich und Fehler kostspielig.
Die häufigsten Konflikte traten zwischen Entwicklungs- und Betriebsteam auf im Spannungsfeld zwischen Innovation und Stabilität: Während Entwickelnde unter dem Druck ihrer Vorgaben so schnell wie möglich neue Features liefern wollten, wollte das Betriebsteam Änderungen um jeden Preis vermeiden, um die Verfügbarkeit ihrer Anwendungen zu gewährleisten. Statt die wertvolle Erfahrung sowie Sichtweisen der anderen Teams zu verstehen und voneinander zu lernen, entwickelten sich schnell auch zwischenmenschlich tiefe Gräben zwischen den Abteilungen.
Zwar fing Automatisierung an, eine zunehmende Rolle zu spielen, weil dadurch manuelle Schritte wie ein Build oder ein Deployment deutlich stabilisiert und beschleunigt werden konnten. Dennoch waren diese Verbesserungen immer noch auf nur ein Team jeweils beschränkt. Wir mussten einen anderen und besseren Weg finden.
DevOps: Crossfunktionale Teams mit gemeinsamer Verantwortung
Im Zuge von Agile begannen Organisationen bereits zunehmend, Silos abzubrechen und Kollaboration zwischen getrennten Teams zu fördern. Eine Kultur namens DevOps2 entstand in diesem Kontext, und diese Kultur zielt auf crossfunktionale Teams ab, wie wir in Abb. 1–4 sehen: Teams sind nicht mehr nach Funktion separiert, sondern Entwickelnde, QA Engineers und Betriebler übernehmen eine gemeinsam geteilte Verantwortung (oftmals beispielsweise für ein einzelnes Softwareprodukt). Feedback, Automatisierung und Qualität von Anfang an einzubauen sind wichtige Werte für jedes Team.
Abb. 1–4
Teamorganisationen traditionell vs. mit DevOps
In einem solchen Umfeld kann es einem Developer nicht mehr egal sein, ob die Anwendung unperformant in Produktion läuft. Ebenso kann ein Betriebler nicht länger Deployments mit Instabilitätssorgen blockieren. Alle haben eine gemeinsame Verantwortung und lernen deshalb voneinander, was in der jeweiligen Rolle wichtig ist.
Geschwindigkeit und Stabilität stehen nicht mehr im Widerspruch
Dank dieser Fokussierung und Kollaboration und durch hochgradige Automatisierung sind kurze Feedbackzyklen und zügiges Deployen nicht länger ein ferner Wunschtraum. Mittels Continuous Integration (CI) wird auf jedem Commit im Standardbranch eine Pipeline ausgeführt, die den Code testet und einen Build durchführt, an dessen Ende ein deploybares Artefakt steht (beispielsweise ein Java Archive (JAR) oder ein Container-Image)3. Mit Continuous Delivery (CD) als nächster Stufe wird das in CI gebaute Artefakt automatisch auf ein Environment deployt, Integrationstests werden ausgeführt und bei Erfolg wird ins nächste Environment deployt bis direkt vor Produktion. Bei Continuous Deployment (ebenfalls mit CD abgekürzt) wird selbst das Deployen nach Produktion vollständig automatisiert.
Weil Cloud Computing und SaaS-Dienste im Laufe der Zeit zunehmend genutzt wurden, gewannen Themen wie Self-Service und dynamische Infrastruktur an Bedeutung. Im Gegensatz zur »Eisenzeit«, in der man zum Deployen physische Hardware im On-Premises-Rechenzentrum benötigte, konnten wir nun im Cloud-Zeitalter mit sehr geringen Hürden nach Belieben virtuelle Infrastruktur erstellen und wieder abreißen4. In diesem Kontext entstand die Praxis von Infrastructure as Code (IaC).
Infrastruktur wird mit den gleichen Methoden wie Code behandelt.
Wie der Name anklingen lässt, behandelt IaC die Infrastruktur, die zum Deployen von Code benötigt wird, mit ähnlichen Methoden, wie der Code selbst behandelt wird: Statt manuell sich mit Servern zu verbinden und Änderungen durchzuführen, werden Änderungen an Code gemacht, dieser wird committet, eine Pipeline führt Tests aus und rollt diese Änderungen automatisiert aus.
Geringere Hürden, »Operations by PR«
Mit dieser Herangehensweise sind Änderungen an der Infrastruktur mit weniger Hindernissen verbunden. Sowohl Entwickelnde als auch Betriebler haben ein einheitliches Format, um über Infrastruktur zu diskutieren, und können kollaborativ an Infrastruktur arbeiten, wo es nötig ist. Selbst kleine Änderungen an Infrastruktur-Code können genauso leicht wie große Änderungen durch Pull Requests (PRs) durchgeführt werden, was iterative Herangehensweisen an Infrastrukturaufgaben fördert.
Deklarativer Code beschreibt nur den Zielzustand.
Bei dem Code, der IaC ausmacht, gibt es allerdings deutliche Unterschiede zu konventionellem Anwendungscode: Meist ist er nicht imperativ, sondern deklarativ. Imperativer Code beschreibt,wie etwas ausgeführt werden soll. Deklarativer Code hingegen beschreibt,was für ein Zielzustand erreicht werden soll.
Ein einfaches Beispiel zur Illustration ist die Gegenüberstellung von jQuery und CSS: Mit jQuery können wir beispielsweise ein bestimmtes Element mit einer bestimmten ID rot einfärben:
Listing 1–1
Imperatives Einfärben mit jQuery
1
$(document).ready(function(){
2
$("p").on({
3
mouseenter: function(){
4
$(this).css("color", "red");
5
}
6
});
7
});
Dabei führen wir einen JavaScript-Befehl aus – das ist ein imperatives Vorgehen. Dasselbe Ergebnis können wir mit CSS erreichen, indem wir in einem Block unseren Wunsch ausdrücken, dass alle gewünschten Elemente beim Hovern rot eingefärbt werden:
Listing 1–2
Deklaratives Einfärben mit CSS
1
p:hover {
2
color: red;
3
}
Diese deklarative Formulierung hat einige Vorteile:
Bei IaC wird genau das gleiche Prinzip von Deklarationen verwendet: Statt ein Shell-Skript zu nutzen, definieren wir unseren Wunschzustand beispielsweise in Terraform-Dateien, Pulumi-Code, Kubernetes-Manifesten oder Docker-Compose-Dateien. Dadurch ernten wir bei Infrastruktur-Code ganz ähnliche Vorteile wie eben bei CSS beschrieben.
Deklarationen abstrahieren imperative Befehle weg.
Selbstverständlich braucht es zur Umsetzung von deklarativem Code an irgendeiner Stelle auch imperative Befehle. Aber diesen imperativen Code können wir durch Deklarationen elegant wegabstrahieren und mehr Zeit auf wertvolle Infrastruktur-Änderungen investieren. Dieser imperative Code für Infrastruktur muss sich nämlich auch mit vielen Edge Cases befassen:
Wenn wir Infrastruktur mit IaC verwalten, gehen wir davon aus, dass die eben genannten Punkte grundsätzlich vom Interpreter unserer IaC-Deklarationen (beispielsweise die Terraform-CLI bei Terraform-Manifesten) erfüllt werden. Das letzte Kriterium der Idempotenz wollen wir kurz genauer betrachten:
Idempotenz macht IaC-Rollouts risikofrei.
Idempotente Befehle führen bei wiederholter, sequenzieller Ausführung immer zum gleichen Ergebnis. Ein Negativbeispiel, das Idempotenz nicht erfüllt, wäre der Shell-Befehl echo gitops >> test.txt, weil nach jeder Ausführung eine zusätzliche Zeile in der Text-Datei steht. Der Shell-Befehl echo gitops > test.txt hingegen ist idempotent, weil nach jeder Ausführung das Resultat identisch ist: Immer steht eine einzige Zeile mit dem Inhalt »gitops« in der Text-Datei.
Ein Controller, der IaC-Manifeste ausrollt, muss letztlich imperative Aktionen ausführen, damit unser gewünschter Zustand Realität wird. Wenn jedoch die Aktionen, die er durchführt, so idempotent wie nur möglich sind, dann sind wir in guten Händen, denn ein wiederholter Rollout von Manifesten, in denen sich nichts geändert hat, ist dann vollkommen ungefährlich.
Kubernetes5 ist mittlerweile die am weitesten verbreitete Plattform für Containerbetrieb. Dieses Cluster-Betriebssystem, das aus seiner ursprünglichen Herkunft bei Google 2014 als Open Source zur Verfügung gestellt wurde, hat sich sowohl bei großen Cloud-Providern als auch On-Premises bewährt. Auch wenn keine Plattform alle Anforderungen erfüllen kann, hat Kubernetes einige ganz besondere Eigenschaften.
Eine große Kontrollschleife mit deklarativen Manifesten
Eine dieser Eigenschaften ist, dass deklarative YAML-Manifeste der bevorzugte Weg sind, um mit dem Kubernetes-API-Server (sozusagen dem Backend eines Kubernetes-Clusters) zu reden. Kubernetes arbeitet nämlich mit Controllern, die solche Manifeste entgegennehmen und kontinuierlich dafür sorgen, dass diese »Wunschlisten« Realität werden. Dieses kontinuierliche Konvergieren wird auch als Reconciliation bezeichnet. Kubernetes ist mit seinen Controllern und den zugrunde liegenden Ideen aus der Kontrolltheorie letztlich als eine einzige große Kontrollschleife implementiert.
Kubernetes nimmt also einige der Werte und Praktiken von IaC und setzt sie praktisch um. Und wenn wir zurückdenken an unsere initiale Beschreibung von GitOps, dann wird klar, dass ein GitOps-Operator, der kontinuierlich ein Repo überwacht und dessen Manifeste ausrollt, durch die Grundstruktur von Kubernetes nicht sehr schwer zu implementieren sein sollte.
Und tatsächlich ist es so, dass die beiden großen GitOps-Operatoren Flux und Argo CD, die heutzutage zunehmend genutzt werden, überhaupt erst auf Basis von Kubernetes entstehen konnten. 2014 wurde die Firma Weaveworks gegründet, und sie setzten früh auf Kubernetes als Container-Orchestrator6. 2016 praktizierten Mitarbeiter bereits GitOps, ohne dass es den Begriff überhaupt schon gab, und durch GitOps konnten sie bei einem Incident ihr gesamtes gelöschtes System innerhalb von weniger als einer Stunde wiederherstellen.
2017: Alexis Richardson definiert erstmals GitOps.
Anschließend beschrieb Alexis Richardson im Blogpost »GitOps – Operations by Pull Request« im August 2017 zum ersten Mal die damaligen Prinzipien hinter GitOps und benutzte dafür auch zum ersten Mal das Wort »GitOps«7. Grundsätzlichen sind die meisten der damaligen Prinzipien auch in den heutigen Prinzipien enthalten, allerdings in deutlich verfeinerter Form.
Flux und Argo CD entstehen und reifen.
Dieser Blogpost war auch der Moment, wo Weaveworks ihren GitOps-Operator Flux CD8 vorstellten. Ebenfalls im Jahr 2017 begann bei Intuit die Entwicklung von Argo CD9,10. 2019 wurde Flux in die Cloud Native Computing Foundation (CNCF) aufgenommen, das Argo-Projekt folgte im Jahr 2020. 2020 wurde Flux von Grund auf neu geschrieben und als deutlich reifere und stabilere v2 releast. Im Jahr 2022 erreichten beide Tools den höchsten Reifegrad »Graduated«.
2020: Die GitOps Working Group definiert OpenGitOps.
Aufgrund der rasant wachsenden Einführung von GitOps in der gesamten Industrie bildete sich 2020 eine sogenannte Working Group innerhalb der CNCF, die »GitOps Working Group«. Unter den Gründungsmitgliedern waren Mitarbeiter von Firmen wie Amazon, Azure, GitHub, RedHat und Weaveworks11. Diese Working Group überarbeitete die ursprünglichen GitOps-Prinzipien von Alexis Richardson und erschuf »OpenGitOps«12. Unter diesem Projektnamen wird ein GitOps-Standard erarbeitet und verwaltet, der solide und herstellerneutrale Prinzipien für GitOps gewährt. Im Oktober 2021 wurde die Version 1.0.0 von OpenGitOps und den vier Prinzipien veröffentlicht. Im Jahr 2021 fand auch zum ersten Mal die »GitOpsCon«, eine dedizierte Konferenz zu GitOps, im Rahmen der KubeCon statt.
2023: Zertifizierungen in Vorbereitung
Die GitOps Working Group arbeitet auch weiterhin an der Standardisierung von GitOps. Neben der Bereitstellung von Wissen, Use Cases und Whitepapers, wurde auf der GitOpsCon 2023 das Zertifizierungsprogramm »Certified GitOps Associate (CGOA)«13 im Rahmen der Linux Foundation vorgestellt. Interessierte können sich auf diese Weise GitOps-Wissen aneignen und die Erfüllung der Anforderungen zur Erlangung eines Zertifikats nachweisen. Zudem wird an einem Siegel »Certified OpenGitOps Compliance« gearbeitet, damit Tools in der Lage sind, die definierten OpenGitOps-Standards in ihren Produkten nachzuweisen.
Nun sind wir bei OpenGitOps gelandet und können ungefähr sehen, wie die Herausforderungen, denen wir mit DevOps und IaC begegnen und bei denen uns Kubernetes hilft, uns zu den vier Prinzipien führen, die heutzutage GitOps ausmachen. Im Folgenden stellen wir diese vier Prinzipien vor. OpenGitOps besteht im Kern aus diesen vier Prinzipien und einem zugehörigen Glossar.
Beide Materialien wurden bereits in mehrere Sprachen übersetzt, auch ins Deutsche. Wir würdigen diese Übersetzungsleistung und verwenden die deutschen Begriffe in diesem Buch, an manchen Stellen greifen wir jedoch auch auf die entsprechenden englischen Begriffe zurück, da Englisch im Entwicklungsalltag ein ständiger Begleiter ist.
Wir stützen uns auf OpenGitOps v1.0.014.
Allerdings sind die deutschen Übersetzungen nicht in diesem Release enthalten, deswegen verlinken wir dafür auf konkrete Commits:
Diese Prinzipien sind anfangs noch sehr abstrakt und schwer vorstellbar. Das ist wichtig, damit sie so wenig wie möglich an eine bestimmte Implementierung oder Plattform gebunden sind. Andererseits ist das nicht hilfreich, weil wir die Konsequenzen daraus nur schwer erfassen können. Deswegen führen wir zuerst jedes Prinzip inklusive seiner Glossareinträge auf und erläutern es anschließend zusätzlich mit eigenen Beschreibungen und Analogien.
»Der Soll-Zustand eines durch GitOps verwalteten Systems muss deklarativ beschrieben sein.«
Infrastructure as Code
Wir erkennen an dieser Stelle das Prinzip von deklarativem Arbeiten wieder, das uns in IaC erstmalig begegnet ist. Imperative Deployment-Skripte, die bei CIOps an der Tagesordnung sind, haben bei GitOps keinen Platz. Deklarative Formate hingegen wie HashiCorp Configuration Language (HCL, das Format von Terraform-Dateien), Pulumi-Code, YAML (bei Kubernetes, Docker-Compose, AWS CloudFormation und vielen mehr) und Azure Bicep sind hervorragend geeignet.
Analogie Hausbau
Gerne nutzen wir in diesem Zusammenhang die Analogie eines Hausbaus: Ein Haus kann man selbst bauen, wenn man entsprechend handwerklich begabt ist. Man legt das Fundament auf einem vorher festgelegten Grundstück, baut Mauern, verlegt die Elektrik und natürlich das Dach. Allerdings ist dieser Prozess sowohl sehr langwierig als auch aufwendig. Der Bauherr muss sich in allen genannten Bereichen gut auskennen, damit nicht »gepfuscht« wird.
Im Gegensatz dazu hat der Bauherr aber auch die Möglichkeit, ein Fertighaus bauen zu lassen. Vorab vereinbaren der Bauherr und der Dienstleister einen zu erfüllenden Vertrag. Der Vertrag und die Baupläne enthalten genaue Vereinbarungen über die Anzahl und die Größe der Räume, wie die Räume beheizt oder belüftet werden sowie die Ausstattung der Bäder. Die Aufgabe des Dienstleisters ist es, die Beschreibungen dieses Vertrags in konkrete Pläne umzuwandeln und eigenständig auszuführen.
In dieser Analogie entspricht das Haus dem Softwaresystem, Dienstleister dem GitOps-Operatoren, Bauherren den Entwickelnden (die IaC-Code formulieren) und die Baupläne den deklarativen Beschreibungen.
»Der Soll-Zustand wird in einer Weise gespeichert , die Unveränderlichkeit sowie Versionierung erzwingt und die vollständige Historie erhält.«
In diesem Prinzip sehen wir zum ersten Mal die Anklänge an den Namen »GitOps«. Streng genommen haben wir es an dieser Stelle mit zwei separaten Kriterien zu tun, die aber eng miteinander verknüpft sind: Versionierung und Unveränderlichkeit.
Versionierung ist heutzutage technisch ziemlich einfach zu erzielen: Wir nutzen beispielsweise eine Versionsverwaltung (Git, Mercurial, Subversion) oder alternativ einen Object Store (AWS S3, Azure Blobs, Google Cloud Storage) mit aktivierter Versionierung.
Ohne Unveränderlichkeit kein deterministischer Rollout
Unveränderlichkeit hingegen ist nicht zuallererst ein technisches, sondern ein inhaltliches Kriterium. Es geht nicht primär um Immutable Image-Tags, auch wenn sie bei Prinzip 2 vieles vereinfachen. Stattdessen hilft folgender hypothetischer Test, um zu überprüfen, ob Deklarationen unveränderlich formuliert sind: Wenn ich in der Zukunft dieselben Manifeste erneut ausrolle, wird der Endzustand der gleiche sein?
Schauen wir uns ein paar Beispiele für Inhalte in Deklarationen an, die diesen Test nicht erfüllen:
Auch wenn diese Beispiele alle nicht exakt das Kriterium von Unveränderlichkeit erfüllen, sehen wir dennoch, dass es Unterschiede in den Auswirkungen gibt: Wenn bei den jeweiligen Anwendungen SemVer befolgt wird, dann nimmt die Schwere der Auswirkungen von oben nach unten graduell ab.
Positivbeispiele
Betrachten wir als Kontrast ein paar Positivbeispiele, die vollständig Unveränderlichkeit erfüllen:
Unveränderlichkeit hat ihren Preis.
Je exakter man Versionen pinnt, desto mehr Aufwand entsteht dabei aber auch. Manche Aufwände kann man mit Tools wie Renovate17 automatisieren, aber auch die Instandhaltung davon kommt mit Kosten. Letztendlich ist Unveränderlichkeit also keine binäre Entscheidung, sondern wird immer ein Kompromiss sein. Ist mir ein möglichst exaktes Pinnen einer Version wichtig genug für diese oder jene Ressource – oder kann ich mit einem bestimmten Grad an Nichtdeterminismus leben? Diese Entscheidungen sind letztlich immer eine kontextabhängige Entscheidung, für die es kaum Leitlinien geben kann. Immerhin stößt GitOps uns mit der Nase darauf und fordert uns dazu heraus, diese Entscheidungen bewusst zu treffen.
Analogie Hausbau fortgeführt
Führen wir noch die Analogie des Hausbaus fort: Der Bauherr und der Dienstleister sind mit dem Vertrag und den Bauplänen zum Notar gegangen. Beide Parteien übergeben die Dokumente dem Notar, lassen sich eine Kopie mitgeben, und der Notar verwahrt die originalen Dokumente in einem Safe.
Wenn der Bauherr eine Änderung an den Plänen wünscht, dann schlägt er diese dem Dienstleister schriftlich vor. Ist dieser einverstanden, dann unterschreibt er das Dokument und schickt es an den Notar. Der Notar nimmt das Dokument entgegen, schickt den beiden Parteien Kopien des Dokuments und legt es zur ersten Fassung in den Safe dazu. So haben alle Parteien immer einen aktuellen Stand der Dokumente.
Der Safe beim Notar ist in diesem Fall der Zustandsspeicher (beispielsweise ein Git-Repo), der Änderungsvorschlag des Bauherren entspricht einem PR, das Unterzeichnen des Änderungsvorschlags ist der Approval auf dem PR, die Kopien sind dezentrale, lokale Kopien des Git-Repos und das Ablegen im Safe mit dem Verschicken der Kopien entspricht dem Merge des PR. (Der Notar selbst ist in diesem Fall keine wirkliche entscheidende Entität, aber vielleicht ist er mit dem SCM insgesamt gleichzusetzen, alternativ mit einem CI-Server, der nach dem Durchlaufen einer PR-Pipeline automatisch einen Merge durchführt.)
»Software-Agenten beziehen den beschriebenen Soll-Zustand automatisch.«
Der »Software-Agent« ist nichts anderes als der GitOps-Operator, von dem wir bereits am Anfang des Kapitels gesprochen haben. Dieser lädt die deklarativen Beschreibungen aus dem Zustandsspeicher selbstständig herunter (Pull-Prinzip), beispielsweise indem er in regelmäßigen Intervallen einen git clone ausführt.
Prinzip 3 und 4 lassen sich praktisch nicht trennen.
Prinzip 3 und 4 gehören im Grunde genommen untrennbar zusammen. Dennoch beschreiben sie zwei unterschiedliche Aspekte von »Continuous Operations«: Prinzip 3 legt den Fokus auf das Herunterladen der Manifeste, während Prinzip 4 den Rollout dieser Manifeste beschreibt.
Die Trennung ergibt in der Theorie und für die Eindeutigkeit der Prinzipien Sinn; in der Praxis werden wir jedoch kein System finden, das nur eines dieser beiden Prinzipien erfüllt. Deswegen wenden wir uns direkt Prinzip 4 zu und betrachten es im Verbund mit Prinzip 3.
»Software-Agenten beobachten den tatsächlichen Systemzustand und versuchen kontinuierlich , ihn dem Soll-Zustand anzugleichen.«
Wenn uns eine sofortige Angleichung wichtig ist, gibt es dennoch Möglichkeiten, die aber mit ihren eigenen Herausforderungen kommen. Diese beleuchten wir in Abschnitt 7.2.4 auf Seite 212 genauer.
Mit Abweichung ist genau der Drift gemeint, den wir anfangs als die besonders große Gefahr von CIOps identifiziert haben. Hier sehen wir auch Beispiele für Ursachen von Drift: Dies können entweder manuelle Änderungen im Cluster sein, die wir eliminieren wollen und die vom GitOps-Operator rücksichtlos überschrieben werden, oder es können bewusste Änderungen sein, die wir in einem Git-Repo durchgeführt haben und dann vom Agenten ausgerollt werden.
Die Formulierung, dass »Maßnahmen ergriffen werden, um die Abweichung im Laufe der Zeit zu verringern«, passt haargenau zu Beschreibungen, die man über die generelle Arbeitsweise von Kubernetes-Controllern liest. Wir sehen erneut, warum Kubernetes ein exzellentes »Substrat« bildet, auf dem GitOps hervorragend gedeiht.
Analogie Hausbau fortgeführt
Wenden wir uns noch ein letztes Mal der Hausbau-Analogie zu. Der Vertrag zwischen Bauherr und Dienstleister enthält eine besondere Klausel: Der Dienstleister ist verpflichtet, in regelmäßigen Abständen das Grundstück zu begutachten und Änderungen, die seit dem letzten Besuch passiert sind und die den Bauplänen widersprechen, rückgängig zu machen. Dazu könnten selbst verlegte Leitungen, eigens gezimmerte Anbauten, aber auch beschädigte Türen oder zerstörte Fenster zählen. Solche Änderungen werden rigoros beseitigt und müssen stattdessen durch den schriftlichen Prozess durchgeführt werden, wenn sie permanent sein sollen.
Der Bauherr hat sich damit ein zweischneidiges Schwert eingehandelt: Wenn er manuelle Aufwände investiert, werden sie kurz darauf zunichte gemacht werden. An solchen Stellen wird ihn der zusätzliche Aufwand über den Schriftweg ärgern. In den Situationen jedoch, in denen der Dienstleister anstandslos Schäden repariert, wird er sich sehr glücklich schätzen – und dann geht ihm das Beantragen von Änderungen schon viel leichter von der Hand.
In diesem Fall sind die eigenen Anbauten und beschädigten Elemente die Resultate von manuellen Tätigkeiten im Cluster (beispielsweise Installationen von Helm-Charts oder Löschungen von Ressourcen).
Bei Prinzip 1 ist am meisten ersichtlich, wie es aus IaC entstanden ist. Bei Prinzip 2 können wir zumindest noch erkennen, wie eine DevOps-Kultur von gemeinsamer Verantwortung einen auditierbaren Zustandsspeicher bevorzugt. Prinzip 3 und 4 hingegen sind das, was GitOps am meisten zu GitOps selbst macht. Deswegen wird auch die Hausbau-Analogie Schritt für Schritt surrealer, weil es im echten Leben bisher kaum Situationen gibt, für die man eine gute Entsprechung für den Kern von GitOps findet.
An dieser Stelle wollen wir in Kurzform auf einige häufige Fragen eingehen, die sich beim ersten Kontakt mit GitOps stellen: