Infrastructure as Code mit Plan-Diff im Merge Request
Terraform manuell von der Kommandozeile auszufuehren funktioniert im Team schnell nicht mehr zuverlaessig. Die GitLab-eigene Terraform-Integration bildet plan und apply als Pipeline-Jobs ab, verwaltet den State zentral und zeigt Aenderungen direkt als Diff im Merge Request, bevor irgendjemand echte Infrastruktur veraendert.
Inhaltsverzeichnis
- 1. Warum Infrastructure-as-Code-Pipelines mehr brauchen als lokales Terraform
- 2. Grundlagen: GitLab-Terraform-Template einbinden
- 3. terraform plan als Pipeline-Job
- 4. terraform apply als manueller Job mit needs auf plan
- 5. State-Backend direkt in GitLab speichern
- 6. Merge-Request-Widget mit Plan-Diff
- 7. Vorteile gegenueber manuellem Terraform-CLI-Einsatz
- 8. Automatisches Apply auf Staging, manuelles Apply auf Produktion
- 9. Fazit: Terraform-Pipelines als Standard fuer Team-Infrastruktur
- 10. Zusammenfassung
- 11. FAQ
1. Warum Infrastructure-as-Code-Pipelines mehr brauchen als lokales Terraform
Terraform lokal auf dem eigenen Rechner auszufuehren funktioniert, solange nur eine einzige Person Infrastrukturaenderungen vornimmt. Sobald ein Team gemeinsam an derselben Infrastruktur arbeitet, entstehen typische Probleme: Der lokale State ist veraltet, zwei Personen fuehren gleichzeitig apply aus und ueberschreiben sich gegenseitig, oder niemand kann im Nachhinein nachvollziehen, wer wann welche Aenderung tatsaechlich angewendet hat.
Eine Pipeline-basierte Terraform-Ausfuehrung loest diese Probleme strukturell: Jede Aenderung durchlaeuft denselben, reproduzierbaren Prozess aus plan und apply, der State liegt zentral an einem Ort, auf den alle Teammitglieder ueber dieselbe, kontrollierte Schnittstelle zugreifen, und jede angewendete Aenderung ist über die Pipeline-Historie lueckenlos nachvollziehbar.
2. Grundlagen: GitLab-Terraform-Template einbinden
GitLab stellt ein offizielles CI-Template unter Terraform/Base.latest.gitlab-ci.yml bereit, das die grundlegenden Terraform-Jobs mit sinnvollen Defaults vordefiniert. Durch include: wird dieses Template in die eigene .gitlab-ci.yml eingebunden, ohne dass die komplette Job-Logik von Hand nachgebaut werden muss. Das Template kuemmert sich unter anderem um terraform init mit dem GitLab-Backend und um das Format der JSON-Ausgabe, die spaeter fuer das Plan-Widget im Merge Request benoetigt wird.
Zusaetzlich zum Template wird ueblicherweise ein eigenes Image benoetigt, das Terraform selbst enthaelt, etwa registry.gitlab.com/gitlab-org/terraform-images/stable, das GitLab passend zu den eigenen Templates pflegt. Die konkrete Terraform-Version laesst sich ueber den Image-Tag festlegen, was Versions-Upgrades kontrollierbar und reproduzierbar macht, statt sich auf eine zufaellig auf dem Runner installierte Version zu verlassen.
include:
- template: Terraform/Base.latest.gitlab-ci.yml
variables:
TF_ROOT: ${CI_PROJECT_DIR}/infrastructure
TF_STATE_NAME: production
default:
image:
name: registry.gitlab.com/gitlab-org/terraform-images/stable:latest
3. terraform plan als Pipeline-Job
Der plan-Job berechnet, welche Aenderungen an der realen Infrastruktur noetig waeren, ohne sie tatsaechlich anzuwenden. Das Template erzeugt dabei automatisch sowohl eine menschenlesbare Textausgabe als auch eine maschinenlesbare JSON-Datei, die als artifacts: reports: terraform an GitLab uebergeben wird. Diese JSON-Datei ist die Grundlage fuer das spaeter im Merge Request angezeigte Plan-Widget.
In jedem Merge Request laeuft plan automatisch, sodass Reviewer bereits vor der eigentlichen Freigabe sehen, welche konkreten Ressourcen erstellt, geaendert oder geloescht wuerden. Das verhindert, dass ein Code-Review sich ausschliesslich auf die .tf-Dateien selbst stuetzt, waehrend die tatsaechliche Auswirkung auf die Infrastruktur erst beim spaeteren apply sichtbar wird.
plan:
extends: .terraform:build
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH == "main"
plan-json:
extends: .terraform:build
script:
- gitlab-terraform plan-json
needs: ["plan"]
artifacts:
reports:
terraform: ${TF_ROOT}/plan.json
4. terraform apply als manueller Job mit needs auf plan
Der apply-Job wendet die im plan-Job berechneten Aenderungen tatsaechlich an. Aus Sicherheitsgruenden sollte er praktisch immer when: manual nutzen und ueber needs: explizit vom vorherigen plan-Job abhaengen, damit niemals ein apply ohne einen zugehoerigen, vorher gepruften plan-Lauf ausgefuehrt werden kann. Ein protected environment fuer den apply-Job stellt zusaetzlich sicher, dass nur autorisierte Teammitglieder die tatsaechliche Anwendung freigeben duerfen.
Wichtig ist, dass apply auf dem exakt gleichen Terraform-Plan aufbaut, der im Merge Request angezeigt wurde, und nicht auf einer neu berechneten Version, die sich durch zwischenzeitliche Aenderungen an der realen Infrastruktur unterscheiden koennte. Das GitLab-Template loest das, indem der plan-Output als Artefakt an den apply-Job weitergereicht wird, statt plan innerhalb des apply-Jobs erneut auszufuehren.
apply:
extends: .terraform:build
environment:
name: production
needs: ["plan"]
rules:
- if: $CI_COMMIT_BRANCH == "main"
when: manual
5. State-Backend direkt in GitLab speichern
Statt eines separat zu betreibenden Backends wie S3 oder Azure Blob Storage bietet GitLab ein eigenes, HTTP-basiertes Terraform-State-Backend, das ueber die GitLab-API angesprochen wird und den State direkt im Projekt speichert, versioniert und mit Locking absichert. Konfiguriert wird das ueber einen backend "http"-Block, dessen Adresse sich aus der GitLab-Projekt-ID und dem gewaehlten TF_STATE_NAME zusammensetzt, wie im GitLab-Template bereits vordefiniert.
Der Vorteil dieses gemanagten Backends ist, dass keine zusaetzliche Cloud-Ressource ausschliesslich fuer die Terraform-State-Verwaltung betrieben und mit eigenen Zugriffsrechten abgesichert werden muss. Die Zugriffskontrolle laeuft ueber dieselben GitLab-Projektrechte, die ohnehin fuer das Repository gelten, und der State ist ueber die GitLab-Oberflaeche unter Operate > Terraform States direkt einsehbar und bei Bedarf sperrbar.
terraform {
backend "http" {
}
}
# Init-Aufruf im Template setzt die Backend-Parameter automatisch:
# terraform init \
# -backend-config=address=... \
# -backend-config=lock_address=... \
# -backend-config=unlock_address=... \
# -backend-config=username=... \
# -backend-config=password=$CI_JOB_TOKEN
6. Merge-Request-Widget mit Plan-Diff
Sobald der plan-json-Job seinen Report als artifacts: reports: terraform bereitstellt, zeigt GitLab automatisch ein eigenes Widget im Merge Request an, das die geplanten Aenderungen zusammengefasst darstellt: wie viele Ressourcen erstellt, geaendert und geloescht werden, mit der Moeglichkeit, ins Detail jeder einzelnen Ressourcenaenderung hineinzuklicken. Das erspart Reviewern, die vollstaendige, oft lange Textausgabe von terraform plan manuell im Job-Log zu durchsuchen.
Dieses Widget ist besonders wertvoll bei Aenderungen, die auf den ersten Blick harmlos wirken, tatsaechlich aber ein Neuanlegen statt eines In-Place-Updates einer Ressource ausloesen, etwa wenn ein unveraenderliches Attribut wie ein AWS-Instanztyp in einer Weise geaendert wird, die Terraform zwingt, die Ressource zu loeschen und neu zu erstellen. Ein Reviewer sieht solche destruktiven Aenderungen im Plan-Widget klar markiert, bevor der apply-Job ueberhaupt freigegeben wird.
7. Vorteile gegenueber manuellem Terraform-CLI-Einsatz
Manueller Terraform-CLI-Einsatz auf Entwicklerrechnern bringt mehrere strukturelle Schwaechen mit sich: Jede Person braucht lokalen Zugriff auf Cloud-Zugangsdaten mit potenziell weitreichenden Rechten, es gibt kein zentrales, erzwungenes Vier-Augen-Prinzip vor einem apply, und unterschiedliche lokale Terraform-Versionen zwischen Teammitgliedern koennen zu leicht abweichendem Verhalten fuehren. Die GitLab-Integration adressiert all diese Punkte strukturell, nicht nur durch Team-Disziplin und Konventionen.
Zusaetzlich profitieren Teams von der vollstaendigen Audit-Historie: Jede Pipeline dokumentiert, wer welchen Merge Request erstellt, welchen Plan gesehen und welchen apply-Job freigegeben hat, alles nachvollziehbar ueber die GitLab-Oberflaeche. Bei manuellem CLI-Einsatz existiert diese Nachvollziehbarkeit meist nur, wenn zusaetzlich manuell Protokoll gefuehrt wird, was in der Praxis selten konsequent passiert.
8. Automatisches Apply auf Staging, manuelles Apply auf Produktion
Nicht jede Terraform-Aenderung muss denselben Freigabeprozess durchlaufen. Fuer eine Staging-Umgebung, in der Fehlkonfigurationen leicht rueckgaengig gemacht werden koennen und die ohnehin regelmaessig neu aufgebaut wird, ist ein automatisches apply direkt nach erfolgreichem plan oft vertretbar und beschleunigt die Feedback-Schleife bei der Entwicklung von Terraform-Modulen erheblich.
Fuer Produktionsumgebungen bleibt ein manuelles, durch ein protected environment abgesichertes apply dagegen die richtige Wahl, da hier Fehlkonfigurationen echte Ausfaelle oder Kosten verursachen koennen. Ueber getrennte TF_STATE_NAME-Werte pro Umgebung und rules:-Bedingungen, die auf den jeweiligen Branch reagieren, lassen sich beide Freigabestufen innerhalb derselben .gitlab-ci.yml sauber abbilden, ohne Code zu duplizieren.
apply-staging:
extends: .terraform:build
environment:
name: staging
variables:
TF_STATE_NAME: staging
needs: ["plan-staging"]
rules:
- if: $CI_COMMIT_BRANCH == "develop"
apply-production:
extends: .terraform:build
environment:
name: production
variables:
TF_STATE_NAME: production
needs: ["plan-production"]
rules:
- if: $CI_COMMIT_BRANCH == "main"
when: manual
9. Fazit: Terraform-Pipelines als Standard fuer Team-Infrastruktur
Die GitLab-eigene Terraform-Integration macht aus einem potenziell riskanten, manuellen Prozess eine nachvollziehbare, reviewte Pipeline mit klarer Trennung zwischen plan und apply. Fuer jedes Team, das Infrastruktur gemeinsam verwaltet, reduziert dieser Ansatz das Risiko unbeabsichtigter oder unkoordinierter Aenderungen erheblich.
Der Umstieg von lokalem Terraform auf die Pipeline-Variante lohnt sich bereits ab zwei Personen, die regelmaessig an derselben Infrastruktur arbeiten. Das gemanagte State-Backend und das Merge-Request-Plan-Widget sind dabei keine Komfortfunktionen am Rande, sondern die eigentlichen Kernvorteile gegenueber dem klassischen CLI-Workflow.
| Aspekt | Manueller Terraform-CLI-Einsatz | GitLab-Terraform-Integration |
|---|---|---|
| State-Verwaltung | Lokal oder selbst betriebenes Backend | GitLab-gemanagtes HTTP-Backend mit Locking |
| Review vor Aenderung | Optional, abhaengig von Teamdisziplin | Plan-Diff automatisch im Merge-Request-Widget |
| Zugriffskontrolle | Lokale Cloud-Zugangsdaten pro Person | Protected environment, GitLab-Projektrechte |
| Audit-Historie | Meist nicht vorhanden | Vollstaendig ueber Pipeline- und MR-Historie |
Mironsoft
CI/CD-Pipelines, Zero-Downtime-Deployments und Release-Automatisierung
Deployments, die ohne Ausfallzeit und ohne Nervenkitzel laufen?
Wir prüfen bestehende GitLab-Pipelines auf fragile Deployment-Schritte und fehlende Absicherung und bauen daraus einen Release-Prozess mit Zero-Downtime-Deployments, automatisierten Checks und einem Rollback, dem ihr im Ernstfall vertrauen könnt.
Pipeline-Review
Bestehende .gitlab-ci.yml auf Fragilität, fehlende Stages und Sicherheitslücken prüfen.
Zero-Downtime-Deployment
Symlink-Releases, Health-Checks und Rollback-Strategien für Magento-Shops aufbauen.
CI/CD-Automatisierung
Tests, Security-Scans und Deployments zu einer zuverlässigen Pipeline verbinden.
10. Zusammenfassung
GitLab-Terraform-Integration: Das Wichtigste auf einen Blick
Template einbinden
include: template: Terraform/Base.latest.gitlab-ci.yml liefert vordefinierte plan- und apply-Jobs.
Plan vor Apply
apply nutzt needs: auf plan und wendet exakt den zuvor berechneten und gepruften Plan an.
Gemanagtes State-Backend
GitLab speichert den Terraform-State versioniert und mit Locking direkt im Projekt.
Plan-Diff im Merge Request
Ein automatisches Widget zeigt erstellte, geaenderte und geloeschte Ressourcen vor der Freigabe.