Terraform-Tipps: Skalierbare & zuverlässige Infrastructure-as-Code – Teil 2
Dies ist der zweite Teil der Terraform-Reihe mit einigen Tipps und Best Practices für eine effiziente Cloud-Infrastruktur. In der OTTO IT wird das leistungsstarke Open-Source-Tool von Hashicorp eingesetzt um via Infrastructure-as-Code (IaC) die Cloud-Infrastruktur effektiv zu orchestrieren, zu automatisieren und zu verwalten. Nachdem es im ersten Teil um die Hauptphasen des Terraform-Workflows und den Terraform Lifecycle sowie Terraform Module ging, werden in diesem Teil spezifische Kniffe zur Optimierung der Verzeichnisstruktur und Ressourcenorganisation sowie zur Handhabung von Ein- und Ausgabewerten vorgestellt.
Es ist entscheidend, bei der Planung der Infrastrukturbedürfnisse vorausschauend zu agieren und eine langfristige Vision für die Skalierung des Services zu haben. Dabei sollten sowohl die individuellen Anwendungsfälle als auch potenzielle Einschränkungen und Herausforderungen von Terraform berücksichtigt werden, um eine skalierfähige und wartbare Infrastrukturverwaltung sicherzustellen.
Wichtige Fragen, die diesbezüglich gestellt werden sollten, sind:
Trotzdem ist Vorsicht geboten, denn der Versuch, den Code frühzeitig zukunftssicher zu machen und zu “overengineeren”, kann auch zu einem unübersichtlichen und schwerverständlichen Code führen. Dieses Prinzip gilt für die Entwicklung von einem üblichen Code ebenso wie für Infrastructure-as-Code. Es ist wichtig, die Terraform-Struktur einfach und nachvollziehbar zu gestalten. Gleichzeitig sollte eine flexible Basis geschaffen werden, um zukünftige Änderungen anpassen zu können.
Wie in Teil 1 der Terraform-Reihe beschrieben, werden während der Phasen “terraform plan” und “terraform apply” Cloud API-Aufrufe für die Ressourcen getätigt. Anstatt die gesamte Infrastructure-as-Code in einem einzelnen großen “monolithischen” Statefile zu definieren, sollte die Komplexität lieber geringgehalten und möglichst überschaubare Zustandsdateien implementieren werden. Die Statefiles sollten klein und kompakt gehalten sein, um das Abhängigkeitsmanagement zu erleichtern, das Deployment zu beschleunigen. Ein weiterer Vorteil von kleinen Statefiles ist, dass dadurch das Testen und Reviewen einfacher ist.
Da sich Terraform-Konfigurationen im Laufe der Zeit weiterentwickeln, sollten frühzeitig entsprechende Strukturierungsmaßnahmen ergriffen werden, um Veränderungen flexibel vornehmen zu können und dadurch langfristig die Wartbarkeit und die Skalierfähigkeit der Ressourcen zu erhalten. Dazu folgt ein Beispiel zu einer Terraform Struktur mit einem modulithischen Ansatz, in welcher die main-branch für alle Umgebungen die „source of truth“ ist; es ist jedoch wichtig zu beachten, dass dies nur ein Beispiel ist und viele andere Ansätze und Lösungen ebenfalls möglich sind.

In dem dargestellten Beispiel bedienen sich die Konfigurationsdateien einem gemeinschaftlichen Directory, in welchem die wiederverwendbaren Bausteine als sogenannte „Modules“ definiert sind. Dieses Directory könnte auch als eigenständiges Repository implementiert und somit von unterschiedlichen Applikationen und Teams genutzt und wiederverwendet werden.
Ebenfalls gibt es in dem aufgeführten Beispiel ein Infrastructure-Directory, welches für das Bootstrapping (bspw. für Desaster Recovery) sowie die übergreifende Cloud-Umgebung, die nicht umgebungsspezifisch ist (bspw. Logging, Secret Manager), dient.
Die Organisation des Terraform-Codes im Beispiel ist nach Umgebungen aufgeteilt. Auf den ersten Blick scheint der Code z.T. redundant. Dieses Setup birgt allerdings viele Vorteile, denn es sorgt für eine lose Kopplung. Die Ressourcen können unabhängig voneinander gemanagt und feingranularer, umgebungsspezifischer konfiguriert werden. Wenn beispielsweise der Service gelöscht werden soll, die Datenbankinstanz aber nicht, kann dies durch separierte Statefiles und die Ordnerstruktur simpel vorgenommen werden. Auch wenn der Live-Umgebung mehr Processing Units zugewiesen werden sollen als der Dev-Umgebung, ist dies einfach zu händeln. Neben der Aufteilung in Entwicklungsumgebungen, kann es auch sinnvoll sein, diese Komponenten nach Infrastruktur-Ebenen zu gruppieren (wie bspw. Networking, Database, ...).
Hier ein kurzer Blick auf die im Beispiel verwendeten Terraform-Files und deren Inhalte:
Ein zusätzlicher Tipp ist die Nutzung von „terraform graph“ und Tools zur Visualisierung der aktuellen Terraform-Konfigurationen, um schnell Verbesserungspotentiale im Setup erkennen zu können.
Zusammengefasst sollte Infrastructure-as-Code mit der gleichen Strenge wie Anwendungscode geschrieben und gewartet werden. Dabei sollten Best-Practices und Standards berücksichtigt werden. Denn eine klare Verzeichnisstruktur führt zu einer verbesserten Qualität, Wartbarkeit und Zusammenarbeit im Entwicklungsteam.
Um ein langfristig wartbares Setup zu erhalten, sollte auch bei den Ein- und Ausgabewerte auf Standardisierung, Konsistenz und Wiederverwendbarkeit geachtet werden. Hier gibt es wieder viele nützliche Hinweise und Best-Practices aus der Terraform-Community und auch die Cloud-Provider folgen bestimmten Konventionen, die uns automatisch in die richtige Richtung leiten:
Input-Variablen in Terraform sind ein mächtiges Werkzeug, um Konfigurationen flexibel und wiederverwendbar zu gestalten. Da sie aber auch zu einer höheren Komplexität im Code führen können, sollten sie nur eingesetzt werden, wenn sie wirklich notwendig sind. Hilfreich ist es, vor der Implementierung die Frage zu stellen, ob sich der Wert der potenziellen Variable wirklich verändern lassen sollte. Zusätzlich sollte darüber nachgedacht werden, ob es einen konkreten Usecase gibt oder der Einsatz von Local Values eventuell der sinnvollere Weg ist.
Der Einsatz von Output-Values kann viele Vorteile bieten. Child-Module können Outputs nutzen, um ein Subset der Ressourcen zum Parent-Modul hochzureichen. Das Root-Module kann Outputs nutzen, um Werte bspw. über das CLI auszugeben. Zum Verständnis: Das Root- oder auch Parent-Modul ist das Modul, welches andere Module (Child-Module) aufruft, um deren Ressourcen in die Infrastruktur- Konfiguration mit aufzunehmen.
Zusätzlich können Outputs nach dem Deployment auch von anderen Infrastruktur-Konfigurationen genutzt werden (im Falle eines Remote-States). Um dem Nutzer eines Moduls die Verwendung zu erleichtern, ist es hilfreich, die Outputs so zu gestalten, dass direkt ersichtlich ist, welche Wert-Typen und Attribute sie bereitstellen.
Abschließend ist es entscheidend, die spezifischen Anforderungen und Rahmenbedingungen des jeweiligen Projekts zu berücksichtigen. Es kann zudem sinnvoll sein, von den Empfehlungen abzuweichen und individuelle Lösungen zu entwickeln.
"Die Kunst liegt darin, die Vor- und Nachteile verschiedener Ansätze abzuwägen und die beste Lösung für die jeweilige Situation zu finden. Denn wie so oft in der IT gibt, es nicht den einen richtigen Weg, sondern viele verschiedene Möglichkeiten."
Der nächste Teil derTerraform-Reihe behandelt das Testen von Infrastructure-as-Code. Stay tuned! ;-)
Möchtest du Teil des Teams werden?
ttt

We have received your feedback.