Environment Propagation

Im vorherigen Teil dieser Serie haben wir verschiedene Konzepte erläutert und erklärten, wie wir unser GitOps Konzept sicher gestalten können.
Im letzten Teil der Serie beleuchten wir zum Abschluss das Thema Environment Propagation.

Nachfolgende Beispiele stützen sich alle auf ein Helm-basiertes Deployment. Grundsätzlich lassen sich die Beispiele ohne weiteres auch auf Kustomize übertragen.

Git Branch Model

„Welches Git Branching Model soll ich für GitOps verwenden?“ Diese Frage taucht öfters auf, sobald man sich mit GitOps befasst. Auf diese Frage gibt es keine Standardantwort. Daher möchte ich euch drei verschiedene Branching Modelle mit den entsprechenden Vor- und Nachteilen aufzeigen.

Multi Branch Model

Das Multi Branch Model dürfte den meisten Entwickler*innen unter euch ein Begriff sein und ist meistens auch die erste Wahl, wenn es um das GitOps Branching Modell geht. Die Idee dahinter ist, für jede Umgebung einen eigenen Branch zu erstellen (z.B. Dev / Staging / Production).

Die Dateistruktur sieht folgendermassen aus:

.
├── Chart.yaml
└── values.yaml

Es existiert nur ein Values File, die umgebungsspezifischen Values sind im selben Values File, aber in unterschiedlichen Branches abgelegt.
Um Änderungen zu promoten, werden diese zuerst auf dem Dev Branch gemacht und anschliessend in den Staging Branch gemergt und ausgerollt. Dasselbe gilt für das Promoten von Staging nach Production.

Betrachten wir aber folgenden Fall, stellt sich schnell heraus, dass dieses Modell in der Theorie besser funktioniert als in der Wirklichkeit.

In diesem vereinfachten Beispiel haben wir zwei verschiedene Branches. Einerseits ein Dev Branch, auf welchem in hoher Frequenz Änderungen erfolgen, welche direkt in die Dev Umgebung deployed werden. Andererseits den Production Branch, welcher folglich die Änderungen in die Produktionsumgebung deployed.

Wie wir auf oben stehender Grafik erkennen können, gibt es vier verschiedene Commits auf dem Dev Branch:

  • Image Version wird geändert
  • Replicas werden nach einem früheren Test wieder auf 1 gestellt
  • Der Mount Pfad eines Volumes wird angepasst
  • Eine neue Environment Variable wird definiert mit einer umgebungsspezifischen URL (z.B. ldap-test.puzzle.ch)

An dieser Stelle sollte bereits sichtbar sein, warum dieses Modell nicht so gut funktioniert wie gedacht. Von diesen vier Commits sollen nur drei in die Produktion einfliessen. Der Commit mit den Replicas muss ignoriert werden, sonst wird die in der Produktion definierte Anzahl Replicas überschrieben. Auch der letzte Commit kann nicht so einfach übernommen werden, da dieser nebst der neuen Environment Variable auch noch umgebungsspezifische Werte enthält (LDAP Url).

Aus diesem Grund ist es nicht mehr möglich, den Branch einfach in die nächste Stage zu mergen und somit die Konfiguration zu promoten. Somit endet man entweder mit Cherry-Picking von einzelnen Commits, oder überträgt die Änderungen manuell auf den nächsten Branch. Beide Möglichkeiten sind aber fehleranfällig und skalieren nicht wirklich gut mit der Anzahl von Environments.

Ein weiteres Problem, das wir häufig antreffen, sind sogenannte Hotfixes in Staging & Production Branches. Wie bereits zu Beginn erwähnt, leben wir nicht in einer perfekten Welt. Daher kann es immer wieder vorkommen, dass wir ungeplant Hotfixes in (Semi-)Produktiven Umgebungen deployen, ohne dass wir diese Anpassungen wieder backporten. Dazu kommt, dass wir das Backporten von Änderungen nur manuell oder mit Cherry-Picking bewerkstelligen können. Es ist im Grunde genommen möglich, aber auf keinen Fall ratsam, Branches in die Gegenrichtung zu mergen (Prod -> Staging -> Dev)

Single Branch Model

Als Alternative zum Mulit Branch existiert das Single Branch Model. Entwickler*innen unter euch dürfte das vielleicht auch mit dem Trunk Based Development ein Begriff sein. Die Idee dahinter ist, dass wir nur noch einen Branch für sämtliche Umgebungen besitzen, und somit auf Merge Operationen verzichten können.

Die Dateistruktur sieht folgendermassen aus:

.
├── Chart.yaml
├── values-dev.yaml
├── values-staging.yaml
└── values-production.yaml

Für jede Umgebung existiert ein Values File. All diese Files sind im selben Branch. Dieses Modell steht auch in Einklang mit Helm und Kustomize. Beide Tools verwenden für die Modellierung der verschiedenen Environments verschiedene Ordner und Dateien, und keine Branches.

Wir empfehlen auch beim Single Branch Modell mit Merge/Pull Requests zu arbeiten und wenn möglich, nie direkt in einen Branch zu pushen, welcher direkt via GitOps deployed wird.

Combined Model

Als letztes Beispiel möchten wir euch noch die Kombination beider Modelle näher zeigen.

Wir haben dabei dieselbe Directory Struktur wie im Single Branch Model. Mit dem Unterschied, dass noch ein Branch pro Environment existiert, wie dies im Multi Branch Model der Fall ist. Im Gegensatz dazu aber existieren die verschiedenen Value Files in jedem Branch. Eine Änderung der Konfiguration beginnt immer im Dev Branch, unabhängig davon, in welcher Stage diese Änderung am Ende deployed wird. Änderungen in der Dev Umgebung werden im Dev Branch auf den Dev Value Files gemacht. Änderungen für Staging werden auch im Dev Branch im Staging Value Files gemacht. Anschliessend werden alle Files auf den nächsten Branch gemerged.

 
.
├── Chart.yaml
├── values-dev.yaml
├── values-staging.yaml
└── values-production.yaml

Config file structure

Eine oft gestellte Frage ist: «Wie strukturiere ich meine Konfigurationen für die verschiedenen Umgebungen und wie propagiere ich Änderungen von einer Umgebung auf die Nächste?» Die Frage erscheint im ersten Moment als ziemlich einfach, bei genauerer Betrachtung jedoch stellt sich heraus, dass dies ein nicht zu unterschätzendes Unterfangen ist.

Ohne die richtige Strukturierung der Konfigurationen endet man trotz GitOps Ansatz sehr schnell in einer Situation, die wir “Configuration Hell” nennen.

Wir möchten euch anhand dieses Beispiels Repository eine Strategie aufzeigen, wie ein bewährtes Modell der Environment Propagation mit dem Single Branch Model aussehen kann.

Das Beispiels Repository befindet sich hier.

Dazu orientieren wir uns am Blogartikel von Kostis Kapelonis (Codefresh.io). In seinem Ansatz werden die Config Files nicht einfach nur nach Environment (z.B. values-test.yaml, values-prod.yaml etc.) geteilt. Die Value Files werden nach logischer Gruppierung und Environment unterteilt.

Wir können vier verschiedene Kategorien von Konfigurationen unterscheiden:

  1. Application Version in Form eines Container Image Tags. Dies ist möglicherweise eine der wichtigsten Konfigurationen. In der Regel ändert diese Konfiguration sehr häufig und wird über die verschiedenen Stages hinweg propagiert.
  2. Kubernetes Specific Setting sind Konfigurationen wie die Anzahl Replicas. Diese Konfigurationen werden für gewöhnlich nicht propagiert.
  3. Static Business Settings sind Konfigurationen, welche nichts mit Kubernetes zu tun haben. Das können zum Beispiel externe URLs, Credentials, Localization settings etc. oder die Anbindung an ein Environment spezifisches Umsystem wie ldap-staging.puzzle.ch sein. Dies sind alles Einstellungen, welche nicht propagiert werden.
  4. Non-Static Business Settings sind wie bei Punkt 3 Konfigurationen, welche unabhängig von Kubernetes sind, aber zwischen den Stages promoted werden. Beispiele sind verfügbare TLS Ciphers, Cache Einstellungen usw.
.
├── Chart.yaml
├── common
│   └── values.yaml
├── envs
│   ├── prod-a
│   │   ├── values.yaml
│   │   └── version.yaml
│   ├── prod-b
│   │   ├── values.yaml
│   │   └── version.yaml
│   ├── staging-a
│   │   ├── values.yaml
│   │   └── version.yaml
│   └── staging-b
│       ├── values.yaml
│       └── version.yaml
├── values.yaml
└── variants
    ├── customer-a
    │   └── values.yaml
    ├── customer-b
    │   └── values.yaml
    ├── prod
    │   └── values.yaml
    └── staging
        └── values.yaml

Mit einer solchen Aufteilung der Konfigurationen wird die Promotion auf die nächste Stage um einiges einfacher. Denn diese Aufteilung erlaubt es uns in den meisten Fällen, mit einem einfachen File Copy die Änderungen zu promoten. Folgendes Beispiel verdeutlicht dies.

Promoten einer neuen Image Version von Staging nach Prod. Dazu werden folgende Befehle ausgeführt:

    • Zuerst wird ein neuer Branch erstellt: git branch prod .Dieser wird später für den Merge Reuqest verwendet
    • Anschliessend wird das Version YAML von Staging nach Prod kopiert. cp env/staging-a/version.yaml env/prod-a/version.yaml
    • Die Änderungen werden zum Schluss commited und gepusht git add env/prod-a/version.yaml && git commit -m "update version prod" && git push
    • Anschliessend wird ein Merge/Pull Request auf den main branch erstellt. Sobald dieser genehmigt und gemerget wird, wird die neue Version automatisch in der produktiven Umgebung ausgerollt.

Conclusion

Wie wir sehen gibt es verschiedene Ansätze, wie man die Git Branches und die Daten strukturieren kann. Jeder dieser Ansätze hat seine Vor- und Nachteile. Daher macht es durchaus Sinn, bevor man mit GitOps startet, sich darüber Gedanken zu machen, wie sich die ganzen Repositories am besten strukturieren lassen.

Wir von Puzzle helfen gerne dabei, passende Lösungen zu finden und diese zu implementieren. Und falls ihr mehr über ArgoCD und dessen Anwendung lernen möchtet, besucht doch die Acend Trainings Webseite, wo wir Trainings zu ArgoCD und weiteren Cloud Native Technologien anbieten.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.