Image 2
Alle Artikel anzeigen

Ein praxisnaher Leitfaden um ein Terraform-Refactoring in Azure richtig zu strukturieren.

Viele Terraform-Projekte starten pragmatisch und enden als Monolith. Dieser Leitfaden zeigt, wie Sie Azure-Infrastruktur ohne Downtime in modulare States, klare Modulgrenzen und belastbare CI/CD-Governance überführen.

Microsoft Azure
Cloud
Terraform
Infrastructure as Code
Image

Einleitung

Fast jedes Terraform-Projekt beginnt gleich: ein main.tf, ein paar Azure-Ressourcen, schnelle Erfolge. Nach einigen Monaten ist daraus häufig ein unübersichtlicher Monolith geworden mit langen Plan-Laufzeiten, globale State-Locks und steigendem Risiko bei jeder Änderung.

In Enterprise-Umgebungen ist das kein kosmetisches Problem, sondern ein operatives Risiko. Wenn Netzwerk, IAM, Datenplattform und Workload-Deployment im selben State leben, wird jede kleine Anpassung zur potenziellen Großstörung.

Dieser Leitfaden zeigt eine praxiserprobte Refactoring-Strategie für Azure mit klaren Modulgrenzen, belastbarer State-Struktur, Migration ohne Downtime, Governance-Integration und CI/CD-Absicherung.

Warum Refactoring in Azure oft zu spät startet

Typische Warnsignale in gewachsenen Terraform-Landschaften:

  • State-Lock-Engpässe: Ein Team blockiert das andere, weil alles im selben Backend-Key liegt.
  • Unklare Verantwortlichkeiten: Plattform- und Applikationsteams verändern dieselben Dateien.
  • Drift zwischen Umgebungen: dev, staging und prod sind nicht mehr funktional vergleichbar.
  • Compliance-Risiko: Änderungen sind schwer nachvollziehbar und Rollen trennen sich nicht sauber.
  • Wachsende Recovery-Zeit: Bei Fehl-Deployments ist unklar, welche Ressourcen tatsächlich betroffen sind.

Zielbild: Architektur nach Lifecycle und Blast Radius

Die wichtigste Designentscheidung lautet: Nicht nach Azure-Services schneiden, sondern nach Lifecycle und Blast Radius.

Die erste Baustelle ist die State-Struktur. Meine Empfehlung für größere Azure-Setups ist dabei eine Trennung:

  • pro Umgebung (dev, staging, prod)
  • pro Domäne/Komponente (z. B. network, platform, identity, workload-x)
  • eigener Backend-Key je State
terraform {
  backend "azurerm" {
    subscription_id      = "xyz"
    resource_group_name  = "rg-tfstate-prod"
    storage_account_name = "sttfstateprod001"
    container_name       = "tfstate"
    key                  = "prod/network/core.tfstate"
  }
}

Damit werden Änderungen kleiner, parallelisierbar und auditierbar.

Die zweite Baustelle ist die Etablierung von klaren Modulgrenzen. Sinnvolle Modulgrenzen orientieren sich dabei an einem gemeinsamen Änderungszyklus:

  • Netzwerk-Baseline: VNet, Subnetze, NSG, UDR
  • Security-Baseline: Key Vault, Managed Identities, Policy Assignments
  • Workload-Module: App Service, AKS, Container Apps inkl. ihrer jeweiligen benötigten Abhängigkeiten
  • Observability: Log Analytics, Diagnostic Settings, Alerts

Ich gehe dabei sogar teilweise so weit, dass ich zum Beispiel bei einem AKS-Modul auch die dazugehörige Identity des AKS, RBAC-Berechtigungen etc. inkludiere. Wir wollen explizit keine "GOD-Modules", aber eine gesunde Gruppierung kann wirklich Sinn machen, wenn ohne die dazugehörigen Ressourcen das AKS-Cluster als Beispiel nutzlos ist.

Die dritte Baustelle ist die Orchestrierung einzelner Stages. Dabei ruft beispielsweise environments/prod dieselben Module auf wie environments/dev nur mit anderen Werten.

So vermeiden Sie Konfigurationsdrift und reduzieren den Pflegeaufwand bei Änderungen erheblich.

Dieses Vorgehen steht und fällt mit der erfolgreichen Spiegelung von Stages. Sobald die Fachbereiche Besonderheiten für einzelne Stages möchten, müssen mit Feature Flags etc. ausgeholfen werden. Das wird perspektivisch immer schwerer zu warten!

Die vierte Baustelle ist eine Migrationsstrategie ohne Downtime. Refactoring in Produktion ist ein kontrollierter Umbau und definitiv kein Big-Bang. Die Konsumenten und Nutzer der Infrastruktur sollen idealerweise nicht einmal merken, dass ein Refactoring stattgefunden hat. Zero-Downtime ist dabei das Stichwort!

Vorgehen zur Migration der State Files

Phase 1: Discovery und Sicherheitsnetz

In der ersten Phase der Migration liegt der Fokus darauf, vollständige Transparenz über den aktuellen Zustand zu schaffen und gleichzeitig ein belastbares Sicherheitsnetz einzurichten. Zunächst wird der bestehende Terraform-State mithilfe von terraform state pull gesichert. Diese Sicherung sollte versioniert und zusätzlich verschlüsselt abgelegt werden, um sowohl eine lückenlose Nachvollziehbarkeit als auch den Schutz sensibler Informationen sicherzustellen.

Anschließend werden die vorhandenen Ressourcen nach ihrer Kritikalität und Funktion gruppiert. Dabei empfiehlt es sich, besonders sensible oder zentrale Komponenten wie Netzwerkressourcen zunächst in einem reinen Read-only-Kontext zu betrachten, um unbeabsichtigte Änderungen zu vermeiden und Abhängigkeiten klar zu erkennen.

Parallel dazu wird geprüft, für welche Ressourcen ein Risiko besteht, dass sie im Rahmen der Migration neu erstellt werden müssen (Force-Recreate). Wartungsfenster sollten gezielt nur für diese Fälle eingeplant werden, um Auswirkungen auf den Betrieb zu minimieren und unnötige Unterbrechungen zu vermeiden.

Als zentrale Erfolgsmetrik für jeden einzelnen Migrationsschritt wird definiert, dass ein anschließender Terraform-Plan keine Änderungen mehr ausweist (Plan = No changes). Dies stellt sicher, dass der State korrekt migriert wurde und vollständig mit der realen Infrastruktur übereinstimmt.

Phase 2: Module aufbauen und exakt spiegeln

Vor der Durchführung des ersten terraform state mv ist es entscheidend, dass das neue Modul das bestehende Ist-Verhalten vollständig und präzise widerspiegelt. Dies bedeutet, dass alle relevanten Eigenschaften der bestehenden Ressourcen identisch übernommen werden müssen, um unbeabsichtigte Änderungen oder eine Neuerstellung von Ressourcen zu vermeiden.

Insbesondere müssen die verwendeten Namen exakt übereinstimmen, da Abweichungen dazu führen können, dass Terraform Ressourcen als neu interpretiert. Ebenso ist darauf zu achten, dass sämtliche Default-Werte im neuen Modul den bisherigen Konfigurationen entsprechen, auch wenn diese zuvor implizit gesetzt wurden. Darüber hinaus müssen alle Tags unverändert übernommen werden, um Konsistenz in Bezug auf Governance, Abrechnung und Automatisierung sicherzustellen.

Ein weiterer zentraler Aspekt ist die korrekte Abbildung aller Abhängigkeiten zwischen den Ressourcen. Diese müssen im neuen Modul genauso definiert sein wie in der bisherigen Struktur, damit Terraform die Beziehungen korrekt erkennt und keine falschen Änderungen oder Neuordnungen im Infrastrukturzustand vornimmt. Nur wenn das neue Modul das bestehende Verhalten vollständig und identisch abbildet, kann die Migration des States sicher und ohne Seiteneffekte erfolgen.

Beispielhafte Befehle als Orientierung:

terraform state mv \
  'azurerm_subnet.app' \
  'module.network.azurerm_subnet.this["app"]'
 
terraform plan
# Ziel: No changes

Wenn Änderungen auftauchen sollten sofort stoppen, Modulparameter korrigieren und erneut prüfen.

Phase 3: State-Splitting mit klaren Übergabepunkten

Sobald die neu eingeführten Module stabil funktionieren und konsistente Ergebnisse liefern, können die einzelnen Domänen schrittweise in separate Terraform-States überführt werden. Ziel dieses State-Splittings ist es, die Infrastruktur logisch zu entkoppeln, die Wartbarkeit zu verbessern und Änderungen auf klar abgegrenzte Verantwortungsbereiche zu beschränken.

Dabei ist es besonders wichtig, dass Abhängigkeiten zwischen den einzelnen States sauber und kontrolliert über definierte Schnittstellen erfolgen. Hierfür sollten bevorzugt explizite Outputs aus den vorgelagerten (Upstream-)States verwendet werden. Diese Outputs dienen als klar definierte Übergabepunkte und stellen sicher, dass nachgelagerte States nur die tatsächlich benötigten Informationen erhalten. Die Nutzung von terraform_remote_state sollte bewusst und gezielt erfolgen und auf das notwendige Minimum beschränkt werden, um unnötige Kopplung und Komplexität zu vermeiden.

Ein zentraler Grundsatz ist zudem die konsequente Vermeidung zirkulärer Abhängigkeiten zwischen States. Solche gegenseitigen Abhängigkeiten führen zu schwer wartbaren Strukturen und können Terraform-Ausführungen blockieren oder unvorhersehbares Verhalten verursachen.

Ein typisches Beispiel für eine saubere Übergabe ist die Bereitstellung einer Subnet-ID aus dem State der Netzwerkdomäne (network) an einen nachgelagerten State, beispielsweise für eine Workload-Domäne (workload). Die Subnet-ID wird dabei im Netzwerk-State als Output definiert und anschließend im Workload-State als Eingabevariable verwendet. Dadurch bleibt die Verantwortung klar getrennt, während gleichzeitig eine kontrollierte und transparente Integration gewährleistet wird.

Phase 4: Parallelisierung in CI/CD

Nach der Aufteilung der Infrastruktur in separate States werden auch die CI/CD-Pipelines konsequent entlang der jeweiligen Domänen strukturiert. Statt einer zentralen Pipeline, die sämtliche Ressourcen gemeinsam verarbeitet, entstehen eigenständige Pipelines für jede Domäne, beispielsweise plan-network-prod, plan-platform-prod oder plan-workload-a-prod. Jede dieser Pipelines ist ausschließlich für die Planung und Validierung der ihr zugeordneten Infrastrukturkomponenten verantwortlich.

Diese domänenspezifische Trennung führt zu deutlich kürzeren Feedback-Zyklen, da Änderungen nur noch den jeweils betroffenen Bereich betreffen und nicht mehr die gesamte Infrastruktur berücksichtigen müssen. Gleichzeitig wird verhindert, dass Teams sich gegenseitig blockieren, etwa durch konkurrierende Änderungen oder gemeinsam genutzte State-Locks. Dadurch verbessert sich nicht nur die Geschwindigkeit der Entwicklung und Bereitstellung, sondern auch die organisatorische Skalierbarkeit, da mehrere Teams unabhängig und parallel an unterschiedlichen Teilen der Infrastruktur arbeiten können.

Governance in Azure: nicht optional

Ein Refactoring von Terraform verbessert zunächst vor allem die Struktur und Lesbarkeit der Konfigurationen. Ohne eine konsequent umgesetzte Governance führt dies jedoch nicht automatisch zu einer stabileren, sichereren oder besser kontrollierten Plattform. Nachhaltige Architekturqualität entsteht erst dann, wenn klare Regeln nicht nur dokumentiert, sondern technisch erzwungen werden.

Ein zentraler Bestandteil ist die Umsetzung von Azure Policy als Code. Dadurch können organisatorische und sicherheitsrelevante Anforderungen systematisch durchgesetzt werden, etwa die Einschränkung auf erlaubte Regionen, die verpflichtende Verwendung definierter Tags oder die Nutzung von Private Endpoints. Diese Regeln stellen sicher, dass alle Ressourcen unabhängig vom verantwortlichen Team den gleichen Standards entsprechen.

Ebenso wichtig ist die konsequente Anwendung des RBAC-Minimumprinzips (Least Privilege) für jede Pipeline und jeden State. Jede Ausführungseinheit sollte nur die Berechtigungen erhalten, die für ihre spezifische Aufgabe zwingend erforderlich sind. Dies reduziert das Risiko unbeabsichtigter Änderungen und begrenzt mögliche Auswirkungen im Fehlerfall.

Darüber hinaus müssen verbindliche Tagging-Standards etabliert und eingehalten werden. Einheitliche Tags wie Kostenstelle, Kritikalität oder verantwortlicher Owner ermöglichen eine klare Zuordnung von Ressourcen, verbessern die Kostenkontrolle und unterstützen Betriebs- und Governance-Prozesse.

Ein weiterer wesentlicher Aspekt ist das konsequente Version-Pinning von Providern und Modulen. Durch die explizite Festlegung von Versionen wird sichergestellt, dass Infrastrukturänderungen reproduzierbar bleiben und nicht unbeabsichtigt durch neue Provider- oder Modulversionen beeinflusst werden.

Ergänzend dazu sollten automatisierte Policy- und Compliance-Prüfungen fest in den Pull-Request-Prozess integriert werden, beispielsweise mithilfe von Tools wie Checkov oder Open Policy Agent (OPA). Diese Prüfungen stellen sicher, dass Verstöße gegen Governance-Vorgaben frühzeitig erkannt und bereits vor der Zusammenführung von Änderungen verhindert werden.

Auf diese Weise wird Architekturqualität nicht nur als theoretisches Ziel formuliert, sondern durch technische Kontrollen verbindlich umgesetzt und dauerhaft sichergestellt.

CI/CD-Integration: Referenzablauf

Ein robuster Pipeline-Flow pro State:

  1. terraform fmt -check
  2. terraform init -backend=false
  3. terraform validate
  4. tflint + Security-Scan (Checkov/Tfsec)
  5. terraform plan (Artifact speichern)
  6. Manuelle Freigabe für prod
  7. terraform apply nur auf freigegebenen Plan

Ergänzend sinnvoll:

  • Drift-Detection als nächtlicher Read-Only-Job
  • verpflichtende Reviewer aus Plattformteam bei prod
  • Blockade bei unpinned Provider-Versionen

Typische Fehlerbilder aus Projekten

1) "Unkritischer" Rename im Modul: Ein umbenannter Resource-Block ohne moved-Anweisung erzeugt Destroy/Create-Pläne.

2) Zu frühes State-Splitting: Wer vor stabilen Modul-Schnittstellen splittet, produziert fragile Cross-State-Abhängigkeiten.

3) Remote State als Datenbank missbraucht: Zu viele implizite Abhängigkeiten machen Änderungen unvorhersehbar.

4) Pipeline ohne Plan-Artifact-Bindung: Wenn Apply nicht exakt auf dem freigegebenen Plan basiert, sinkt Change-Kontrolle drastisch.

5) Governance nachgelagert: Ohne frühe Policy-Checks werden Verstöße erst kurz vor Go-Live sichtbar.

Praxisbeispiel (Azure)

In einem Kundenprojekt (reguliertes Umfeld) wurden ~300 Ressourcen aus einem Monolithen in 6 Domänen-States überführt. Das Ergebnis nach Abschluss war dabei:

  • Plan-Zeiten von ~40 Minuten auf 4–8 Minuten je Domäne reduziert
  • parallele Deployments für zwei Teams ohne Lock-Konflikte
  • deutlich schnellere Root-Cause-Analyse bei Incidents
  • sauber auditierbare Änderungen entlang von Verantwortungsgrenzen

Entscheidend war nicht nur die technische Migration, sondern die Kombination aus State-Architektur, Modul-APIs und Governance in der Pipeline.

Fazit

Terraform-Refactoring in Azure ist eine Architekturaufgabe mit Betriebsverantwortung. Der größte Hebel liegt dabei in drei Entscheidungen:

  • States entlang von Blast Radius und Verantwortlichkeit schneiden
  • Module als stabile Verträge definieren
  • Migration inkrementell mit konsequentem No-Change-Plan absichern

So wird aus einer riskanten Monolith-Struktur eine skalierbare IaC-Plattform, die Teams beschleunigt statt auszubremsen.

Wenn Sie Unterstützung bei der Refaktorierung Ihrer Azure-Infrastruktur benötigen, erfahren Sie mehr über unseren Cloud Audit Service.


Sie möchten mit uns zusammenarbeiten?

Wir freuen uns von Ihnen zu hören.

Sie mögen keine Formulare?

mertkan@henden-consulting.de