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

      Terraform @ Scale - Teil 1c: Praktische Umsetzung von Remote State Data Flows

      Durch eine Kombination aus sorgfältig strukturierten Remote Backends, durchdachter Output-Gestaltung und gezieltem Einsatz der terraform_remote_state Data Source können Sie einen kontrollierten Informationsfluss zwischen verschiedenen Tenant-Ebenen etablieren – und das, ohne die Isolation der einzelnen Mandanten zu beeinträchtigen.

      Die effektive Nutzung von Remote State zum Informationsaustausch zwischen organisatorischen Einheiten erfordert eine durchdachte Konfiguration der Terraform-Umgebung. Zentral dabei ist die Auswahl und Einrichtung eines geeigneten Storage Backends für die Speicherung der Zustandsdaten in den sogenannten State Files.

      Konfiguration eines Remote Backends

      Terraform bietet verschiedene offizielle Backends zur Speicherung von Zustandsdaten. Die von HashiCorp offiziell unterstützten Optionen sind:

       Beispielkonfiguration
       HashiCorp Consul
       terraform {
        backend "consul" {
          address = "consul.example.com:8500"
          scheme  = "https"
          path    = "terraform/network/global"
          lock    = true
        }
      }
       HashiCorp Terraform Enterprise
       terraform {
        backend "remote" {
          hostname     = "terraform.example.com"
          organization = "ict-technology"
          
          workspaces {
            name = "network-global"
          }
        }
      }
        HashiCorp Terraform Cloud
       terraform {
        backend "remote" {
          hostname     = "app.terraform.io"
          organization = "ict-technology"
          
          workspaces {
            name = "network-global"
          }
        }
      }

       

       Diese Storage Backends bieten mehrere Vorteile gegenüber lokalen Zustandsdateien:

      • Zentrale Speicherung: Alle Teammitglieder arbeiten mit demselben Zustand.
      • Locking-Mechanismen: Verhindert parallele Änderungen und damit verbundene Konflikte.
      • Versionierung: Speichert den Verlauf von Änderungen.
      • Zugriffskontrolle: Ermöglicht differenzierte Berechtigungskonzepte.

      Bei der Auswahl eines Backends sollten Sie die Anforderungen Ihrer Organisation berücksichtigen.

      Consul ist eine ausgezeichnete Wahl für Unternehmen, welche die kostenlose Variante von Terraform einsetzen. 

      Terraform Cloud und Terraform Enterprise hingegen haben eine Client/Server-Architektur und serverseitig bereits ein Storage Backend integriert, man muss sich hier also keine Gedanken darüber machen und kann einfach das integrierte Storage benutzen. Terraform Cloud/Enterprise bieten darüber hinaus zusätzliche Funktionen wie Policy-as-Code zur Implementierung und Erzwingung von Richtlinien, Governance-Kontrollen und erweiterte Zusammenarbeitsfunktionen.

      Es gibt auch weitere Backends, die Terraform unterstützt. Dazu gehören Backends wie Amazon S3, HTTPS, diverse Datenbanken und einige mehr.
      Bei diesen gibt es allerdings einen Haken, denn HashiCorp bietet nur Support für lokale State Files, Consul und Terraform Cloud/Enterprise.
      Sie können andere Backends also einsetzen, aber im Problemfall dürfen Sie keine Unterstützung oder Problemlösung durch HashiCorp erwarten, auch nicht bei existierendem Enterprise-Supportvertrag.
      In kritischen Produktionsumgebungen sollten Sie daher ausschließlich Consul oder Terraform Cloud/Enterprise als Remote State Backends einsetzen.

      Falls Sie ein von HashiCorp nicht supportetes Backend benutzen, sollten Sie unbedingt darauf achten, dass das Backend einen Locking-Mechanismus bietet!
      Ansonsten laufen Sie Gefahr, dass bei parallelen Läufen eines terraform apply Kommandos sich die parallel laufenden Terraform-Instanzen gegenseitig die State Files überschreiben und dann wäre ihre komplette Infrastruktur inkonsistent. Zum Beispiel sind Backends wie ein NFSv3 Share oder ein HTTPS-Backend wahrscheinlich eine eher fragwürdige Wahl und Sie riskieren Datenverluste. 

       

      Sicherheitsmechanismen

      Die Speicherung der State Files ist kritisch, da diese sensible Informationen wie Zugangsdaten und Infrastrukturdetails enthalten können.

      Verschlüsselung 

      Bei Terraform Cloud/Enterprise werden Zustandsdaten automatisch im Ruhezustand und während der Übertragung verschlüsselt. Bei Consul sollten Sie TLS für die Verbindung und die Consul-eigenen Verschlüsselungsfunktionen aktivieren:


      terraform {
        backend "consul" {
          address      = "consul.example.com:8500"
          access_token = var.consul_acl_token  
          scheme       = "https"
          path         = "terraform/network/global"
          ca_file      = "/etc/ssl/certs/ca.pem"
          cert_file    = "/etc/ssl/certs/cert.pem"
          key_file     = "/etc/ssl/private/key.pem"   
        }
      }

       Zugriffssteuerung

      In Terraform Cloud/Enterprise bietet die Benutzerverwaltung detaillierte Rollen und Berechtigungen. Zum Beispiel können unterschiedliche Rollen verschiedene Berechtigungen haben:

      • Team Network-Admins: Vollzugriff auf Workspace network-global.
      • Team App-Developers: Lesezugriff auf Workspace network-global.

      In Consul nutzen Sie ACLs, welche Sie einer Rolle zuweisen. Hier zum Beispiel die ACL, welche der dem Team App-Developers die Leseberechtigung erteilt:


      # Consul ACL policy
      key_prefix "terraform/network/global" {
        policy = "read"
      }

      In produktiven Umgebungen sollten Sie zusätzlich ein ACL-Token verwenden, um unbefugten Zugriff durch andere Clients zu verhindern:


       data "terraform_remote_state" "global_network" {
        backend = "consul"
        config = {
          address = "consul.example.com:8500"
          path    = "terraform/network/global"
          scheme  = "https"
          token   = var.consul_acl_token
        }
      }
      

      Versionierung und Wiederherstellung

      Terraform Cloud/Enterprise erstellt nach jeder erfolgreichen Ausführung automatisch Snapshots Ihrer States. Diese Snapshots können genutzt werden, um den Zustand einer Umgebung im Fehlerfall wiederherzustellen. Nutzen Sie hierzu entweder die UI oder das CLI (terraform state pull), um den Versionsverlauf einzusehen und die gewünschte Version wiederherzustellen.

      Bei Consul können hochverfügbare Cluster mit Replikas in anderen Rechenzentren konfiguriert werden, um einem Datenverlust vorzubeugen. Sie können ebenso Snapshots des Raft-Storage erstellen.

       

      Struktur der State-Dateien für optimales Multi-Tenancy-Management

      Die Organisation von States sollte Ihre organisatorische Struktur widerspiegeln und gleichzeitig Informationsflüsse unterstützen.

      In Terraform Cloud/Enterprise werden verschiedene Konfigurationen als „Workspaces“ organisiert. In Consul erfolgt die Trennung über „Paths“, die einem logischen Namespace entsprechen.

      Eine bewährte Struktur könnte so aussehen:

      InfrastrukturWorkspace/Path
       Globale notwendige Systeme und Services  global-infrastructure
      Regionale Standorte region-{region_name}
      Kundenspezifische Infrastruktur customer-{customer_name}
      Teamspezifische Infrastruktur team-{team_name}
      Anwendungsspezifische Infrastruktur app-{app_name}

       

      Eine derartige Struktur ermöglicht eine klare Trennung der Verantwortlichkeiten und erleichtert das Auffinden relevanter States.

      In Terraform Cloud/Enterprise können Sie zusätzlich mit Workspace-Tagging arbeiten, um verwandte Workspaces zu gruppieren. Damit können Sie dann zum Beispiel eine logische Verbindung zwischen der Infrastruktur einer Applikation und jener des zugehörigen Teams herstellen.

       

      Code-Beispiele für die Datenvererbung zwischen Tenants

      Die eigentliche Datenvererbung erfolgt über die terraform_remote_state Data Source, die Zugriff auf Outputs aus anderen Terraform-Konfigurationen ermöglicht.

      Beispiel: Netzwerkkonfiguration in einer Multi-Tenant-Umgebung

      Globales Netzwerkteam (in Workspace global-infrastructure):


      module "global_network" {
        source = "./modules/network"
      
        name            = "global-vcn"
        description     = "Global customer network"
        cidr_block      = "10.0.0.0/16"
        parent_id       = var.root_compartment_id
      }
      
      output "global_vcn_id" {
        value = module.global_network.id
      }
      

      Regionales Team (in Workspace region-dc1):


      data "terraform_remote_state" "global_network" {
        backend = "remote"
        config = {
          organization = "ict-technology"
          workspaces = {
            name = "network-global"
          }
        }
      }
      
      module "regional_network" {
        source = "./modules/network"
      
        name            = "regional-subnet-dc1"
        description     = "Subnet of europe region"
        parent_id       = data.terraform_remote_state.global_network.outputs.global_vcn_id
        cidr_block      = "10.0.1.0/24"
      }
      
      output "regional_subnet_id" {
        value = module.regional_network.id
      }

       Kundenteam (in Workspace customer-acme):


      data "terraform_remote_state" "regional_network" {
        backend = "remote"
        config = {
          organization = "ict-technology"
          workspaces = {
            name = "region-europe"
          }
        }
      }
      
      module "customer_environment" {
        source = "./modules/environment"
      
        name            = "customer-instance"
        description     = "customer instance"
        parent_id       = var.customer_compartment_id
        subnet_id       = data.terraform_remote_state.regional_network.outputs.regional_subnet_id
        instance_shape  = "VM.Standard2.1"
      }
      

       Ein besonders empfehlenswertes Muster ist die Verwendung strukturierter Outputs. Statt viele einzelne Werte bereitzustellen, kann ein strukturierter Output verschiedene Informationen gebündelt exportieren:


      output "network_config" {
        value = {
          vcn_id     = module.global_network.id
          cidr_block = module.global_network.cidr_block
          dns_label  = module.global_network.dns_label
          subnets    = {
            public  = module.subnet_public.id
            private = module.subnet_private.id
          }
        }
      }
      

      Damit kann eine abhängige Konfiguration gezielt auf die relevanten Informationen zugreifen:


      module "app_server" {
        source = "./modules/compute"
      
        name            = "app-server"
        description     = "application server in private subnet"
        parent_id       = var.customer_compartment_id
        subnet_id       = data.terraform_remote_state.network.outputs.network_config.subnets.private
        instance_shape  = "VM.Standard2.1"
      }
      
      

        Im nächsten Kapitel schauen wir uns Fallstricke, Best Practices und einige Tricks beim Umgang mit Remote States an.