Start / Seminare / Terraform und OpenTofu in der Praxis
Modul
Wiederverwendbare Module entwickeln
Modul 5 von 14 aus dem Seminar Terraform und OpenTofu in der Praxis
5 Kapitel in diesem Modul-Video · Laufzeit
Für Teams
Die Videos zeigen, wie es geht. Die Schulung sorgt dafür, dass Ihr Team es danach tut.
Ein Video kann niemanden fragen, warum es ausgerechnet in Ihrem Repository nicht funktioniert. Es kann kein Team auf eine gemeinsame Konvention bringen. Und es setzt sich niemand freiwillig drei Tage hin. In der Schulung sind am Ende alle auf demselben Stand, gearbeitet wurde am eigenen Code, und die offenen Fragen sind entschieden — in ein paar Tagen statt irgendwann nebenbei.
Vorgespräch am Telefon · Programm nach Maß · ab 1 Tag (8 Unterrichtseinheiten) · Teilnahmezertifikat
Transkript
Der gesprochene Text dieses Moduls zum Mitlesen, Überfliegen und Durchsuchen. Ein Klick auf einen Zeitstempel springt an die Stelle im Video.
Wiederverwendbare Module entwickeln
0:00 Bisher ist unsere Konfiguration für Saatplan gewachsen wie ein Gartenbeet ohne Plan: Netz, Datenbank, API und Weboberfläche stehen nebeneinander in einem Verzeichnis. Das funktioniert, solange es genau eine Umgebung gibt. Sobald die Gärtnerei Lindenhof eine Testumgebung neben der Produktion haben möchte, wird kopiert — und Kopien laufen auseinander. Module sind die Antwort darauf.
0:22 In diesem Modul geht es darum, wie man sie so schneidet, dass sie wirklich wiederverwendbar sind: mit sauberer Schnittstelle, ohne versteckte Provider-Konfiguration und mit einer Versionsbindung, die Überraschungen verhindert. Am Ende lösen wir den Anwendungsteil von Saatplan als eigenes Modul heraus und rufen ihn für zwei Umgebungen auf.
Wiederverwendbare Module entwickeln
0:42 Wir bleiben am zweiten Tag und wechseln die Blickrichtung: weg von der einzelnen Ressource, hin zur Struktur. Der Leitsatz auf dieser Folie ist der Maßstab für alles Weitere — ein gutes Modul beschreibt einen Begriff aus Ihrer Architektur, nicht eine Ressource unter neuem Namen. Wir klären zuerst, was Root und Child Modules sind, gestalten dann Schnittstellen, reichen Provider weiter und kümmern uns um Versionen. Zum Schluss wird geübt.
Root Modules und Child Modules
1:07 Die gute Nachricht vorweg: Sie haben längst Module geschrieben, ohne es zu merken. Jede Konfiguration ist bereits eines. Die eigentliche Frage ist, wo man die Grenzen zieht — und wer wen aufruft. Denken Sie an einen Gärtner, der ein Beet anlegt. Er kauft keine einzelnen Samenkörner, Erdsäcke und Etiketten, sondern ein fertiges Kräuterpaket mit klarer Anleitung.
1:31 Genau so verhält sich ein Child Module zum Root Module. Das Root Module ist das Verzeichnis, in dem Sie terraform plan aufrufen. Es bestellt über einen module-Block ein Child Module: ein eigenes Verzeichnis, das Variablen entgegennimmt, im Inneren Ressourcen anlegt und Outputs zurückgibt. Der entscheidende Punkt steht im letzten Satz der Folie. Ein Modul, das nur einen einzelnen Container dünn umhüllt, bringt keinen Gewinn — es verschiebt bloß die Arbeit.
1:58 Lohnend wird es erst, wenn es einen neuen Begriff einführt, etwa „die Saatplan-Anwendung“. Hier stehen sich zwei Denkweisen gegenüber. Links die flache Komposition: Das Root Module ist der Ort, an dem die Bausteine zusammengesteckt werden, und jeder Baustein bekommt seine Abhängigkeiten von außen gereicht. Rechts der tiefe Baum, in dem Module wieder Module aufrufen und sich alles selbst besorgen. Der rechte Weg wirkt zunächst bequem, weil ein einziger Aufruf alles erledigt.
2:27 Doch spätestens beim zweiten Anwendungsfall entsteht das gefürchtete Universalmodul mit unzähligen Schaltern. Und wer verstehen will, was passiert, muss drei Ebenen tief graben. Die linke Seite ist langweiliger — und genau deshalb im Alltag die bessere Wahl. So sieht Komposition in der Praxis aus. Das Netz saatplan-netz entsteht oben im Root Module, also dort, wo der Überblick liegt.
2:51 Das Modul für die Anwendung legt es nicht selbst an, sondern bekommt nur den Namen hereingereicht, zusammen mit dem gewünschten Image für die API. Die Doku nennt dieses Muster Dependency Inversion: Das Modul sagt, was es braucht, und der Aufrufer liefert es. Der praktische Vorteil ist schnell erklärt. Möchte Lindenhof später ein zweites Netz oder ein bestehendes nutzen, ändert sich nur der Aufrufer — das Modul bleibt unangetastet.
3:17 Behalten Sie dieses Bild im Kopf, denn in der Übung bauen wir genau diese Struktur.
Schnittstellen gestalten
3:22 Ein Modul ist nur so gut wie seine Schnittstelle. Sie entscheidet, wie angenehm es sich aufrufen lässt und wie viel man später noch ändern darf, ohne Aufrufer zu verärgern. Klein, typisiert, beschrieben — das ist die Richtung. Diese Variable zeigt, wie eine Schnittstelle schlank bleibt, obwohl sie flexibel ist. Die Ports für API und Weboberfläche sind als Objekt typisiert, und jedes Feld ist optional mit einem sinnvollen Standardwert.
3:48 Für den Aufrufer heißt das: Er muss gar nichts angeben, und wenn doch, dann nur das, was vom Üblichen abweicht. Pflicht sind nur das Image und der Netzname, also die Dinge, ohne die das Modul nicht sinnvoll arbeiten kann. Achten Sie auch auf die Beschreibung. Sie wirkt wie eine Nebensache, ist aber das Erste, was jemand liest, der Ihr Modul in einem halben Jahr verwendet.
4:10 Ein Typ fängt falsche Werte früh ab, eine Beschreibung verhindert falsche Erwartungen. Bei den Ausgängen gilt das Gegenteil von Großzügigkeit. Jeder Output ist ein Versprechen an alle, die das Modul aufrufen: Dieser Wert wird da sein, und er wird dasselbe bedeuten. Hier gibt das Modul genau zwei Dinge heraus — den Containernamen der API, über den andere Container sie im Netz erreichen, und den externen Port der Weboberfläche.
4:36 Alles andere bleibt drinnen. Das wirkt knauserig, ist aber eine Freiheit. Was nicht hinausgeht, dürfen Sie später umbauen, ohne dass irgendwo eine Konfiguration bricht. Ein Modul mit wenigen, gut beschriebenen Outputs altert deutlich würdevoller als eines, das alles preisgibt. Diese Tabelle erweitert den Begriff Schnittstelle über den Code hinaus. Zur Schnittstelle gehört alles, was ein Aufrufer wissen muss, ohne in die Ressourcen hineinzuschauen.
5:04 Das README erklärt Zweck und Annahmen, die feste Dateistruktur sorgt dafür, dass sich jeder sofort zurechtfindet, und die Beispiele zeigen den Aufruf so, wie er von außen tatsächlich aussieht. Besonders interessant ist die Markierung deprecated: Damit warnen Sie Aufrufer bei Variablen und Outputs, die demnächst entfallen, statt sie plötzlich zu entfernen.
5:25 Laut Fußzeile beherrscht Terraform das ab Version 1.15, OpenTofu ebenso. Das Changelog ist dagegen reine Konvention — aber eine, die bei jedem Versionssprung Ärger erspart. Alle vier Fallen haben eine gemeinsame Wurzel: Die Schnittstelle wird nicht als Vertrag ernst genommen. Der erste Fehler entsteht aus Hilfsbereitschaft — man reicht jedes Ressourcenargument als Variable durch, damit niemand eingeschränkt ist.
5:50 Am Ende verbirgt das Modul nichts mehr und ist nur noch eine Umleitung. Der zweite ist das Spiegelbild: Wer ganze Ressourcenobjekte als Output herausgibt, macht interne Details zum Versprechen. Variablen ohne Typ sind tückisch, weil der Fehler erst tief im Plan auftaucht, weit weg von seiner Ursache. Und das stille Umbenennen einer Variable trifft jeden Aufrufer unvorbereitet. Der Weg über deprecated kostet eine Version Geduld und erspart viel Ärger.
Provider an Module übergeben
6:17 Jetzt wird es grundsätzlich. Wo gehört eigentlich die Provider-Konfiguration hin, wenn Code auf mehrere Module verteilt ist? Die Antwort ist eindeutig, und sie hat handfeste Gründe. Stellen Sie sich den Provider wie den Lieferdienst vor, mit dem die Gärtnerei arbeitet. Welcher Dienst, welche Adresse, welcher Zugang — das entscheidet die Zentrale, also das Root Module.
6:40 Ein Child Module erbt diese Standardkonfiguration automatisch und muss sich darum nicht kümmern. Komplizierter wird es, sobald es mehrere Konfigurationen desselben Providers gibt, die über Aliase unterschieden werden. Diese Aliase vererben sich nie von selbst. Der Aufrufer muss sie ausdrücklich übergeben, und zwar über das Argument providers im module-Block.
7:01 Das klingt nach Mehraufwand, ist aber ein Gewinn: Man sieht beim Aufruf sofort, gegen welchen Docker-Host ein Modul arbeitet. Im Modul selbst steht also keine Provider-Konfiguration, sondern nur eine Anforderung: Ich brauche den Docker-Provider von kreuzwerker, und zwar mindestens Version 4.6. Zwei Details lohnen den genaueren Blick. Erstens die Quellangabe.
7:23 Fehlt sie, nimmt das Werkzeug stillschweigend einen Provider aus dem Namensraum hashicorp an — und der ist hier nicht gemeint. Zweitens die Versionsangabe. Ein wiederverwendbares Modul setzt nur eine Untergrenze, keine feste Version. Würde jedes Modul seine eigene exakte Version festnageln, könnten zwei Module gemeinsam gar nicht mehr aufgerufen werden.
7:45 Die genaue Auswahl trifft das Root Module, festgehalten im Lock File. Hier sehen Sie die andere Seite. Im Root Module entsteht eine zweite Konfiguration des Docker-Providers mit dem Alias test, die per SSH auf den Testserver der Gärtnerei zeigt. Beim Aufruf des Moduls wird diese Konfiguration über providers ausdrücklich zugeordnet: Innerhalb des Moduls heißt der Provider einfach docker, von außen kommt aber die Testvariante hinein.
8:12 Das ist elegant, weil das Modul nichts von Umgebungen weiß. Es baut die Saatplan-Anwendung — wohin, entscheidet der Aufrufer. Für die Produktion genügt ein zweiter Alias und ein zweiter Aufruf desselben Moduls. Genau dieses Muster setzen Sie gleich in der Übung um. Warum diese strenge Regel? Der wichtigste Grund ist zeitlich.
8:32 Ein Provider muss länger leben als die Ressourcen, die er verwaltet — denn um einen Container zu löschen, braucht es den Provider noch. Steckt der Provider aber im Modul und Sie entfernen das Modul, verschwinden Ressourcen und Provider gleichzeitig, und niemand kann mehr aufräumen. Hinzu kommt eine praktische Einschränkung: Ein Modul mit eigener Provider-Konfiguration verträgt weder count noch for_each noch depends_on.
8:56 Und wer später in die Cloud wechselt, profitiert erst recht, weil dort der Provider Account und Region trägt. Die Regel hält Module umgebungsneutral — überall dasselbe Prinzip.
Versionieren und veröffentlichen
9:07 Sobald ein Modul von mehreren Konfigurationen genutzt wird, wird es zur Abhängigkeit. Und eine Abhängigkeit ohne Versionsbindung ist eine, die sich still verändert. Darum geht es jetzt. Die Tabelle zeigt, aus welchen Quellen ein Modul stammen kann, und die eigentliche Lehre steckt in der rechten Spalte. Ein lokaler Pfad hat keine eigene Version; das Modul lebt im selben Repository wie sein Aufrufer und ändert sich mit ihm.
9:33 Bei Git-Quellen, auch bei einem Unterordner in einem größeren Repository, legt man die Version über einen Tag fest. Nur bei einer Registry gibt es das eigene Argument version. Das wird gern verwechselt: Wer version an einen Git-Pfad schreibt, erreicht damit nichts. Die OCI-Registry in der letzten Zeile ist eine Besonderheit von OpenTofu und zeigt, dass beide Werkzeuge hier nicht mehr ganz deckungsgleich sind.
9:58 Module und Provider werden beide versioniert, aber auf ganz unterschiedliche Weise — und genau das sorgt oft für Verwirrung. Provider landen samt Prüfsummen im Lock File; ein Upgrade ist ein bewusster Schritt mit terraform init und der Option upgrade. Module dagegen tauchen im Lock File gar nicht auf. Ihre Version steht ausschließlich im module-Block oder in der Git-Referenz.
10:19 Daraus folgt die Faustregel der Folie: Fremde Module pinnen Sie exakt, weil sonst nichts eine bestimmte Version festhält. Ihre eigenen wiederverwendbaren Module fordern Provider dagegen nur mit einer Untergrenze an. Zwei Regeln, die auf den ersten Blick widersprüchlich wirken, aber beide demselben Ziel dienen: Vorhersehbarkeit.
10:39 Ein Modul aus dem Internet ist bequem, aber es ist fremder Code, der Infrastruktur verändert. Deshalb lohnt der Blick vorab. Lizenz, Pflegestand und offene Issues verraten, ob jemand das Modul noch betreut — ein verwaistes Modul wird schnell zu Ihrer eigenen Baustelle. Den Code selbst zu lesen, ist kein Misstrauen, sondern Sorgfalt: Welche Ressourcen und Defaults bringt es mit?
11:01 Und weil Module nicht im Lock File stehen, gehört eine exakte Version dazu, sonst holt init beim nächsten Mal einfach die neueste passende. Wer selbst veröffentlichen will, findet auf der Folie die Namenskonvention für Repositories und Tags, die eine Registry erwartet. Die vier Fallen dieses Kapitels haben alle mit Zeit zu tun: Heute funktioniert alles, morgen nicht mehr.
11:24 Ein Branch statt eines Tags ist der Klassiker — derselbe Aufruf liefert morgen anderen Code, ohne dass sich in Ihrem Repository etwas geändert hat. Variablen in source oder version wirken verlockend, brauchen in Terraform ab Version 1.15 aber eine ausdrückliche Kennzeichnung als konstant, während OpenTofu das meist selbst ableitet.
11:43 Bei den Registries unterscheiden sich die Namensräume der beiden Werkzeuge, was beim Wechsel auffällt. Und der ärgerlichste Fall liegt beim Herausgeber: ein Bruch ohne neue Hauptversion und ohne Hinweis. Dagegen hilft nur, selbst diszipliniert zu versionieren.
Übung
11:58 Genug Theorie. Jetzt lösen wir den Anwendungsteil von Saatplan als eigenes Modul heraus — mit allem, was wir in diesem Modul besprochen haben: schmale Schnittstelle, Provider von außen, Komposition im Root Module. Die eigentliche Fähigkeit, um die es hier geht, ist das Ziehen einer Grenze. Was gehört fachlich zur Anwendung, was zur Basis?
12:19 Die Antwort gibt der Hinweis bereits vor: Die Daten der Datenbank bleiben im Root Module, das Modul bekommt nur das Netz herein. Daran üben Sie, eine Schnittstelle nach Verantwortung zu schneiden statt nach Bequemlichkeit. Erfolgreich sind Sie, wenn Test und Produktion dasselbe Modul mit je eigenem Provider-Alias aufrufen.
12:38 Und das zweite Kriterium ist das anspruchsvollere: Der Plan darf nach dem Umbau keinen Container ersetzen wollen. Ein Umbau, der die laufende Anwendung neu startet, wäre kein Refactoring, sondern eine Störung. Die Schritte folgen der Logik des Moduls. Zuerst ziehen Sie die Container um und geben dem neuen Verzeichnis nur eine Anforderung an den Provider, keine Konfiguration.
13:01 Dann gestalten Sie die Eingänge mit Typ und Beschreibung, danach die Ausgänge — bewusst sparsam, ergänzt um ein README mit Beispiel. Erst jetzt kommen die beiden Umgebungen ins Spiel: zwei Provider-Aliase, zwei Aufrufe. Der letzte Schritt ist der, an dem sich Erfolg oder Ärger entscheidet. Mit einem moved-Block teilen Sie dem Werkzeug mit, dass die Container nur umgezogen sind. Vertieft wird das in Modul 9.
13:26 Im nächsten Modul geht es um den State — das Gedächtnis hinter all diesen Adressen.
Dieses Modul als Schulung für Ihr Team
Das Video zeigt den Stoff. In der Schulung arbeitet Ihr Team damit — an Ihrem eigenen Code, mit Übungen und mit den Fragen, die ein Video nicht beantwortet. Sie wählen die Module aus Terraform und OpenTofu in der Praxis, wir bauen daraus ein Programm.
5 Tage·ab 900 EUR netto pro Tag (bis 3 Teilnehmende) ·Termin nach Vereinbarung