Sägetstrasse 18, 3123 Belp, Switzerland +41 79 173 36 84 info@ict.technology

      Terraform @ Scale - Teil 1d: Fallstricke und Best Practices in mandantenfähigen Umgebungen

      Remote States sind ein leistungsfähiges Werkzeug, um Informationen kontrolliert zwischen Teams und Tenants weiterzugeben. Gerade in komplexen Cloud-Umgebungen mit mehreren Verantwortungsbereichen schafft es Transparenz, Wiederverwendbarkeit und Skalierbarkeit. Gleichzeitig birgt es Risiken: fehlerhafte Zustände, Zugriffsprobleme und nicht aufgelöste Abhängigkeiten können die Stabilität der gesamten Infrastruktur gefährden. Dieser Artikel zeigt, wie Sie diese Herausforderungen vermeiden und mit klaren Strukturen und erprobten Praktiken die Grundlage für zuverlässige, automatisierte Infrastruktur schaffen.

      State-Locking in Multi-Tenant-Umgebungen

      In einer Umgebung mit mehreren Teams, die gleichzeitig an verschiedenen Teilen der Infrastruktur arbeiten, ist State-Locking unverzichtbar. Ohne Locking kann es passieren, dass mehrere Terraform-Anwendungen denselben State parallel zu aktualisieren versuchen und sich dabei gegenseitig überschreiben. Und das endet fast immer in einem Desaster. Vielmehr braucht es zwingend einen Mechanismus, welcher ein Statefile exklusiv für einen Anwender reserviert, bis dieser seine Schreibzugriffe beendet hat und es wieder für andere zugreifende Prozesse freigibt.

      Best Practices für State-Locking

      • Wählen Sie ein Backend mit robustem Locking-Mechanismus:

        Wie bereits im letzten Teil dieser Artikelserie erklärt, sind neben der Verwendung einer lokalen Datei in einer Single-User-Umgebung nur noch die Verwendung von Consul als Remote Backend für Statefiles sowie Terraform Cloud und Enterprise supportet und offiziell zertifiziert. Terraform unterstützt auch andere Backends wie HTTPS oder S3, aber für diese sind von den Supportverträgen nicht abgedeckt, Benutzung erfolgt ausdrücklich auf eigene Gefahr und Verantwortung und robustes, fehlerfreies Locking unterstützen dort auch längst nicht alle. Consul und Terraform Enterprise bieten hingegen zuverlässige Locking-Mechanismen auf Enterprise-Level.

        Bei Verwendung von Consul setzt Terraform automatisch eine Session, um den State während des Plan- und Apply-Prozesses zu schützen.


        terraform {
          backend "consul" {
            address = "consul.example.com:8500"
            path    = "terraform/customer-a"
            lock    = true  # Explizites Aktivieren des Lockings
          }
        }

        Diese Locking-Funktion benötigt also keine manuelle Einrichtung von Sessions oder anderen Mechanismen – Terraform übernimmt dies automatisch.

      • Verwaiste Locks erkennen und auflösen:

        Verwaiste Locks entstehen meist, wenn Terraform-Prozesse unerwartet abbrechen (z. B. durch CI/CD-Abbruch, Netzwerkausfall oder Abbruch durch den Benutzer).

        Um verwaiste Locks automatisch zu bereinigen, sollten Sie einen präventiven Cleanup-Prozess implementieren. Ein einfacher Check vor dem Terraform-Lauf genügt in der Praxis:


         

        consul lock -delete "terraform/customer-a" || true


        Dieser Befehl entfernt bestehende Locks, bevor ein neuer Terraform-Prozess startet – effizient und ohne unnötigen Verwaltungsaufwand.

      • Automatische Lock-Timeouts konfigurieren:In der Consul-Konfiguration (consul.hcl) können Sie Timeout-Werte für Lock-Sessions definieren. Dies stellt sicher, dass festgesetzte Locks automatisch entfernt werden.


        Empfohlene Werte für produktive Umgebungen sind:


        session_ttl_min = "15m"
        session_ttl_max = "1h"

        So verhindern Sie, dass verwaiste Locks langfristig Ressourcen blockieren.

      • Berücksichtigen Sie Lock-Konflikte in CI/CD-Pipelines:

        In CI/CD-Umgebungen können Konflikte auftreten, wenn mehrere Pipelines gleichzeitig versuchen, denselben Lock zu setzen.

        Ein bewährtes Muster ist hier ein Retry-Mechanismus mit Backoff-Strategie:


        for i in {1..5}; do
          terraform apply && break
          echo "Lock-Konflikt erkannt. Neuer Versuch in $((i * 10)) Sekunden..."
          sleep $((i * 10))
        done

        Dieser Mechanismus sorgt dafür, dass Ihr Deployment auch bei kurzzeitig festhängenden Locks stabil bleibt. Eine Erklärung, was das Skript macht:

        1. Es führt eine Schleife fünfmal durch: (for i in {1..5})
        2. Bei jedem Durchlauf versucht es, terraform apply auszuführen
        3. Wenn terraform apply erfolgreich ist (&&), bricht es die Schleife mit break ab
        4. Falls terraform apply fehlschlägt, wartet es eine gewisse Zeit (sleep) bevor der nächste Versuch startet
        5. Die Wartezeit erhöht sich mit jedem Versuch ($((i * 10))), also 10, 20, 30, 40 und 50 Sekunden


        Somit wird die Bereitstellung mittels Terraform automatisch wiederholt, falls es temporäre Probleme wie Netzwerkfehler, API-Limits oder Ressourcenkonflikte gibt, die einen einzelnen Versuch fehlschlagen lassen könnten. 

        Ein robustes Locking-System reduziert das Risiko von Fehlern und verhindert, dass sich Terraform-Läufe gegenseitig blockieren. Mit den beschriebenen Maßnahmen sichern Sie Ihre Umgebung zuverlässig gegen verwaiste Locks und unnötige Verzögerungen ab.

       

      State-Versioning

      Eine solide Versionierung ist unverzichtbar. Bei Problemen können Sie so einfach auf frühere Versionen zurückgreifen. Terraform Cloud/Enterprise bieten diese Funktion standardmäßig; bei Consul müssen Sie hingegen zusätzliche Backup-Mechanismen (Snapshots) implementieren.

      Umgang mit State-Abhängigkeiten und Vermeidung von Zyklen

      Abhängigkeiten zwischen States können schnell komplex werden und im schlimmsten Fall zu zirkulären Abhängigkeiten führen, die eine Aktualisierung der Infrastruktur unmöglich machen.

      Da Remote State-Abhängigkeiten in terraform_remote_state statisch sind, müssen abhängige Konfigurationen manuell aktualisiert werden, sobald sich relevante Outputs ändern.

      Best Practices zur Vermeidung von Zyklen:

      • Hierarchische Abhängigkeitsstruktur: Globale RessourcenRegionale RessourcenTenant-spezifische RessourcenAnwendungsspezifische Ressourcen. Diese klare Einwegabhängigkeit verhindert Zyklen.
      • Indirekte Referenzen nutzen: Falls eine direkte Referenz Zyklen verursacht, führen Sie die Information über eine Zwischenebene weiter:
        # Statt direkter Referenz von A → C und C → A:
        # A → B → C (mit B als Vermittler)
        # In Konfiguration B:
        output "information_from_a" {
        value = data.terraform_remote_state.a.outputs.needed_value
        }




        # In Konfiguration C:
        data "terraform_remote_state" "b" {
        # ...
        }
        local {
        value_from_a = data.terraform_remote_state.b.outputs.information_from_a
        }

      • Data Sources statt Remote State: Falls möglich, setzen Sie auf native Data Sources. Diese sind dynamischer und reduzieren Abhängigkeiten zwischen States.

        # Statt:
        data "terraform_remote_state" "network" {
          # ...
        }
        
        # Besser, wenn möglich:
        data "oci_core_subnet" "app_subnet" {
          subnet_id = "ocid1.subnet.oc1..."
        }

        Allerdings: Setzen Sie Data Sources gezielt ein und vermeiden Sie deren Einsatz in for_each- oder anderen Schleifen, da sie bei jeder Ausführung einen API-Call auslösen und deshalb schlecht skalieren. Die Benutzung von Data Sources innerhalb von Basismodulen ist deshalb in vielen Fällen keine gute Idee. Aber von ihren Root-Modulen aufgerufene Module sollten auch nicht auf Statefiles zugreifen. Setzen Sie daher Ihre Data Sources ebenfalls auf der Ebene der Root-Module ein. 

      Minimierung von State-Informationen zur Verbesserung der Leistung

      Je größer Ihre State-Datei wird, desto länger dauert jeder terraform plan. Terraform liest den gesamten State ein und kann bei komplexen Umgebungen deutlich ausgebremst werden.

      Best Practices zur State-Optimierung:

      • Granulare State-Aufteilung: Ein guter Richtwert: Ein State sollte nicht mehr als 100 bis 250 Ressourcen umfassen, um akzeptable Planungszeiten zu gewährleisten. Wenden Sie das Goldilocks-Principle (Goldlöckchen-Prinzip) an (darüber sprechen wir noch in einem späteren Artikel dieser Serie).
      • Vorsicht mit komplexen Outputs: Beschränken Sie Outputs auf das Wesentliche und Notwendige:

        # Vermeiden:
        output "entire_vcn" {
          value = oci_core_vcn.main
        }
        
        # Besser:
        output "vcn_essential_info" {
          value = {
            id         = oci_core_vcn.main.id
            cidr_block = oci_core_vcn.main.cidr_block
          }
        }

      • Vermeiden Sie sensitive Outputs, wenn nicht nötig: Sie vergrößern den State und Terraform speichert zusätzliche Metadaten. Sensitive Outputs lassen sich in anderen Modulen auch nicht verwenden, deshalb kann man sie in der Regel auch weglassen.

      Debugging komplexer State-Abhängigkeiten

      Das Debugging von Remote State-Problemen kann knifflig sein. Hier einige Tipps:

      • Detaillierte Logs aktivieren:

        export TF_LOG=DEBUG
        export TF_LOG_PATH=./terraform.log

      • State-Validierung in CI/CD einbauen:

        terraform state pull | jq '.outputs.network_config.value | has("vcn_id")'

      • terraform console benutzen:

        $ terraform console
        > data.terraform_remote_state.network.outputs.subnet_ids

      Provider-Aliasing für komplexe Szenarien

      Bei der Arbeit mit mehreren Accounts (oder mehreren Regionen unter einem Account) erleichtert Provider-Aliasing die Konfiguration massiv:


      provider "oci" {
        alias  = "global"
        region = "eu-frankfurt-1"
      }
      
      provider "oci" {
        alias  = "customer_a"
        region = "eu-amsterdam-1"
      }
      
      module "customer_a_instance" {
        source     = "./modules/instance"
        provider   = oci.customer_a
        subnet_id  = module.global.outputs.subnet_id
      }

      Fazit

      Die Beherrschung dieser Best Practices hilft Ihnen, Ihre Multi-Tenant-Infrastruktur stabil und effizient zu betreiben. Mit zunehmender Erfahrung werden Sie feststellen, dass diese Muster nicht nur Probleme vermeiden, sondern auch die Zusammenarbeit zwischen Teams verbessern und die Gesamtqualität Ihrer Infrastruktur erhöhen.