← Alle Artikel

Terraform-Module, die mit Ihrem Team skalieren, nicht gegen es

Ein Terraform-Modul ist die Art, wie ein Team aufhört, Infrastruktur zu kopieren und einzufügen. Aber ein Modul, das nicht versioniert, reviewt und getestet wird, skaliert nicht das Team — es skaliert den Schadensradius. Eine unachtsame Änderung an einem gemeinsam genutzten 'vpc'-Modul, und jede Umgebung, die es referenziert, erbt den Bug beim nächsten apply. Die Lösung: Module nicht mehr als geteilte Ordner behandeln, sondern als versionierte Produkte.

Das ist die praktische Fassung: was ein Modul zuverlässig macht, wie man es über eine richtige Pipeline veröffentlicht, und wie Teams es konsumieren — lokal und in CI — direkt aus GitHub, gepinnt an einen Tag oder, wenn die Sicherheit es verlangt, an einen unveränderlichen Commit-Hash. Ohne privates Registry.

Was ein Modul zuverlässig macht

Zuverlässigkeit beginnt vor jeder Pipeline. Ein Modul, dem andere Teams vertrauen, ist klein und tut eine Sache — ein Netzwerk, eine Datenbank, einen Service — keine 'Plattform', die alles macht. Es legt einen klaren, dokumentierten Satz von Inputs und Outputs offen, bringt vernünftige Defaults mit und pinnt seine eigenen Provider-Versionen, damit es sich heute genauso verhält wie in sechs Monaten.

Nichts davon überlebt ein wachsendes Team, wenn nicht zwei Dinge nicht verhandelbar sind: jede Änderung wird reviewt und jede Änderung wird gegen echte, wegwerfbare Infrastruktur getestet. Ein Modul ist das einzige Stück Code, dessen Bugs sich mit jedem Konsumenten multiplizieren — es verdient die höchste Messlatte, die du hast.

Die Version ist der Vertrag

Wenn die Organisation wächst und neue Umgebungen auftauchen — eine Sandbox hier, eine zweite Region dort — hört die Version auf, ein Luxus zu sein, und wird zum Vertrag zwischen dem Modul und allen, die es nutzen. Jeder Konsument pinnt eine explizite Version; niemand folgt main. Semantische Versionierung sagt dem Konsumenten dann, was ein Sprung bedeutet: ein Patch ist sicher, ein Minor erweitert, ein Major zwingt ihn, Code zu ändern.

Pinnen ist auch, wie man mit Zuversicht vor- und zurückgeht. Auf Produktion zu heben ist ein Versionssprung; ein schlechtes Release ist ein Sprung zurück zum vorherigen. Ohne Versionen heißt 'das Modul zurückrollen' in Wahrheit 'herausfinden, auf welchem Commit es letzten Dienstag war'.

Konsumiere es aus GitHub — ohne Registry

Du brauchst weder ein privates Registry noch Artifactory, um Module sauber zu teilen. Referenziere das Modul direkt über seine Git-Source und einen ref. Dieselbe Source funktioniert auf einem Laptop und in CI; die Authentifizierung ist nur ein GitHub-Token — ein PAT lokal, das Runner-Token in CI — einmal verdrahtet, damit HTTPS-Clones die Credential mitführen.

# main.tf — an einen veröffentlichten Tag pinnen
module "vpc" {
  source = "git::https://github.com/acme/tf-modules.git//modules/vpc?ref=v1.4.0"

  cidr_block = "10.20.0.0/16"
  azs        = ["eu-west-1a", "eu-west-1b"]
}

Die Credential wird einmal über gits insteadOf gesetzt, sodass weder dein Code noch dein State ein Geheimnis enthält — Terraform klont einfach per HTTPS in deinem Namen:

# lokal + CI: git ein Token fuer github.com ueber HTTPS nutzen lassen
git config --global url."https://oauth2:${GITHUB_TOKEN}@github.com/".insteadOf "https://github.com/"

Unveränderlichkeit: pinne einen Commit-Hash, wenn er fest bleiben muss

Ein Tag ist bequem, aber er ist beweglich: jeder mit Schreibrechten kann v1.4.0 umbiegen oder auf anderen Code forcen, und dein nächster apply zieht ihn. Für die meisten Module reicht dieser Kompromiss. Für die, die sich nicht unter deinen Füßen ändern dürfen — alles Sicherheits- oder Compliance-Sensible — pinne stattdessen den vollen Commit-Hash. Ein Commit lässt sich nicht austauschen: ref=<sha> löst immer exakt zu den Bytes auf, die du reviewt hast.

# auf einen exakten, nicht austauschbaren Commit verriegelt
module "kms" {
  source = "git::https://github.com/acme/tf-modules.git//modules/kms?ref=3f9a1c4e8b7d6c5a4f3e2d1c0b9a8f7e6d5c4b3a"
}
Modul pinnen: Tag (beweglich) vs Commit-Hash (unveränderlich) source = "git::https://github.com/acme/tf-modules.git//vpc?ref=v1.4.0" Tag — bequem, aber beweglich: kann umgebogen oder geforct werden source = "git::https://github.com/acme/tf-modules.git//vpc?ref=3f9a1c4e8b7d" Commit-Hash — unveränderlich: niemand tauscht den Code unter dir Dieselbe Source läuft lokal und in CI — mit einem GitHub-Token authentifizieren. Kein Registry oder Artifactory nötig.
Dieselbe Git-Source funktioniert lokal und in CI hinter einem Token. Ein Tag ist bequem, lässt sich aber verschieben; ein Commit-Hash ist unveränderlich — nutze ihn, wenn das Modul sich nicht unter deinen Füßen ändern darf.

Veröffentliche das Modul wie ein Produkt

Wenn Konsumenten Versionen pinnen, muss etwas diese Versionen zuverlässig erzeugen — eine Pipeline im Repository des Moduls. Und es lohnt sich, präzise zu sein: ein Modul wird nicht 'deployt'. Es wird validiert, getestet und veröffentlicht. Das Deployen ist Sache des Konsumenten.

  1. Bei jedem Pull Request laufen die statischen Prüfungen: terraform fmt -check, terraform validate und tflint.
  2. Dann teste richtig — fahre das Beispiel des Moduls in einem wegwerfbaren Sandbox-Account hoch, validiere es (terraform test, oder Terratest für reichere Prüfungen) und zerstöre es. Ein Modul, das nie angewendet wurde, ist ein ungetestetes Modul.
  3. Beim Merge auf main lass GitVersion die nächste semantische Version aus der Commit-Historie berechnen, den passenden Git-Tag erstellen und veröffentlichen — kündige das Release im Slack- oder Teams-Kanal des Teams an (und spiegle es in ein Registry, falls du zufällig eines hast).
Baue das Modul wie ein Produkt: vom PR zu einer pinnbaren Version BEIM PULL REQUEST validierenfmt · validate · tflint testenin Sandbox anwenden · prüfen · zerstören Merge auf main AUF MAIN GitVersionnächste semver berechnen tagv1.4.0 veröffentlichenauf Slack / Teams ankündigen · (in ein Registry spiegeln)
Eine Version existiert erst, nachdem die Änderung reviewt, validiert und tatsächlich angewendet wurde. Beim Merge leitet GitVersion die nächste semver ab, erstellt den Tag und kündigt ihn an.
# .github/workflows/release.yml (Skizze)
on: { pull_request: {}, push: { branches: [main] } }

jobs:
  check:
    steps:
      - run: terraform fmt -check -recursive
      - run: terraform validate
      - run: tflint
      - run: terraform test          # wendet die Beispiele in einer Sandbox an und zerstoert

  release:
    if: github.ref == 'refs/heads/main'
    needs: check
    steps:
      - uses: gittools/actions/gitversion/execute@v3   # -> naechste semver
      - run: gh release create "v${VERSION}"
      - run: ./notify.sh "tf-modules ${VERSION} veroeffentlicht"  # Slack / Teams

Das genaue Werkzeug ist egal — GitVersion, release-please und semantic-release taugen alle. Worauf es ankommt: eine Version beginnt erst zu existieren, nachdem die Änderung reviewt, validiert und tatsächlich angewendet wurde.

Jetzt nutze es: von Terraform zu Terragrunt

Mit einem getesteten, getaggten Modul pinnt ein einfaches Terraform-Projekt es einfach — der module-Block oben ist die ganze Geschichte. Für ein oder zwei Umgebungen reicht das. Aber sobald du dieselbe Stack in dev, staging und Produktion fährst, in einer oder mehreren Regionen, ist das Kopieren dieser Verknüpfung pro Umgebung genau die Duplizierung, die Module beseitigen sollten.

Terragrunt hält Umgebungen DRY

Terragrunt umhüllt Terraform, um diese Wiederholung zu entfernen: es generiert die Backend- und Provider-Konfiguration, hält die Modulversion an einer Stelle pro Stack, und lässt jede Umgebung eine dünne Datei sein, die sagt 'dieses Modul, diese Version, diese Inputs'. Das Hochstufen entlang der Kette wird zu einem einzeiligen Versionssprung.

# live/prod/eu-west-1/vpc/terragrunt.hcl
terraform {
  source = "git::https://github.com/acme/tf-modules.git//modules/vpc?ref=v1.4.0"
}
include "root" { path = find_in_parent_folders() }

inputs = {
  cidr_block = "10.30.0.0/16"
  azs        = ["eu-west-1a", "eu-west-1b", "eu-west-1c"]
}

Eine Struktur, die skaliert: Projekt / Umgebung / Region

Lege das live-Repository so an, dass der Pfad die Adresse der Infrastruktur ist. Eine bewusst langweilige Form ist Umgebung → Region → Komponente — passe die Ebenen daran an, wie deine Organisation tatsächlich denkt (manche trennen zuerst nach Projekt, Account oder Team):

live/
├── root.hcl                    # Remote-State, Provider, gemeinsame Tags
├── dev/
│   └── eu-west-1/
│       ├── vpc/terragrunt.hcl
│       └── eks/terragrunt.hcl
├── staging/
│   └── eu-west-1/ ...
└── prod/
    ├── eu-west-1/
    │   ├── vpc/terragrunt.hcl
    │   └── eks/terragrunt.hcl
    └── eu-central-1/ ...

Jedes Blatt pinnt eine Modulversion, und der Ordner sagt dir genau, was wo läuft. Die Produktion zu aktualisieren wird zu einem reviewten PR, der ein ref= von v1.4.0 auf v1.5.0 ändert — klein, offensichtlich und trivial rückgängig zu machen.

Zuverlässige Module mit einem Zweck; bei jeder Änderung reviewt und getestet; von einer Pipeline versioniert und veröffentlicht; aus GitHub per Tag konsumiert — oder per unveränderlichem Commit-Hash, wenn es darauf ankommt — und mit einer Terragrunt-Struktur zusammengesetzt, die deine Landschaft abbildet. Das ist der Unterschied zwischen Modulen, die mit deinem Team skalieren, und Modulen, die still gegen es skalieren.

Alle Artikel

Haben Sie ein System wie dieses?

Kontakt aufnehmen

Finden Sie mich in den sozialen Medien