2Welchen Unterschied macht GitOps?

GitOps besteht im grundsätzlichsten Kern aus den vier Prinzipien, die wir im vorigen Kapitel kennengelernt haben. Dass der offensichtlichste Unterschied zu traditionellen Deployments darin liegt, dass ein Prozess im Zielsystem deployt statt ein CI-Server, ist noch recht klar zu erkennen. Aber oftmals fällt es schwerer, klar zu benennen, zu welchen Konsequenzen GitOps ganz konkret im Alltag eines Entwicklungsteams führt.

Wir wollen dieses Kapitel nutzen, um diese Unterschiede genauer zu analysieren. Dementsprechend werden wir Vergleiche ziehen zwischen Teams, die aus dem CI-Server heraus deployen, und Teams, die GitOpsbasiert Deklarationen laden und kontinuierlich anwenden. Dass (zumindest in einem grundlegenden Maß) Deklarationen wie Kubernetes-Manifeste genutzt werden statt imperativen Befehlen und dass diese Deklarationen in Versionskontrolle verwaltet werden, nehmen wir grundsätzlich als gegeben an.

Den Großteil dieses Kapitels werden wir in den Alltag eines fiktiven Entwicklungsteams eintauchen, um anhand von kleinen Geschichten den Effekt von GitOps besser zu verstehen. Anfangs werden wir allerdings noch einen Blick in die Forschung werfen, um zu untersuchen, ob sich die Effekte von GitOps womöglich auch beziffern lassen.

2.1Indizien aus den DORA-Studien

Für unsere sehr spezifische Fragestellung ist die wissenschaftliche Faktenlage noch sehr dünn. Ein hilfreiches Instrument zur Beurteilung der Effekte von GitOps sehen wir im Bericht »Accelerate State of DevOps 2022« von Google1, der von der Forschungsgruppe DevOps Research and Assessment (DORA)2 erstellt wird. Diese DORA-Studie, die seit 2018 jedes Jahr unter mehreren Zehntausend Berufstätigen durchgeführt wird, untersucht die Themenbereiche Softwarebereitstellung und Betriebsleistung und mögliche zugrunde liegende Faktoren. Der Bericht enthält in der neuesten Fassung von 2022 einige Punkte, die wir als Indizien sehen, dass GitOps-Praktiken positive Effekte auf Teams und Organisationen haben können.

DORA misst die Leistung eines Software-Teams (»Software Delivery Performance«) anhand von fünf Metriken:

  1. Deployment-Häufigkeit: Wie oft wird Code nach Produktion deployt?
  2. Lieferzeit für Änderungen: Wie lange dauert es, bis eine committete Codeänderung in Produktion landet?
  3. Fehlerrate bei Änderungen: Welcher Prozentsatz an Codeänderungen führt zu Beeinträchtigungen und benötigt Wiederherstellungsmaßnahmen?
  4. Wiederherstellungszeit bei Ausfällen: Wie lange dauert das Beheben von Beeinträchtigungen?
  5. Betriebliche Verlässlichkeit: Wie oft werden die Erwartungen hinsichtlich Stabilität und Verfügbarkeit erfüllt?

Die Kombination der GitOps-Prinzipien 2 bis 4 ist assoziiert mit höherer Software Performance Delivery.

In ihrer Untersuchung nutzten leistungsstarke Teams, die in allen fünf Metriken überdurchschnittlich abschnitten, mit 33 % höherer Wahrscheinlichkeit Versionskontrolle und praktizierten mit 46 % höherer Wahrscheinlichkeit Continuous Delivery als schwächere Teams. Zusätzlich haben Teams, die beide Praktiken ausführen, 2,5-mal wahrscheinlicher eine hohe Software Delivery Performance als Teams, die sich nur auf eine der beiden Methodiken fokussieren.

Den Faktor »Versionskontrolle« können wir ziemlich eindeutig auf GitOps-Prinzip 2 abbilden. Besonders auffällig ist, dass gerade die Kombination der beiden Faktoren eine deutliche Leistungssteigerung bringt.

Den Faktor »Continuous Delivery« können wir nur beschränkt auf GitOps-Prinzipien abbilden: Die Autoren der Studie betonen, dass Continuous Delivery nicht automatisch Continuous Deployment mit einschließt. Continuous Delivery bedeutet, dass der Code immer in einem deploybaren Zustand ist (er kann jederzeit deployt werden), während Continuous Deployment darüber hinausgeht, indem der Code auch tatsächlich ständig automatisiert deployt wird.

GitOps ermöglicht es vor allem, Continuous Deployment zu leben. Somit sind Aussagen der DORA-Studie über Continuous Delivery nur beschränkt für GitOps als Ganzes belastbar. Dennoch können sie ein Indikator dafür sein, ob Teams durch GitOps zu mehr Produktivität befähigt werden.

Continuous Delivery indiziert höhere Performance und bessere Sicherheit.

In diesem Kontext merkt die Studie an, dass die Nutzung von Continuous Delivery ganz generell mit höherer Software Delivery Performance gekoppelt ist – sowohl alleinstehend als auch in Kombination mit anderen DevOps-Methodiken. Teams, die eine höhere Bewertung im Bereich Continuous Delivery erhalten haben, haben eine größere Wahrscheinlichkeit dafür, dass sie häufiger Code in Produktion bereitstellen und kürzere Lieferzeiten für Änderungen und Wiederherstellung haben – sie schneiden also in drei der fünf Metriken besser ab. Ebenso lässt sich erkennen, dass die Umsetzung der technischen Aspekte von Lieferkettensicherheit (»Supply Chain Security«), dem diesjährigen Schwerpunkt der DORA-Studie, klar zusammenhängt mit der Nutzung von CI/CD.

Ohne Verlässlichkeit garantiert hohe Performance allein keinen Gesamterfolg.

Interessanterweise heben die Autoren hervor, dass ein gutes Abschneiden in den ersten vier Metriken kein Erfolgsgarant für ein Unternehmen ist, wenn ein Team nicht auch in der fünften Metrik (betriebliche Verlässlichkeit) erfolgreich ist. Ohne betriebliche Verlässlichkeit ist selbst eine sonst hohe Software Delivery Performance kein belastbarer Indikator für den Erfolg einer Organisation. Die Studie betont diese statistische Auffälligkeit im Kontext von Site Reliability Engineering (SRE), einer Disziplin zur Verbesserung der Verlässlichkeit.

SRE legt Wert auf empirisches Lernen, funktionsübergreifende Zusammenarbeit, weitgehendes Vertrauen auf Automatisierung und die Verwendung von Messverfahren, einschließlich Service Level Objectives (SLOs). (Auch wenn SRE bei Google entsprang, werden diese selben Prioritäten stellenweise auch mit anderen Bezeichnungen praktiziert. Die Studie verwendet SRE als Bezeichnung für die relevanten Praktiken und Werte, ohne in der eigentlichen Umfrage das Modewort direkt zu verwenden.)

Eine sehr aufschlussreiche Erkenntnis der Studie, die auch in der Publikation »Enterprise Roadmap to SRE«3 beschrieben wird, ist die sogenannte J-Kurve: Teams, die erst anfangen, SRE zu praktizieren, erzielen anfänglich eine bessere Verlässlichkeit, dann jedoch verschlechtert sich diese Verlässlichkeit, bis schließlich Kultur, Prozesse und Tooling aufgeholt haben und so weit aufeinander abgestimmt sind, dass die Verlässlichkeit deutlich besser wird und beständig zunimmt.

Dementsprechend sollen Teams, die SRE einführen, sich auf Rückschläge einstellen und durchhalten, denn auf dem langen Weg wird letztlich mit hoher Wahrscheinlichkeit bessere Verlässlichkeit herauskommen, und damit auch besserer Erfolg der Organisation. Nun sind SRE und GitOps zwei sehr unterschiedliche Themenfelder, aber dennoch haben sie eine Sache gemeinsam: Beide sind konkrete Implementierungen einer Kultur namens DevOps.

GitOps einzuführen braucht womöglich Ausdauer über die anfängliche leichte Beute hinaus.

Und GitOps sieht zwar oberflächlich betrachtet vor allem nach einer rein technischen Übung aus, bei der es um das Schreiben von Manifesten und Konfigurieren von Operatoren geht. Dennoch sind in GitOps auch subtilere soziotechnische Elemente enthalten wie das funktionsübergreifende Befähigen von Entwickelnden, die sowohl bei SRE als auch bei GitOps auftreten können und sollen. Deshalb sind wir der Überzeugung, dass auch Teams, die anfangen, GitOps einzuführen, sich sinnvollerweise auf eine solche J-Kurve einstellen sollten. Es lohnt sich, den Weg durch das (mögliche) Tal der Enttäuschung durchzuhalten, bis zunehmend die Früchte hinsichtlich Verlässlichkeit, Software Delivery Performance und Organisationserfolg reifen.

2.2Der Unterschied im Alltag: Geschichten eines Entwicklungsteams

Nachdem wir die empirischen Hinweise auf GitOps-Effekte untersucht haben, wenden wir uns einigen beispielhaften Situationen zu, wie sie in einem fiktiven Entwicklungsteam in einer fiktiven Firma auftreten könnten. Diese Erzählungen dienen nicht nur zur Veranschaulichung dessen, was GitOps ist, sondern auch, was GitOps nicht ist.

Diese Geschichten sind alle nur als Illustrationen zu verstehen und nicht als faktische Erzählungen. Dennoch entsprechen die beschriebenen Situationen insofern der Wahrheit, als dass wir sie alle in ähnlicher Form hautnah so erlebt haben.

Jede Geschichte beleuchtet eine typische Situation im Entwicklungsalltag und zeigt dasselbe fiktive Team, wie es einmal klassisch ohne GitOps arbeitet und wie es im Vergleich dazu mit GitOps handeln würde. Dieses Team, seinen Kontext, seine Repositories und Deployment-Strukturen malen wir zuerst aus, bevor wir die Geschichten erzählen.

2.3Szenario

Wir haben ein App-Entwicklungsteam in einem mittelständischen deutschen Unternehmen mit etwa 250 Mitarbeitern. Die Mitglieder dieses Entwicklungsteams sind:

Sie entwickeln und betreiben eine Java-Anwendung mit einem Next.js-Frontend und einer Postgres-Datenbank, die als ERP-Tool für interne und externe Anwender fungiert. Ihren Code verwalten sie in einem im eigenen Rechenzentrum gehosteten GitLab. Zur Organisation ihrer Arbeit nutzen sie Jira als SaaS, und das Unternehmen als Ganzes nutzt Confluence (SaaS) zur Dokumentation.

Das IT-Team ihrer Firma kümmert sich um das On-Premises-Hosting von GitLab und stellt ihnen als Entwicklungsteam zwei Kubernetes-Cluster im Haus zur Verfügung: eines für das Dev-Environment (»Dev«) und eines, in dem das Staging-Environment (»Staging«) und das Produktions-Environment (»Prod«) laufen. Das IT-Team nutzt intern Splunk zur Überwachung ihrer Infrastruktur und für Benachrichtigungen bei Problemen. Die Organisation insgesamt nutzt Microsoft Teams für Kommunikation und Organisation.

2.3.1Repositories

Welche Repositories verantwortet das Team? Wir unterscheiden an dieser Stelle (und im ganzen Buch) zwischen zwei Arten von Git-Repositories:

App-Repo Hier wohnt der Applikationscode (zum Beispiel Java, JavaScript).

Config-Repo Hier wohnen Manifeste , die für das Konfigurieren und Deployen der Applikationen relevant sind.

Ein Config-Repo kann beispielsweise eine Kustomize-Struktur mit Base und Overlays enthalten, welche die Kubernetes-Ressourcen der jeweiligen Applikation in den drei Environments beschreibt.

Tab. 2–1
Config-Repo vs. App-Repo

image

Tabelle 2–1 stellt Inhalt, Synonyme und typische Verzeichnisstrukturen von App-Repo und Config-Repo einander gegenüber.

In Abschnitt 6.4.3 auf Seite 141 betrachten wir das Für und Wider der Trennung der Repositories genauer.

Unser Entwicklungsteam verwaltet seine beiden Services hauptsächlich in den folgenden vier GitLab-Repositories:

  1. backend-app: Hier wohnt der Java-Code des Backends. Wir bezeichnen es im Folgenden auch als BE-App-Repo.
  2. backend-config: Hier wohnt eine Kustomize-Struktur mit Base und Overlays, welche die Kubernetes-Ressourcen des Backends in den drei Environments beschreibt. Wir bezeichnen es im Folgenden auch als BE-Config-Repo.
  3. frontend-app: Hier wohnt der Angular-Code des Frontends. Wir bezeichnen es im Folgenden auch als FE-App-Repo.
  4. frontend-config: Hier wohnt eine Kustomize-Struktur mit Base und Overlays, welche die Kubernetes-Ressourcen des Frontends in den drei Environments beschreibt. Wir bezeichnen es im Folgenden auch als FE-Config-Repo.

Das Team hat also zwei App-Repos und zwei dazugehörige Config-Repos.

2.3.2Deployment-Fluss

In Abb. 2–1 zeigen wir den Deployment-Fluss im Überblick inklusive der vier Repos, den beiden Clustern und den drei Umgebungen. Die Zahlen in der Abbildung entsprechen denen aus der folgenden Liste (ab Schritt 3).

Der Arbeitsrhythmus des Teams sieht grundsätzlich so aus:

  1. Entwicklung: Lucía, Viktor und Kareena arbeiten an Features für Frontend und Backend. Basierend auf Trunk-Based Development4 erstellen sie dafür kurzlebige Feature Branches.
  2. PR: Wenn ein Feature Branch bereit für Dev ist, erstellt die entwickelnde Person einen Pull Request, auf dem eine Pipeline durchläuft. Sobald diese fehlerfrei durchläuft, kann der PR in den Standard-Branch main gemergt werden.
  3. CD: Alles, was auf main ist, wird kontinuierlich nach Dev ausgerollt.
  4. Manuelle Promotion: Die Entwickelnden testen ihre Änderungen manuell auf Dev. Sobald sie mit ihrer Änderung zufrieden sind, nutzen sie eine manuelle Pipeline im jeweiligen Config-Repo, um nach Staging zu deployen.
  5. Manuelles Release: Thomas testet die Änderungen auf Staging. Wenn die Tests erfolgreich waren, deployt er jeden Donnerstag alle Änderungen nach Prod mittels einer manuellen Pipeline in einem Config-Repo.

2.3.3Vergleichsszenario »Mit GitOps«

Wie unterscheidet sich das alternative GitOps-Szenario vom bisher beschriebenen Szenario?

  1. In den beiden Kubernetes-Clustern läuft ein GitOps-Operator , in diesem Fall Argo CD.
  2. Es gibt ein fünftes Repository namens erp-gitops. Es ist ein zusätzliches Config-Repo.
    • Es bindet die beiden bestehenden Config-Repos ein in Form von Argo CD Applications.
image

Abb. 2–1
Branching und Deployment-Fluss

image

Abb. 2–2
Branching und Deployment-Fluss mit GitOps

In Abb. 2–2 sehen wir, wie sich der Deployment-Fluss insgesamt verändert.

In Abschnitt 3.3 auf Seite 51 tauchen wir ein in ein praktisches Beispiel, wie eine solche Implementierung aussehen kann.

2.4Kontinuierlich nach Dev deployen

Lucía, die Frontend-Entwicklerin, arbeitet an einer Änderung für das Frontend. Sie nutzt dafür einen Feature Branch. Sobald ihre Änderung bereit ist, erstellt sie einen Pull Request. Die Pipeline auf dem PR läuft erfolgreich durch, und Lucía mergt ihren Branch in den Standard-Branch. Die Pipeline auf main baut als letzten Schritt ein Docker-Image, publiziert es in die GitLab Container Registry und committet den neuen Image-Tag in das Dev-Overlay im FE-Config-Repo. In frontend-config läuft daraufhin eine Pipeline, die sich in den Dev-Cluster verbindet und dort einen kubectl apply -k overlays/dev ausführt.

Wir brauchen keinen Deploy-Schritt mehr in CI.

Mit GitOps läuft alles genauso wie oben beschrieben – bis auf den letzten Schritt: Das Config-Repo hat keine Deploy-Pipeline. Stattdessen läuft im Kubernetes-Cluster eine Anwendung, ein sogenannter »Operator«, der das Config-Repo überwacht und in regelmäßigen Abständen (vereinfacht gesprochen) ebendiesen kubectl apply -k overlays/dev ausführt.

Dieser kubectl apply wird beständig ausgeführt, egal ob es neue Änderungen gibt oder nicht. Dadurch werden manuelle Änderungen an den Kubernetes-Ressourcen, die im Config-Repo beschrieben sind (zum Beispiel manuelles Skalieren eines Deployments), automatisch überschrieben und mit den Deklarationen im Repository synchron gehalten.

Möglicherweise bereitet uns die Vorstellung Unbehagen, dass unsere Manifeste ständig angewendet werden. Womöglich fühlt sich das nach Ressourcenverschwendung an oder wir befürchten die Konsequenzen einer Fehlkonfiguration in einem Config-Repo.

Erstere Sorge ist durchaus berechtigt, allerdings erst ab einer beträchtlichen Größenordnung. Eine Handvoll Repositories wird – bei gesunder Standardkonfiguration – in der Regel keine merkliche Ressourcennutzung verzeichnen.

Sorge über kontinuierliches Angleichen führt uns zu tiefer liegenden Problemen.

Wenn wir uns jedoch um Letzteres Sorgen machen, dann sollten wir nicht vor GitOps zurückschrecken, denn GitOps wird an unserem eigentlichen Problem praktisch nichts ändern: Fehlkonfigurationen können jederzeit auftreten; mit GitOps kommen sie nur schneller ans Licht. Stattdessen sollten wir das Problem an der Wurzel angehen, und das besteht oft teils in Misstrauen gegenüber unserer Zielplattform und teils in technischen Stolperfallen.

Wenn wir größeres Vertrauen in unsere Zielplattform aufbauen wollen, kann das Nachforschen in vergangenen Ausfällen helfen, das tiefere Erlernen der Funktionsweise der Plattform und – im aufwendigsten Fall – ein Wechsel auf eine verlässlichere Plattform. Bei rein technischen Problemen können verpflichtende PRs auf Config-Repos eine Hilfe sein. In einer PR-Pipeline würden dann Linting-Tools laufen, um invalide Kubernetes-Manifeste zu entdecken, bevor sie deployt werden, oder Security-Tools, um unsichere Konfigurationen zu vermeiden. Der Kasten »Beispiele für Tools zur statischen Codeanalyse der Config« in 6.4.3 auf Seite 142 zeigt für beides konkrete Beispiele.

2.5Ressourcen aufräumen

Kareena, die Backend-Entwicklerin mit etwas DevOps-Tooling-Kenntnissen, soll den Apache Solr, den das Backend verwendet, durch einen Elasticsearch austauschen. Die Codeänderungen hat Viktor bereits vorbereitet. Also bearbeitet sie die Infrastruktur des Backends im BE-Config-Repo: Sie ersetzt das Apache-Solr-StatefulSet mit einem Elasticsearch-StatefulSet.

Die Pipeline in backend-config führt daraufhin einen Apply aus, und kurz darauf startet ein Elasticsearch im Dev-Cluster. Kareena arbeitet weiter an anderen Issues. Ein paar Stunden später schaut sie im Zuge einer anderen Aufgabe im Dev-Cluster vorbei – und bemerkt verwundert, dass der Apache Solr immer noch vorhanden ist. Ist die Pipeline fehlgeschlagen? Laut den Logs in GitLab hat alles normal funktioniert.

Da erinnert sich Kareena, dass kubectl zustandslos agiert und eigentlich nicht wissen konnte, dass sie auch Ressourcen löschen will. Sie führt den kubectl delete manuell aus und erstellt einen Jira-Issue, um die Machbarkeit von Helm für ihr Team zu evaluieren. (Genauer gesagt: Sie findet einen schon existierenden Issue dafür, der seit neun Monaten nicht mehr aktualisiert wurde. Mit einem Seufzer schiebt sie ihn im Backlog weiter nach oben.)

GitOps ermöglicht automatisches Pruning ohne zusätzlichen Aufwand.

Mit GitOps bleibt der erste Schritt gleich: Wir verändern Manifeste in einem Config-Repo, um eine Ressource zu ersetzen. Allerdings hat das Config-Repo (wie auch in der vorherigen Geschichte) keine Deploy-Pipeline, sondern stattdessen läuft ein Operator im Cluster. Dieser Operator hat die gesamte Git-Historie des Config-Repos zur Verfügung. Er führt eine Angleichung der neusten Manifeste aus und räumt automatisch auch die Ressourcen von gelöschten Manifesten auf.

Ein solches Pruning kann sehr invasiv wirken, und es kann durchaus Umstände geben, unter denen das Deaktivieren von Pruning sinnvoll sein kann (beispielsweise für die Manifeste, mit denen der GitOps-Operator selbst installiert wird). In den allermeisten Fällen fördert aktiviertes Pruning aber die Developer Experience, und wir empfehlen es als Standardeinstellung.

Exkurs: GitOps als intuitivste Art, mit Kubernetes umzugehen

Eine Sache lässt sich an dieser Geschichte besonders gut sehen, und es ist auch eine Entdeckung, die wir in Kubernetes-Trainings immer wieder gemacht haben: Für viele Menschen ist GitOps ein viel natürlicherer Zugang zu Kubernetes als der imperative Weg über kubectl apply oder helm upgrade --install:

Einige Teilnehmer solcher Trainings erwarteten nach ihrem allerersten kubectl apply -f manifest.yaml, dass der Kubernetes-API-Server ab jetzt die lokale Datei auf ihrer Festplatte kontinuierlich überwacht und sie nur noch speichern müssen, damit ihre Änderungen im Cluster ankommen. Diese Personen haben quasi die GitOps-Prinzipien 3 und 4 auf ganz natürliche Weise verinnerlicht. Und diese Intuition ist wertvoll, weil wir den Schritt des »Speicherns« nur noch um git commit && git push erweitern müssen, um bei GitOps zu landen.

In Wirklichkeit verschickt die kubectl-CLI natürlich nur den Dateiinhalt als String, und das auch nur einmalig auf Befehl. Aber diese Implementierungsdetails und die anstrengende Tatsache, dass man ab jetzt immer wieder einen kubectl apply ausführen muss (und irgendwann auch mal einen kubectl delete), können wir mit GitOps elegant wegabstrahieren. Und diese Abstraktion ist sehr wertvoll, weil sie den Zugang zu Kubernetes erleichtert für noch mehr Menschen, und zwar aus folgenden Gründen:

  1. GitOps lenkt den Fokus mehr auf die Inhalte der Manifeste und weg vom konkreten Umgang damit – und deklarative Manifeste sind der Kern von Kubernetes. Technisch betrachtet können wir gewissermaßen einen Teil der Inhalte von etcd vorverlagern nach Git.
  2. Durch die Lagerung in Git und die kontinuierliche Überwachung und Anwendung wird das Bearbeiten von Kubernetes-Ressourcen demokratisiert: Jeder mit Zugriff auf das SCM kann Pull Requests erstellen – und es braucht keinen zusätzlichen Schritt (noch nicht einmal eine automatische Pipeline), damit die Änderungen direkt ankommen.

2.6Grenzfälle in CI vermeiden

Wir knüpfen an die erste Situation an (siehe Abschnitt 2.4 auf Seite 34): Lucía hatte ihre Frontend-Änderung am Montag auf Dev deployt, indem sie ihren Pull Request gemergt hatte. Direkt nach dem Durchlaufen der Pipeline überprüfte sie ihre Änderung und alles sah gut aus, also startete sie eine weitere Pipeline in frontend-config. Diese Promotion-Pipeline rollt einen Frontend-Image-Tag von Dev nach Staging aus (oder alternativ von Staging nach Prod) und sieht ungefähr so aus:

Listing 2–1
Skript zum Promoten von Dev nach Staging

1

#!/usr/bin/env sh

2

set -eux

3

 

4

source_env=dev

5

target_env=staging

6

 

7

# 1) Copy image tag from source to target overlay

8

yq '.images.0.newTag' \

9

"overlays/$source_env/kustomization.yaml" \

10

| xargs -I % yq -iP '.images.0.newTag = "%"' \

11

"overlays/$target_env/kustomization.yaml"

12

 

13

# 2) Commit idempotently and push the change

14

git add "overlays/$target_env/kustomization.yaml"

15

git commit -m "chore($target_env): \

16

promote $source_env to $target_env" || true

17

git push origin

18

 

19

# 3) Connect to the prod cluster

20

kubectl config use-context prod

21

 

22

# 4) Apply the target overlay

23

kubectl apply -k "overlays/$target_env"

Heute ist Dienstag; das nächste Release auf Prod steht in zwei Tagen an. Thomas, der QA-Engineer, lässt sich in Jira die Issues auflisten, die seit dem letzten Release abgeschlossen wurden. Der Issue mit der Frontend-Änderung von Lucía ist der neueste darunter.

Er öffnet das Frontend von Staging und beginnt, die Änderungen der verschiedenen Issues zu überprüfen. Als er bei Lucías letzter Story ankommt, wundert er sich, dass er die in der User-Story beschriebenen Änderungen gar nicht vorfindet. Er leert seine Browser-Caches und probiert es in einem Inkognito-Fenster, aber nichts ändert sich.

Kann es sein, dass Lucía vergessen hat, von Dev nach Staging zu deployen? Er schreibt Kareena an und bittet sie, kurz im Cluster zu prüfen, ob die Frontend-Änderungen schon auf Staging angekommen sind. Kareena ist den ganzen Tag in Meetings und kann Thomas nur kurz schreiben, dass er im Config-Repo nachschauen soll: In den Kustomizations stehen immer die aktuellen Image-Tags drin, und die Image-Tags sind so strukturiert, dass sie immer auch die Commit-ID von frontend-app enthalten.

Thomas öffnet das Repo frontend-config und vergleicht die Commit-ID im Image-Tag mit dem neuesten Commit im FE-App-Repo: Sie stimmen überein; also hat Lucía die Pipeline korrekt ausgeführt und die Änderung müsste definitiv angekommen sein! Woran kann es jetzt noch liegen?

Er schaut sich die letzten Pipelines in frontend-config an und sieht, dass die neueste Pipeline seit über einer Stunde läuft und feststeckt. Die Logs deuten zwar darauf hin, dass der Commit funktioniert hat, aber der Cluster war wohl für die Pipeline kurzzeitig netzwerktechnisch nicht erreichbar, und dann blieb der Prozess bei Schritt 4 stecken.

Thomas bricht die Pipeline ab und startet sie neu. Dieses Mal läuft sie erfolgreich durch. Er wartet ein paar Minuten, öffnet das Frontend von Staging und kann jetzt endlich die Änderungen sehen und sie testen.

GitOps vermeidet einige Sondersituationen von CI-getriggerten Rollouts.

Mit GitOps würde die Pipeline im Config-Repo, welche die Promotion von Dev nach Staging ausführt, ausschließlich einen Commit pushen auf sich selbst. Die Schritte 3 und 4 wären nicht Teil dieser Pipeline. Dadurch würde die Blockade-Situation in dieser Geschichte überhaupt gar nicht auftreten.

Auch hier gilt wie in den vorherigen Geschichten: Das kontinuierliche Anwenden des gewünschten Zustandes geschieht durch Operatoren im Cluster, die beständig das Repository klonen und anwenden. Dadurch würde ein stecken gebliebener Apply nie ein Problem darstellen, weil dieser Apply nicht punktuell läuft, sondern kontinuierlich immer wieder durchgeführt wird.

Kurze Verbindungsprobleme behindern den GitOps-Operator nicht.

Auch kurzzeitige Konnektivitätsprobleme zwischen CI-Server und Cluster (oder SCM und Cluster) wären keine Problematik, die wir bedenken müssten: Während der CI-Server nur punktuell auf Zuruf läuft, arbeitet der Operator im Cluster kontinuierlich. Das gilt sowohl für Prinzip 3 als auch für Prinzip 4: Das Laden des Repositorys erholt sich automatisch, wenn bei einem der nächsten Durchläufe das SCM wieder erreichbar ist. Währenddessen läuft aber auch die Angleichung der gecacheten Manifeste asynchron weiter. (Weitere Folgen dieser Asynchronität beleuchten wir in Kapitel 7 auf Seite 197 genauer.)

Sind deshalb alle Deploy-Mechanismen böse, mit denen wir von außerhalb auf den Cluster zugreifen? Natürlich nicht! Das GitOps-Prinzip 3 verbietet mit keinem Wort, dass man nicht auch ereignisgetrieben arbeiten darf: Das Team könnte ergänzend zum GitOps-Operator aus GitLab heraus einen Webhook im Cluster aufrufen, der den Operator über einen neuen Commit benachrichtigt. Dennoch ist der Kern von Prinzip 3 und 4, dass der primäre Mechanismus das kontinuierliche Beziehen und Angleichen sein muss.

Monitoring-Aufwände können sich vereinfachen.

Betrachten wir auch noch kurz die Problemstellung von Monitoring: Spricht irgendetwas dagegen, dass wir in unsere CI-Pipeline Überwachung einbauen und bei Fehlerfällen eine Benachrichtigung verschicken? Mitnichten! Dennoch können wir uns manche Aufwände für Monitoring sparen, wenn wir einen GitOps-Operator nutzen.

Selbstverständlich können auch beim GitOps-Operator und im Cluster generell Probleme auftreten, über die wir benachrichtigt werden wollen. Diesem Thema widmen wir uns auch in Kapitel 8 auf Seite 229 ausführlicher. In jedem Fall sind wir bei GitOps von der impliziten Grundvoraussetzung von CI-getriebenem Deployen befreit, nämlich dass unser CI-Server immer verlässlichen »Sichtkontakt« zum Zielsystem haben muss.

2.7Ressourcen wiederherstellen

Das IT-Team will einiges an veralteter Hardware austauschen, und darunter sind auch die Server, auf denen die VMs des Dev-Clusters laufen. Die IT gibt dem Entwicklungsteam Bescheid, damit sie Maßnahmen für den Umzug auf den neuen Dev-Cluster treffen können, sobald es eingerichtet ist.

Kareena listet alle Pods im Dev-Cluster auf und erstellt eine Liste mit den Applikationen, die sie außer dem Backend und dem Frontend auch wieder im neuen Dev-Cluster installieren muss. Darunter sind zwei Handvoll Helm-Charts von Open-Source-Software.

Als der neue Dev-Cluster bezugsfertig ist, führt sie alle helm install-Befehle aus von den Applikationen, die sie im Dev-Cluster gefunden hat. Als sie damit fertig ist, ändert sie die Verbindungsdaten in den Deploy-Pipelines von Backend und Frontend auf den neuen Cluster und startet die Pipelines von Hand. Die Pipeline läuft erstaunlicherweise direkt beim ersten Durchlauf fehlerfrei durch.

Als sie aber die Pods untersucht, sieht sie, dass die Backend-Pods ständig abstürzen. In den Logs wird klar, dass noch eine Applikation fehlt, die im alten Cluster in einem Namespace wohnte, den Kareena vergessen hatte zu überprüfen. Außerdem hat sie bei einem der Helm-Charts aus Versehen eine neuere Version installiert als damals im alten Cluster.

Ein Inventar nach GitOps-Prinzip 1 ermöglicht reproduzierbare Wiederherstellung.

Sie geht noch einmal alle Namespaces durch, installiert die restlichen Applikationen und ändert die Version des aktuell installierten Helm-Releases. Kareena erstellt sich auch einen neuen Issue am Ende des Backlogs. Sie benennt ihn »Create manual pipeline for full initial provisioning of cluster«.

Mit GitOps würde Kareena nicht manuell den Cluster durchsuchen, sondern alle Applikationen (zum Beispiel Helm-Releases) wären im Config-Repo erp-gitops hinterlegt.

Schauen wir uns ein Beispiel von solch einem Helm-Release-Manifest im Fall von Argo CD an:

Listing 2–2
Manifest eines Helm-Releases bei Argo CD

1

apiVersion: argoproj.io/v1alpha1

2

kind: Application

3

metadata:

4

name: erp-gitops

5

namespace: argocd

6

spec:

7

project: erp-dev

8

source:

9

chart: splunk-connect-for-kubernetes

10

repoURL: >-

11

https://splunk.github.io/splunk-connect-for-kubernetes

12

targetRevision: 1.5.0

13

helm:

14

parameters:

15

- name: global.logLevel

16

value: debug

17

- name: kubernetes.clusterName

18

value: erp-dev

19

destination:

20

server: https://api.erp-dev.k8s.ac.me

21

namespace: erp-gitops

Wir nutzen in diesem Fall Helm als Beispiel, weil es weit verbreitet ist und gleichzeitig nicht vollständig über die kubectl-CLI abgebildet werden kann (beispielsweise als ausgerenderte Manifeste).

Weil Kareena alle Anwendungen als Manifeste nach der gezeigten Art hinterlegt hat, muss sie beim Provisionieren nur noch folgende Schritte manuell durchführen:

  1. Sie erstellt neue Deploy-Tokens in GitLab, die reine Leserechte für erp-gitops und die beiden Config-Repos haben.
  2. Sie bootstrappt Argo CD: sie installiert es manuell in den Cluster und konfiguriert es so, dass es das erp-gitops Repository überwacht mithilfe des Deploy-Tokens.
  3. Weil erp-gitops alle Manifeste für die Helm-Charts enthält und auch die Config-Repos einbindet, wird alles andere (außer Argo CD) vollständig automatisch installiert.

    Übrigens: In Abschnitt 6.6.1 auf Seite 168 erörtern wir, warum es auch empfehlenswert ist, auch Argo CD per GitOps zu betreiben.

Da die Helm-Releases aus dem Config-Repo heraus erstellt wurden, werden sie auch in Zukunft in Git verwaltet.

Anfangs mag dieses Vorgehen mühselig erscheinen. Schließlich ist ein imperativer helm install oft leichter und schneller getan als das Schreiben und Committen eines Manifests. Das gilt allerdings meist nur für das initiale Aufsetzen oder für kurzlebige Experimente.

Vergleichen wir doch für einen Moment, wie wir das Verhalten des obigen Manifests auf imperative Weise nachstellen könnten. Dafür würden wir beispielsweise eine Pipeline erstellen, die alle zehn Minuten läuft und einen helm repo add und einen helm upgrade --install ausführt.

Der erste Befehl entspricht dem Parameter .spec.source.repoUrl im vorherigen Manifest; der zweite Befehl entspricht dem gesamten Block .spec.source. Das sind definitiv weniger Codezeilen als unser Manifest.

Ein Installieren geht damit sicher einfach, aber nachfolgende Konfigurationsänderungen oder Versionsupdates sind ohne deklarative Formate schwerer auszuführen. Und selbst wenn wir ein solches imperatives Skript in einem Repo aufbewahren (was Prinzip 2 ähneln würde), sind die Änderungen und ihre Auswirkungen schlechter nachvollziehbar als bei deklarativen Manifesten. Auch können wir Löschungen von Anwendungen damit nicht in Git festhalten (siehe Abschnitt 2.5 auf Seite 35).

Bestehende Manifeste bahnen den Weg für zunehmende Umdeklarierung.

Unserer Erfahrung nach kann das Einführen von GitOps eine natürliche Sogwirkung entwickeln hinsichtlich der GitOps-Prinzipien 1 und 2: Ein Team fängt meist mit rohen Kubernetes-Manifesten oder Kustomizations an, die in Git liegen und dann mit GitOps überwacht werden. Wenn GitOps sich langsam bewährt und man beispielsweise davon erfährt, dass man auch Helm-Releases mit GitOps verwalten kann, bringt das Team leichter aus eigener Überzeugung immer mehr Anwendungen in den Clustern unter GitOps-Kontrolle, bis schließlich sogar die zugrunde liegende Infrastruktur mit GitOps verwaltet wird (siehe Kapitel 11 auf Seite 291).

2.8Konfigurationsänderungen ausrollen

Kareena ist im Urlaub. Viktor, ihr Backend-Kollege, arbeitet an einer Änderung in der Suchfunktionalität des Backends. Lokal funktionieren seine Änderungen, aber als er seinen Feature Branch mergt und die Änderungen auf Dev testet, erscheinen Fehlermeldungen, die er noch nie gesehen hat. Nach ein wenig Recherche vergleicht er die Version seines lokalen Elasticsearch mit der Version, die im Cluster deployt ist – und tatsächlich besteht ein relevanter Versionsunterschied.

Wie soll er das Helm-Release nun aktualisieren? Viktor hat nur ganz wenige Berührungspunkte mit Helm gehabt, und auch in Confluence findet er keine Hilfestellung zum Aktualisieren von Elasticsearch. Er forscht herum und findet ein Helm-Chart auf Artifact Hub, das passen könnte. Wie bekommt er jetzt nur Zugang zum Cluster? Er wühlt sich durch die Dokumentation, installiert die kubectl und helm CLIs und findet schließlich auch seine Zugangsdaten zum Cluster und erzeugt daraus eine Kubeconfig. Endlich kann er mit den Standardbefehlen von Artifact Hub das Chart installieren.

Nach ein paar Minuten sind immer noch keine Pods da. Woran kann es nur liegen? Viktor bemerkt, dass er das Release im falschen Namespace platziert hat. Nach weiterer Recherche schafft Viktor es, sein Release zu deinstallieren und dieses Mal im richtigen Namespace zu installieren. Nun werden die alten Pods tatsächlich ersetzt mit der neuen Version, die er braucht. Bei den Tests, die er nun durchführt, klappt endlich alles wie lokal vorbereitet. Viktor notiert die Schritte zum Upgraden des Elasticsearch-Charts als Kommentar im Jira-Issue.

GitOps senkt die Eintrittsbarriere für Contributors.

Mit GitOps ist das Helm-Release von Elasticsearch als Manifest im Config-Repo erp-gitops vorhanden, ähnlich wie das Manifest aus der vorigen Geschichte. Um die Version zu aktualisieren, sucht Viktor die passende Zeile im Manifest, committet eine Änderung auf einen Feature Branch und mergt den PR, nachdem die Pipeline erfolgreich durchgelaufen ist. Diese Änderung wiederum wird automatisch ausgerollt ohne zusätzliche Schritte.

Konkretes Wissen über Helm benötigt Viktor in diesem Fall nicht, weil die Schnittstelle dieselbe ist wie bei allen anderen Ressourcen in Kubernetes auch: das Kubernetes Resource Model (KRM)5, das heißt YAML-Manifeste mit Kubernetes-Struktur. Damit gibt uns GitOps im Kontext von Kubernetes quasi eine deklarative Schnittstelle für Deployments und noch viel mehr. Der Dokumentationsaufwand für Upgrades, wie in dieser Geschichte beschrieben, reduziert sich damit auf ein Minimum. Auch Zugriff auf den Cluster sowie die Installation, Konfiguration und das Verständnis der CLIs sind seltener notwendig.

Erleichterter Zugang erhöht Ownership und Performance.

Je geringer die Eintrittsbarriere für alle Teammitglieder ist, um Änderungen an Deployments und Infrastruktur vorzunehmen, desto mehr kann das Team als Ganzes die volle Verantwortung für seine Anwendung übernehmen. Außerdem bedeuten mehr befähigte Contributors auch kürzere Wiederherstellungszeiten, wenn Incidents auftreten.

Ehrlicherweise erhöht sich natürlich nicht nur die Wiederherstellungsgeschwindigkeit, sondern auch die Zerstörungsgeschwindigkeit – mehr Contributors bedeutet auch mehr Fehlerpotenzial. Allerdings ist es auch mit dieser Befürchtung ähnlich wie in Abschnitt 2.4 auf Seite 34: GitOps führt solche Problematiken nicht aus dem Nichts herbei, sondern bringt sie vor allem ans Licht und verstärkt sie in manchen Fällen. Die Sorge um Fehlkonfigurationen führt uns im besten Fall zu grundlegenderen Antworten wie solchen, die im eben erwähnten Abschnitt beschrieben werden: Ein Verschieben nach links innerhalb des Softwareentwicklungs-Lebenszyklus durch automatische Pipeline-Schritte hilft uns am meisten weiter.

2.9Incidents navigieren

In der Vergangenheit gab es einmal einen Vorfall, bei dem das Backend so langsam war, dass praktisch keine Anfragen vom Frontend mehr eine Antwort bekamen. Schlussendlich stellte sich heraus, dass das Backend immer mehr Arbeitsspeicher konsumierte, bis alle Ressourcen der VM aufgebraucht waren.

Ein Memory Leak wurde damals als grundlegende Ursache nicht ausgeschlossen, und nach dem Vorfall arbeiteten Viktor und Kareena längerfristig daran, die Ursachen dafür zu untersuchen. Um aber auch kurzfristig eine Stabilisierung zu erzielen, führten sie Resource Limits ein, damit kein Backend-Container mehr als 8 GB RAM auf einmal verbrauchen kann.

Es ist Donnerstag – heute wird das neue Release auf Prod ausgerollt. Direkt nach dem Daily am Vormittag startet Thomas die Promotion-Pipeline und Kareena hat währenddessen ein Auge auf die Pods der Deployments. Nach 15 Minuten sind alle neuen Image-Tags erfolgreich ausgerollt. Thomas überprüft die Anwendungen im Prod-Environment mit einem kurzen Smoke-Test und meldet keine Auffälligkeiten. Alle sind glücklich über den reibungslosen Ablauf, und Jamal schließt das Release in Jira ab.

Aber eine halbe Stunde später erhält Jamal, der Teamleiter und Product Owner, zunehmend Meldungen von internen Nutzern, dass der Login in die Applikation nicht funktioniert wegen »502 Bad Gateway«. Er probiert es selbst und schafft es ebenfalls nicht, sich erfolgreich anzumelden. Jamal trommelt die anderen auf Teams zusammen in einem spontanen Videoanruf.

Viktor und Kareena wählen sich in den Cluster ein und sehen dort beunruhigende Zustände: Die Backend-Pods sind ohne Ausnahme bei über 90 % ihres Memory-Limits. Offensichtlich verbraucht das Backend auf Prod noch mal deutlich mehr Arbeitsspeicher als auf Dev oder Staging. Als kurzfristige Maßnahme erhöht Kareena das Limit für die Backend-Pods auf 12 GB, indem sie mit kubectl edit die Deployment-Ressource bearbeitet. Die Bearbeitung des Deployments erzeugt die Backend-Pods neu. Nachdem die neuen Pods des Deployments erfolgreich gestartet sind, können sich die internen User wieder einloggen und wie gewohnt arbeiten.

Allerdings finden einige User einen neuen Bug im Frontend, der sie daran hindert, Auftragsdaten an die Kunden zu liefern. Jamal meldet das Problem im Teams-Kanal des Teams und fragt in die Runde, ob ein Rollback sinnvoll ist. Kareena zögert sehr, weil sie die Pipeline dafür schon lange nicht mehr ausgeführt hat und sie auch keine Tests dafür hat. Und weil Lucía optimistisch ist, dass sie den Bug zügig beheben kann, entscheidet sich Jamal für einen Hotfix.

Lucía macht sich also an die Arbeit, und eine intensive Stunde später ist ihr Fix fertig. Sie rollt nach Dev aus, indem sie ihren PR mergt. Und tatsächlich: Der Bug ist auf Dev behoben, also promotet sie zuerst nach Staging und dann nach Prod.

Nachdem die Release-Pipeline für Prod durchlief, melden User allerdings wieder die gleichen Login-Problem wie am Vormittag. Viktor erkennt das Problem: Durch das erneute Ausrollen auf Prod wurde Kareenas manuelle Änderung am Memory-Limit überschrieben!

Er zieht sofort die Änderung an der Ressourcenbeschränkung im Manifest im Config-Repo nach, pusht den Commit und führt erneut die Release-Pipeline aus. Dieses Mal bleibt die Beschränkung erhalten und keine User melden sich mehr mit Login-Problemen. Alle lehnen sich wieder beruhigt zurück und Kareena erstellt einen Monitor für Ressourcenbeschränkungen und einen Alarm für den Fall, dass Pods länger als 30 Minuten bei über 90 % ihres Memory-Limits laufen.

GitOps macht uns etwas langsamer, aber dafür deutlich stabiler.

Mit GitOps hätte Kareena (im besten Fall) das Memory-Limit direkt per Commit geändert. Denn selbst wenn sie es manuell gesetzt hätte (zum Beispiel mit kubectl edit), wäre ihre Änderung nach kurzer Zeit automatisch vom Operator überschrieben worden.

Ein solches Vorgehen bremst uns definitiv aus: Statt eines imperativen Befehls in einem Cluster, mit dem wir möglicherweise sowieso schon verbunden sind, müssen wir ein Manifest suchen, bearbeiten, committen, pushen, eventuell erst noch auf eine PR-Pipeline warten und erst dann können wir das Ergebnis unserer Bemühungen sehen. Für die alltägliche Nutzung eines Config-Repos ist es definitiv gut, dass wir diese Leitplanken haben: Sie geben uns Stabilität und vereinfachte Schienen, auf denen wir fahren können. Außerdem können wir in der Historie zurückblättern und den Änderungsverlauf an einer Ressource viel besser verstehen als ohne GitOps.

Manuelles Eingreifen ist dennoch möglich.

Diese Vorteile sind wie gesagt vor allem für den alltäglichen Umgang mit unseren Config-Repos wichtig und hilfreich. Und mit etwas Übung kann man auch schneller im Committen werden, sodass wir auch kleinere Incidents in GitOps-Art navigieren können. Dennoch wollen wir manchmal in zeitkritischen Situationen schneller agieren können.

Glücklicherweise stehen GitOps und imperatives Eingreifen in keinem grundsätzlichen Widerspruch. Die meisten GitOps-Operatoren erlauben ein Pausieren der automatischen Angleichung nach GitOps-Prinzip 4. (Mehr dazu erfahren wir in Kapitel 9 auf Seite 247.)

2.10Zielsysteme besser absichern

Lucía kennt sich noch nicht wirklich gut mit Kubernetes aus, findet die Technologie aber sehr spannend und schaut sich deshalb neugierig in den beiden Clustern um. Auf Prod entdeckt sie mehrere Pods, die sie in Dev und Staging nicht vorfindet. Die Namen sind sehr generisch, und weder ChatGPT noch Google lassen sie damit eine spezifische Anwendung finden, zu der die Pods gehören könnten.

Sie fragt Kareena, was für Pods das sind und wozu sie gut sind. Kareena wiederum ist alarmiert: Wer hat hier unbekannte Workloads deployt? Sie untersucht die Pods und bald formt sich ein Verdacht: Hier betreibt ein externer Angreifer Cryptomining in ihren Clustern.

Bevor Kareena die Pods und deren Controller, ein DaemonSet, löscht, exportiert sie diese für die Analyse als Manifeste. Anschließend forscht sie nach, über welchen Weg der Angreifer Zugriff auf den Cluster bekommen haben könnte. Ein direkter Zugriff über kubectl ist unwahrscheinlich, da dieser Zugriff nur per VPN erfolgen kann.

Sie fragt beim IT-Team nach, ob sie eine Möglichkeit haben herauszufinden, über welchen User diese Ressource erstellt wurde. Glücklicherweise ist die Audit Policy des Clusters fein genug konfiguriert, sodass die IT herausfinden kann, dass das DaemonSet offensichtlich vom GitLab-CI-User deployt wurde. Kareena forscht in den Config-Repos nach, kann aber das DaemonSet nirgends dort finden.

Kareena weiß nicht weiter und fragt den Rest des Teams um Rat. Thomas hat eine Vermutung und bittet alle darum, ihre aktiven Sessions in GitLab auf ungewöhnliche Aktivität zu überprüfen. Und tatsächlich werden sie fündig bei Lucía, die eine neue Session vom Tag vor dem Ausrollen des DaemonSets hat.

Darauf angesprochen, kann Lucía sich beim besten Willen nicht daran erinnern, zu besagter Uhrzeit sich neu in GitLab angemeldet zu haben. Beschämt gesteht sie aber, dass sie sowohl für den LDAP-Login beim VPN als auch beim öffentlich erreichbaren Login von GitLab dasselbe Passwort verwendet.

Also hat jemand Lucías Passwort erraten oder gestohlen und sich damit in GitLab angemeldet. Und da in GitLab in den Variablen des Config-Repos die Zugangsdaten des CI-Users für beide Cluster hinterlegt waren, war die Tür offen: Er konnte sich mit ihrem LDAP-User ins VPN verbinden und mit dem CI-User auf den Cluster, um dann dort den bösartigen Workload auszurollen.

Als ersten Schritt zur Absicherung beendet Lucía die Session des Angreifers und ändert ihre beiden Passwörter. Gleichzeitig ändern auch alle anderen im Team ihre Passwörter, und alle gemeinsam aktivieren Multi-Faktor-Authentifizierung (MFA). Viktor erstellt auch ein Ticket bei der IT mit Bitte um Aktivieren von erzwungener MFA für alle GitLab-User.

Wir müssen CI-Servern keinen Zugriff mehr auf Zielsysteme geben.

Mit GitOps können wir sicherlich nicht alle Angriffsvektoren verhindern – aber eine bestimmte Gefahrenquelle können wir deutlich entschärfen, nämlich Secrets in unseren CI-Servern. CI/CD-Systeme sind in aller Regel sehr privilegierte Applikationen, weil sie auf viele andere Systeme Zugriff brauchen, um Prozesse automatisieren zu können, und dafür machen sie meist von Usern mit administrativen Rechten Gebrauch. Für Angreifer sind sie eine wahre Goldgrube.

Auch wenn wir es vermutlich nicht schaffen werden, alle Secrets aus der Konfiguration unseres CI-Servers zu entfernen, haben wir mit GitOps dennoch zumindest keine Notwendigkeit mehr dafür, dass das CI/CD-System Zugriff auf Zielsysteme wie zum Beispiel Kubernetes-Cluster hat.

Im Vergleichsszenario hätte es also in GitLab keine Kubeconfig in den CI/CD-Variablen des Config-Repos gegeben. Dadurch hätte der Angreifer nur über einen Commit ins Config-Repo seinen Workload deployen können. Das wäre einerseits deutlich auffälliger gewesen, andererseits wäre es auch leichter zu verhindern, indem man den Standardbranch durch erzwungene Merge-Requests mit manueller Freigabe absichert.

Übrigens: Mit GitOps würden wir auch die Secrets, die in der Anwendung verwendet werden (beispielsweise Zugängen zu Datenbanken oder externen Diensten), nicht im Credentials-Store des CI-Servers speichern. Dadurch wären sie in unserem Szenario nicht gefährdet. Stattdessen sind wir hier zum Wohle der Sicherheit gezwungen, dedizierte Werkzeuge zum Secrets Management einzusetzen. In Kapitel 5 auf Seite 97 schauen wir uns verschiedene Optionen an.