Infrastructure as Code mit Terraform – Teil 2

Dynamisierung und Backend

Dynamisierung und Backend

Infrastructure as Code mit Terraform – Teil 2

Dynamisierung und Backend


Mit diesem Beitrag wird unsere Terraform-Serie fortgesetzt. Der erste Teil behandelte allgemeine IaC-Themen sowie die Terraform-Grundlagen. Es wurde eine Art „Hello World“-Deployment mit einem Storage-Account und fixen Werten aufgezeigt. Jetzt geht es um die diversen Optionen, wie sich der Code dynamisieren und flexibel gestalten lässt. Außerdem klären wir, was genau es mit dem Statefile und den Providern auf sich hat.

Bevor wir in die Dynamisierung einsteigen, werfen wir noch einmal einen Blick auf den typischen Aufbau eines Terraform-Ordners, beispielsweise in einem Coderepository.

Verzeichnisstruktur

Obwohl es technisch möglich ist, sind Terraform-Konfigurationen in der Regel nicht in einer einzigen Datei abgelegt, vor allem nicht im Unternehmenskontext. Prinzipiell ist der Entwickler frei in der Anzahl, Benennung und Einteilung. Es hat sich jedoch eine gewisse Praxis durchgesetzt. Häufig findet sich eine Aufteilung im in Abbildung 1 gezeigten Stil.

Abb. 1: Typische Terraform-PojektstrukturAbb. 1: Typische Terraform-Pojektstruktur

Ausgelagerte Module befinden sich in separaten Ordnern bzw. eigenen Remoterepositorys. Module sind ein eigenes großes Thema in Terraform, damit werden wir uns erst im nächsten Artikel befassen. In diesem Teil soll es nur um die Struktur des Root-Moduls gehen, das in jedem Terraform-Projekt der Einstiegspunkt ist.

In der main.tf – oder auch mit benannt nach der zu erstellenden Ressourcen, z. B. network.tf – befinden sich Ressourcenblöcke, local-Definitionen (dazu später mehr) und nötige Datenabfragen. Die var-Dateien und die Outputs beziehen sich auf die Variablenverwendung. Die nötigen Providerkonfigurationen, beispielsweise für die Version oder unterschiedliche Subscriptions, sind in eine providers.tf ausgelagert.

Variablen

Automatisierung funktioniert nur dann sinnvoll, wenn durch bestimmte Parametrisierung auch eine Dynamik in den Code gebracht werden kann. Bei Terraform geschieht das unter anderem mit Variablen, die in den drei Varianten Input Variables, Locals und Outputs auftreten können.

Input Variables

Wer aus der nativen ARM-Entwicklung kommt, würde Terraform-Inputvariablen vermutlich mit den Parametern aus den ARM-Templates vergleichen. Variablen werden in Blöcken definiert. Neben dem Namen an sich muss innerhalb des Blocks mindestens der Typ definiert werden. Zusätzlich gibt es viele optionale Eigenschaften. Es sollte eine Beschreibung gesetzt werden, ein Standardwert ist auch möglich (Abb. 2).

Abb. 2: Setzen von Beschreibung und DefaultwertAbb. 2: Setzen von Beschreibung und Defaultwert

Interessant ist noch die Eigenschaft sensitive für sensible Angaben wie z. B. Passwörter (Abb. 3). Die Werte werden dann in den diversen Konsolenausgaben maskiert. Achtung: Im Statefile werden die Werte immer im Klartext gespeichert.

Abb. 3: Werte als sensitive deklarierenAbb. 3: Werte als sensitive deklarieren

Eine Validierung der Variablen kann ebenfalls konfiguriert werden (Abb. 4). Dazu wird ein Validation-Block definiert und eine Bedingung zur Überprüfung angegeben. Damit können Zahlen auf Wertebereiche oder z. B. Zeichenketten auf ein Regex-Muster geprüft werden.

Abb. 4: VariablenvalidierungAbb. 4: Variablenvalidierung

Setzen der Variablenwerte

Hat eine Variable einen Standardwert verpasst bekommen, zählt das in der Welt von Terraform als optionale Variable. Für alle anderen Variablen muss ein Wert zugewiesen werden. Hierfür steht eine Reihe von Optionen zur Verfügung. Die Wertzuweisung erfolgt nach einer definierten Reihenfolge, wobei der letzte gefundene Wert verwendet wird. Ein Wert aus einem vorherigen Schritt wird damit überschrieben. Die Abfolge ist:

  1. Umgebungsvariablen

  2. .tfvars-Datei

  3. Verwendung von Konsolenargumenten

Bei Umgebungsvariablen muss das Präfix TF_VAR_ vor dem Variablennamen verwendet werden. Die Konfigurationsdateien werden automatisch ausgewertet, wenn bestimmte Namensmuster eingehalten werden. Entweder steht vor der Dateiendung – .tfvars oder .json – nur terraform oder zwischen einem individuellem Namen und der Endung steht .auto. Es werden bei .tfvars-Dateien Key-Value-Paare oder bei den anderen Optionen JSON-Objekte akzeptiert. auto-Dateien würden die anderen Werte überschreiben, die Reihenfolge innerhalb des Dateisystems wird berücksichtigt:

  1. terraform.tfvars.json

  2. *.auto.tfvars

Variablen können außerdem direkt mit den Terraform-Befehlen über das -var-Argument für die einzelne Eingabe oder mit einem Dateipfad und dem Zusatz -var-file übermittelt werden.

Ist keiner dieser Schritte erfolgreich, muss der Wert über die Konsole eingegeben werden, terraform plan und apply stoppen an dieser Stelle. Insbesondere bei DevOps Pipelines kann das zu Überraschungen führen, wenn die Variable vergessen wurde und die Pipeline deswegen nicht weiterläuft [1].

Locals

Locals folgen einer ähnlichen Logik wie die Variablen aus ARM-Templates. Wie bei Inputvariablen werden sie verwendet, um Wiederholungen und Hartkodierungen in der Konfiguration zu vermeiden. Sie werden aber nicht von außen in die Konfiguration hineingegeben, sondern innerhalb dieser definiert bzw. zur Laufzeit ausgewertet. Bei Locals handelt es sich um eine Verbindung zwischen einem Namen und einem Ausdruck. Sie sind vor allem dann hilfreich, wenn eine bestimmte Logik oder Funktion für die Wertermittlung verwendet werden soll.

Bei der Referenzierung der Locals in der Konfiguration, die prinzipiell wie bei Variablen funktioniert, ist etwas Vorsicht geboten: Der Zugriff erfolgt über das Schlüsselwort local, definiert wird aber immer mit locals.

Unser Storage-Account-Name ist ein typisches Beispiel für Locals (Abb. 5). Als Variableninput kommt ein Teil des Namensmusters, da der Name aber in die Endpoint URLs des Storage integriert ist, braucht es zur weltweiten Eindeutigkeit dann z. B. noch eine Zufallszahl.

Abb. 5: Storage-Account-Name als Beispiel für LocalsAbb. 5: Storage-Account-Name als Beispiel für Locals

Es können außerdem die von Terraform unterstützten Ausdrücke inklusive Funktionsaufrufe verwendet werden – beispielsweise zur Generierung einer Zufallszahl [2].

Outputs

Ein Output ist eine Art Rückgabewert, der z. B. ein Attribut einer Ressource beschreibt und in der Konsole ausgegeben werden kann. Außerdem werden Outputs verwendet, um Informationen zwischen verschiedenen Konfigurationen einer Modulstruktur zu übergeben – mehr dazu im nächsten Artikel. Für die Definition muss ein entsprechender Block definiert werden (Abb. 6).

Abb. 6: OutputdefinitionAbb. 6: Outputdefinition

Die Auflistung der Rückgabewerte erfolgt üblicherweise in der Datei outputs.tf. Erforderlich ist lediglich ein Ausdruck. Beschreibung, Maskierung des Werts und definierte Abhängigkeiten von anderen Ressourcen sind optional (Abb. 7). Outputwerte werden nur nach erfolgreichem apply-Schritt ausgegeben. In unserem Beispiel geben wir die Region und den Web-Endpoint des Storage-Account aus (Abb. 8).

Abb. 7: Beispiel einer outputs.tfAbb. 7: Beispiel einer outputs.tf
Abb. 8: endpoint und location des Storage-Accounts ausgebenAbb. 8: endpoint und location des Storage-Accounts ausgeben

Funktionen

Terraform kann mit Hilfe zahlreicher vordefinierter Funktionen bei der Dynamisierung und Verschlankung des Codes unterstützen. Es finden sich viele Helferlein, vor allem in folgenden Kategorien:

  • Zeichenketten (Substring, Regex, Upper-Lower, Formatierungen)

  • Zahlen und Listen (max.-min., Verkettungen, Länge, Suche und Sortierung)

  • Hashing und Encoding (Base64, JSON-YAML-Kodierung, MD5 und SHA-256)

  • Datum und Zeit (Formate, Manipulationen, Zeitstempel)

  • Netzwerkfunktionen (IP- und Subnetzberechnungen)

Die meisten dürften vom Prinzip her bekannt sein, für die Details sei auf die Dokumentation verwiesen. Individuelle Funktionen können leider nicht erstellt werden [3].

Aus der eigenen...