1
 
 
Profil
In deinem persönlichen Profilbereich kannst du den Status deiner Bewerbung einsehen, unvollständige Bewerbungen zwischenspeichern und aktuelle News und Events einsehen
22. Januar 2025

Terraform-Tipps: Skalierbare & zuverlässige Infrastructure-as-Code – Teil 2

Worum geht es in dem Artikel?

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.

Eine klare Verzeichnisstruktur und Ressourcenorganisation

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:

  • Wie komplex ist mein Service?
  • Wie viele Ressourcen und Provider werden benötigt?
  • Wie oft wird sich die Infrastruktur ändern (mtl./wtl. /tgl./mit jedem Commit)?
  • Wie kann am besten und nachhaltigsten gruppiert werden (nach Environment, Region, oder Projekt)?

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.

Beispiel für eine Terraform-Verzeichnisstruktur mit einem modulithischen Ansatz
Beispiel für eine Terraform-Verzeichnisstruktur mit einem modulithischen Ansatz

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:

  • main.tf – ruft Module, locals und Datenquellen auf, um alle Ressourcen zu erstellen
  • backend.tf – definiert, das cloud-bucket, in dem der remote-state abgelegt ist
  • variables.tf – enthält Deklarationen der in main.tf verwendeten Variablen
  • outputs.tf – enthält Outputs der in main.tf erstellten Ressourcen
  • versions.tf – enthält Versionsanforderungen für Terraform und Provider

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.

Eingabe- und Ausgabewerte – gold in gold out

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:

  • bei der Benamung von Ressourcen lieber beschreibende Begriffe wie “private”, “public”, “database” nutzen und redundante Informationen vermeiden (bspw. nicht den ressourcen-typ im ressourcen-namen wiederholen)

  • Singular, Lowercase und keine ausgeschriebenen Zahlen (“2” anstelle von “two/second”) verwenden

  • Argumente wie “count” oder “for_each” kommen an oberster Stelle in einer Ressource oder einem Data-Source-Block, “Tags”, “depends_on” und “lifecycle” hingegen an unterster Stelle

  • bei der Verwendung von “count” oder “for_each” -> boolean-Werte anstelle von anderen Expressions nutzen

  • Underscores (_) statt Bindestriche (-) bei Ressourcen, Data-Source-, Variablen- und Output-Namen verwenden

  • Bindestriche an allen Stellen, die nach außen preisgegeben werden (DNS-Namen, Instanzen), verwenden

  • mumerische Werte wie (RAM-size or disk sizes) sollten immer mit ihren zugehörigen Units beschrieben werden (“ram_size_gb”)

Input Variablen

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.

  • Variablen in einem variables.tf-File ablegen

  • erforderliche Variablen von optionalen Variablen innerhalb eines Files trennen -> Lesbarkeit des Codes

  • Nutzung des “description”-Arguments, für eine aussagekräftige, zum Variableneinsatz passende Beschreibung (Description -> Warum gibt es diese Variable? Welcher Wert wird erwartet?)

  • Validation-Block nutzen -> Definition von Regeln und Fehlernachrichten bei Regelverstoß (lässt im Feld „conditions“ sogar REGEX zu -> Sicherstellung von Naming-Conventions/Prefixes)

  • “default” (bei optionalen Variablen)

  • “type”

  • einfache typen (string , list(...), map(...), any ) anstelle von spezifischen Typen (objects) bevorzugen

  • spezifische Typen können gut genutzt werden, wenn sie einheitliche Elemente haben (eine map, deren Elemente alle vom Typ “String” sind -> hier kann einfacher konvertiert werden) -> Hierbei “tomap” statt “map” nutzen, um die Erzeugung eines Objektes zu vermeiden

  • den Typ "any" verwenden, wenn die Typprüfung ab einer bestimmten Tiefe deaktivieren oder mehrere Typen unterstützen werden sollen

  • bei Boolean-Werten darauf achten, dass positive Namen wie “enable_external_access” genutzt werden

Output Variablen

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.

  • Outputs in einem outputs.tf – File ablegen

  • Folgende Naming-Convention verwenden: <name>_<type>_<attribute> (bspw. bei einer Ressource “aws_vpc_endpoint” “test” -> test_vpc_endpoint_id)

  • Plural im Namen verwenden, wenn der Rückgabewert eine Liste ist

  • “description” und „type“-Feld bereitstellen

  • “sensitive“-Feld vermeiden, um Sicherheit zu erhöhen und unbeabsichtigte Datenlecks zu verhindern

  • „try” anstelle von “element(concat(…))“ nutzen -> bessere Lesbarkeit, robustere Konfiguration und Fehlervermeidung

Die wichtigsten Erkenntnisse

  1. Langfristige Planung und Vision: Überprüft regelmäßig die Infrastruktur und passt sie an zukünftige Anforderungen an. Es sollte beachtet werden, wie Ressourcen miteinander verbunden sind und welche Abhängigkeiten bestehen. Die Komplexität der API-Aufrufe muss geringgehalten werden.

  2. Modulares Design und Wiederverwendbarkeit: Die Infrastruktur sollte in kleinere, wiederverwendbare Module aufgeteilt werden. Kompakte Statefiles helfen, die Wartbarkeit und Skalierbarkeit zu verbessern.

  3. Verzeichnisstruktur und Organisation: Die Etablierung von einer konsistenten und logischen Struktur für Terraform-Dateien erleichtert die Navigation und Verwaltung. Wichtige Fragen: Wie komplex ist mein Service? Wie viele Ressourcen und Provider werden benötigt? Wo lassen sich gut Schnitte machen (project, region, environment)?

  4. Eingabe- und Ausgabewerte: Es ist vorteilhaft Variablen und Outputs zu verwenden, um Konfigurationen flexibel und anpassungsfähig zu gestalten. Die Community-Best-Practices von Terraform bieten dabei wertvolle Unterstützung.

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?

24 Personen gefällt das

1Kommentar

  • Eli
    30.07.2025 05:17 Uhr

    ttt

Dein Kommentar
Antwort auf:  Direkt auf das Thema antworten

Geschrieben von

Nina Braunger
Nina Braunger
Software Entwicklerin

Ähnliche Beiträge

Gespeichert!

We want to improve out content with your feedback.

How interesting is this blogpost?

We have received your feedback.