Infrastructure as Code (IaC) - Terraform-Konfiguration DRY halten mit Terragrunt
Terraform ist ein Infrastruktur-als-Code-Tool zur Bereitstellung von Cloud- oder On-Premise-Ressourcen unter Verwendung von Konfigurationsdateien, die leicht lesbar und wiederverwendbar sind. Unser Team verwendet Terraform, um GCP-Cloud-Ressourcen mit teilweise unterschiedlicher Konfiguration in verschiedenen Umgebungen (z.B. Produktion oder Testing) zu verwalten. Die Organisation von Terraform-Code kann jedoch mühsam werden, denn mit zunehmender Anzahl von Infrastrukturkomponenten und Umgebungen neigen die Konfigurationsdateien dazu, sich zu wiederholen. Unterschiede zwischen den Umgebungen sind daher möglicherweise schwieriger zu erkennen.
Das Ziel ist also, die Wiederholung von Code zu minimieren oder ganz zu eliminieren, indem die Terraform-Konfiguration DRY (Don't Repeat Yourself) gehalten wird. Idealerweise sollten Ressourcen und allgemeine Terraform-Deklarationen wie die Provider- und Remote-State-Konfiguration nur einmal geschrieben, parametrisiert und dann für mehrere Umgebungen wiederverwendet werden, während gleichzeitig der gesamte Code leicht lesbar und intuitiv bleibt.
Wir sind daher zu Terragrunt gewechselt, eine kleine Wrapper-Applikation um Terraform, die weniger Wiederholungen, eine bessere geteilte Konfiguration und eine Erleichterung der Arbeit mit Terraform-Modulen verspricht. Es kann eine praktikable Lösung sein, um Konfigurationsdateien besser zu organisieren und Unterschiede zwischen Umgebungen deutlich zu machen.
Abbildung 1: Grundlegende Terraform-Dateistruktur ohne jegliche Optimierungen
Allein und ohne weitere Optimierungen offenbarte dieser Ansatz mehrere Designmängel.
Terraform bietet einige Mechanismen, um diese Probleme zu entschärfen. Workspaces erlauben es, eine Reihe von Ressourcen für mehrere Umgebungen nur einmal zu definieren und dann den Kontext für die Einsatzumgebung während der Laufzeit zu wechseln. Module wiederum kombinieren verwandte Ressourcen in einem parametrisierten wiederverwendbaren Blueprint, der lokal oder in einem entfernten Repository gespeichert werden kann.
Die Wahl einer oder einer Kombination aus beiden Methoden kann einem oder mehreren der oben genannten Probleme entgegenwirken. Module verringern definitiv die Wiederholung von Code und ermöglichen den parametrisierten Aufruf häufig verwendeter Ressourcen. Aber die Aufteilung von Ressourcen in eine logisch verknüpfte Gruppe, die jeden möglichen Anwendungsfall abdeckt, kann schwierig und manchmal unverständlich sein. Und die einfache Gruppierung von Ressourcen in Modulen reicht nicht aus, wenn am Ende alle diese Module innerhalb eines einzigen Terraform-State für jede Umgebung verwendet werden. Workspaces ermöglichen einen einfachen Wechsel des Umgebungskontextes für dieselbe Gruppe von Ressourcen, setzen aber voraus, dass der richtige Workspace mit der richtigen *.tfvars-Datei für die aktuelle Ausführungsumgebung verwendet wird, was bei manuellen Eingriffen gefährlich sein kann.
Stattdessen haben wir uns für Terragrunt entschieden, einen kleinen Wrapper für Terraform, der ein besseres dynamisches Remote-State-Handling und insgesamt eine DRY-Konfiguration von Terraform bietet. Terragrunt fördert einen modularen Ansatz zur Strukturierung des Terraform-Codes und verwendet seine eigenen Konfigurationsdateien (*.hcl), um den resultierenden Code so DRY wie möglich zu halten.
Abbildung 2: Terraform-Module
Abbildung 3: Terragrunt-Umgebungen
Jede terragrunt.hcl enthält nun die Konfiguration für das jeweilige Terraform-Modul, einschließlich der Provider- und Backend-Konfiguration sowie der Modulparameter, d.h. Sizing oder zonale Einstellungen für VMs. Neue Funktionen können entweder zu einem bestehenden Modul hinzugefügt werden, ohne die Terragrunt-Konfiguration zu ändern, oder man fügt eine neue Modulquelle und eine entsprechende terragrunt.hcl zu jeder betroffenen Umgebung hinzu.
Änderungen innerhalb der terragrunt.hcl (oder des entsprechenden Terraform-Moduls) können über das Terragrunt CLI für ein einzelnes Modul, mehrere Module innerhalb einer Umgebung oder mehrere Umgebungen/Projekte auf einmal vorgenommen werden. Unsere Deployment-Pipeline verteilt alle Modul-Unterordner mit ihren jeweiligen Ressourcen für jede Umgebung.
Für jedes Modul, das in einer Umgebung verwendet wird, wird ein separater Terraform-Remote-State beibehalten, um zu verhindern, dass der State für jedes Modul aufgebläht wird, und um die Verwaltung von Abhängigkeiten zwischen den Modulen beizubehalten. Z.B. wird das Netzwerkmodul zuerst ausgerollt und dessen Ergebnis als Eingabeparameter für das DNS-Modul verwendet (Abbildung 4).
Abbildung 4: Terragrunt HCL-Datei mit Konfiguration, Parametern und Terraform-Funktionalität
Nach diesem Schritt können wir nicht einfach eine bestehende Datei in eine neue Umgebung oder ein neues Projekt kopieren, ohne Anpassungen vorzunehmen, da jede terragrunt.hcl die Konfigurationseinstellungen für ihre jeweilige Umgebung beibehält. Die unterschiedlichen Konfigurationen sind immer noch über mehrere Unterverzeichnisse verstreut.
Terragrunt erlaubt eine umfangreiche Verwendung von Variablen innerhalb seiner eigenen Konfigurationsdateien, sogar für die Deklaration von Remote-Zuständen (im Gegensatz zu Terraform). Wir haben daher die verbleibenden Codewiederholungen eliminiert, indem wir zusätzliche *.hcl-Konfigurationsdateien verwendeten, die ähnliche Eigenschaften gruppierten. Das Ergebnis waren terragrunt.hcl-Dateien, die auf ihre eigentliche Funktionalität reduziert und vollständig von der Deklaration der von ihnen verwendeten Eigenschaften befreit waren (Abbildung 5).
Abbildung 5: Terragrunt HCL-Datei, nur mit Modulfunktionalität
Jede *.hcl-Datei befindet sich auf der Ebene über denjenigen Ordnern, auf die sich ihre jeweilige Konfiguration bezieht. Alle modul- und projektbezogenen Eigenschaften für Umgebungen werden in einer env.hcl-Datei auf der Umgebungsebene abgelegt, während gemeinsame, von einzelnen Umgebungen unabhängige Eigenschaften in einer common.hcl-Datei auf der Stammebene abgelegt werden. Zu den Umgebungseigenschaften gehören zum Beispiel die oben erwähnten Größenparameter für VMs sowie spezifische Bucket- oder Netzwerknamen (Abbildung 6). Zu den gemeinsamen Eigenschaften auf der Stammebene gehören globale Konfigurationsparameter, d. h. Team-Labels, die für jede unserer Ressourcen vergeben werden.
Abbildung 6: Terragrunt HCL-Datei mit Umgebungseigenschaften
Darüber hinaus wird eine einzige Backend- und Provider-Konfiguration in einer terragrunt.hcl-Datei auf der Root-Ebene deklariert, die dann von allen Untermodulen in jeder Umgebung verwendet wird. Da Terragrunt die Verwendung von Variablen in diesen Deklarationen erlaubt, enthält der eigentliche Code Platzhalter, die während der Laufzeit basierend auf dem Pfad jedes Moduls gefüllt werden (Abbildung 7).
Abbildung 7: Terragrunt HCL-Datei mit dynamischer Provider- und Backend-Konfiguration
Um eine deklarierte Variable zu füllen, durchläuft Terragrunt die Verzeichnisse ausgehend von dem Modulverzeichnis, aus dem der CLI-Befehl ausgeführt wurde, aufwärts, inkludiert alle *.hcl-Dateien auf dem Weg, bis es eine terragrunt.hcl-Datei auf der Stammebene findet. Der Wrapper erzeugt dann die statischen Terraform-Dateien und führt den entsprechenden Terraform-Befehl (plan, apply, destroy) aus.
Die endgültige Terragrunt-Struktur bietet eine zentrale Deklaration gemeinsamer Eigenschaften, Backend- und Provider-Konfiguration, die für jedes Untermodul und jede Umgebung ohne zusätzliche Codezeilen funktioniert (Abbildung 8). Alle umgebungsspezifischen Eigenschaften und Konfigurationen sind in jeder env.hcl gebündelt und können leicht von anderen Umgebungen getrennt werden. Daher müssen Terragrunt-Befehle, die entweder von unseren Entwicklungsmaschinen oder durch automatisierte Pipelines ausgeführt werden, keine Variablendateien einschließen oder explizit eine Eigenschaft setzen (obwohl dies möglich wäre).
Abbildung 8: Endgültige Terragrunt-Ordnerstruktur
Außerdem können Module einfach zu Umgebungen hinzugefügt werden, indem man die entsprechende terragrunt.hcl aus einer anderen Umgebung kopiert. Zum Entfernen eines Moduls entfernt man einfach die passende terragrunt.hcl aus der jeweiligen Umgebung (nachdem terragrunt destroy ausgeführt wurde).
Terragrunt ist eine Lösung, um die Terraform-Konfiguration DRY zu halten, indem es alle Wiederholungen, die innerhalb umfangreicher Konfigurationen in einem Multi-Environment-Setup auftreten können, eliminiert. Es ist weniger fehleranfällig als der Versuch, verschiedene *.tfvars-Dateien korrekt zu handhaben oder Workspaces im laufenden Betrieb zu wechseln. Die Migration von Terraform-Ressourcen zu Terragrunt ist mit wenigen Schritten zu bewerkstelligen. Für uns hat Terragrunt die Verwaltung unserer Cloud-Ressourcen verbessert und für mehr Intuition und Stabilität beim Ändern, Erweitern und Warten unserer Infrastruktur gesorgt.
Wenn du eine Frage an mich oder das Team hast, melde dich gerne in den Kommentaren unter diesem Artikel. Ich melde mich dann schnellstmöglich bei dir zurück.
Möchtest du Teil des Teams werden?
We have received your feedback.