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

      Terraform @ Scale - Part 1b: Multi-Tenancy Architekturbeispiel für modulare Cloud-Infrastrukturen

      Im vorherigen Teil dieser Reihe haben wir die Grundlagen des Remote-State-Konzepts in Terraform erläutert und wie es zur Informationsvererbung in Multi-Tenancy-Umgebungen genutzt werden kann. Nun wollen wir dies anhand eines konkreten Architekturbeispiels veranschaulichen.

      Einleitung

      Eine effektive Multi-Tenancy-Architektur sollte der organisatorischen Struktur eines Unternehmens folgen, während sie gleichzeitig Cloud-Best-Practices berücksichtigt. Moderne Cloud-Plattformen bieten verschiedene Möglichkeiten, um hierarchische Organisationsstrukturen abzubilden - sei es durch Compartments (OCI), Organizations und Accounts (AWS), Ressourcengruppen (Azure) oder Projekte und Ordner (GCP).

      Vorstellung des Architekturkonzepts

      Datacenter View DE

      In unserem Architekturbeispiel sehen wir ein "Globales Datacenter des Kunden" als oberste Organisationseinheit. Innerhalb dieser Einheit gibt es verschiedene Bereiche:

      Globale Systeme und Services: Diese umfassen IAM, Monitoring, Billing und andere zentrale Dienste.
      Regionale Datacenter: Geografisch verteilte Infrastrukturen für unterschiedliche Anforderungen.
      Organisatorische Unterteilungen: Beispielsweise nach Abteilungen, Sicherheitszonen oder anderen Kriterien.

      Das Besondere an diesem Modell: Jede Organisationseinheit repräsentiert einen eigenen logischen Bereich mit definierter Verantwortlichkeit. Diese bilden natürliche Grenzen zwischen verschiedenen Mandanten oder Organisationseinheiten und bieten gleichzeitig einen Rahmen für die Vererbung von Berechtigungen und Konfigurationen.

      Hierarchische Organisation als natürliches Multi-Tenancy-Modell

      Die hierarchische Strukturierung der Cloud-Infrastruktur bietet mehrere Vorteile für Multi-Tenancy-Szenarien:

      Hierarchische Organisation: Einheiten können verschachtelt werden, ähnlich einer Ordnerstruktur.
      Vererbung von Richtlinien: Berechtigungen werden von höheren zu niedrigeren Ebenen vererbt.
      Ressourcenmanagement: Limits können pro Organisationseinheit definiert werden.
      Kostenerfassung: Billing und Metering erfolgen auf Ebene der Organisationseinheiten.

      Ein Beispiel: Ein Unternehmen könnte eine Top-Level-Einheit für jede Geschäftseinheit haben. Innerhalb dieser Einheiten könnten weitere Untereinheiten für Produktion, Entwicklung und Test existieren. Auf der untersten Ebene könnten dann projektspezifische Einheiten folgen.
      Diese Hierarchie spiegelt die organisatorische Struktur wider und erleichtert gleichzeitig die Verwaltung von Zugriffsrechten und Ressourcen. So können beispielsweise Administratoren Zugriff auf eine bestimmte Department-Einheit erhalten, ohne Zugriff auf andere Departments zu haben.

      Unterschiedliche Segmentierungsebenen

      Wir können unsere Infrastruktur entlang verschiedener Dimensionen segmentieren:

      Technische Segmentierung: Beispielsweise "Standort A regionales Datacenter" mit verschiedenen technischen Zonen (DC1, DC2, DC3, DC4).
      Organisatorische Segmentierung: Beispielsweise "Standort B regionales Datacenter" mit verschiedenen Abteilungen (Abt. 1, Abt. 2, usw.).
      Sicherheitsbereiche: Beispielsweise "Standort n regionales Datacenter" mit Hochsicherheits-, Medium-, Low- und Public-Zonen.
      Value Streams: Geschäftsprozesse, die quer zu anderen Strukturen liegen können.

      Diese verschiedenen Segmentierungsdimensionen können sich überlappen und ergänzen. Die Herausforderung besteht darin, sie in Terraform sauber abzubilden und gleichzeitig Informationsflüsse zwischen ihnen zu ermöglichen.
      Besonders wichtig ist die Vererbung von Daten mittels Remote State. Hier sehen wir, wie Informationen von höheren Ebenen (Globales Datacenter) zu niedrigeren Ebenen (regionale Datacenter und deren Untereinheiten) fließen. Dies entspricht genau dem Remote-State-Pattern, das wir im vorigen Abschnitt besprochen haben.

      Praktische Umsetzung mithilfe von Terraform-Modulen

      Die Umsetzung einer solchen Architektur in Terraform erfordert einen strukturierten Modulansatz. Für jede Ebene der Hierarchie erstellen wir dedizierte Terraform-Projekte.

      Hinweis: Die genaue Konfiguration der Remote-State-Data-Source variiert je nach verwendetem Backend. Für gängige Optionen wie S3, GCS oder Consul sind entsprechende Anpassungen erforderlich.

      1. Root-Modul für globale Infrastruktur:

      Glabl Services DE


      # 1. Globale Infrastruktur (Root-Ebene)
      module "global_datacenter" {
        source = "./modules/organization"
        
        name        = "global-datacenter-customer"
        description = "Global Datacenter of the Customer"
        parent_id   = var.root_organization_id
      }
      
      module "global_network" {
        source = "./modules/network"
        
        organization_id = module.global_datacenter.id
        cidr_block      = "10.0.0.0/16"
        name            = "global-network"
      }
      
      # Globale notwendige Systeme und Services
      module "global_services" {
        source = "./modules/organization"
        
        name        = "global-services"
        description = "Required Global Systems and Services the Customer Needs"
        parent_id   = module.global_datacenter.id
      }
      
      # IAM, Monitoring, Billing-Services Module würden hier implementiert werden...
      
      output "global_datacenter_id" {
        value = module.global_datacenter.id
      }
      
      output "global_network_id" {
        value = module.global_network.id
      }
      
      output "global_services_id" {
        value = module.global_services.id
      }

      2. Regionale Infrastruktur nach technischer Segmentierung:


      # 2. Regional Datacenter in Location A (Multiple DCs)
      data "terraform_remote_state" "global" {
        backend = "remote"
        config = {
          organization = "my-org"
          workspaces = {
            name = "global-infrastructure"
          }
        }
      }
      
      module "location_a" {
        source = "./modules/organization"
        
        name        = "location-a-regionales-datacenter"
        description = "Regionales Datacenter Location A"
        parent_id   = data.terraform_remote_state.global.outputs.global_datacenter_id
      }
      
      # Provisioning the 4 DCs as Subunits
      module "dc1" {
        source = "./modules/organization"
        
        name        = "dc1"
        description = "Datacenter 1"
        parent_id   = module.location_a.id
      }
      
      module "dc2" {
        source = "./modules/organization"
        
        name        = "dc2"
        description = "Datacenter 2"
        parent_id   = module.location_a.id
      }
      
      module "dc3" {
        source = "./modules/organization"
        
        name        = "dc3"
        description = "Datacenter 3"
        parent_id   = module.location_a.id
      }
      
      module "dc4" {
        source = "./modules/organization"
        
        name        = "dc4"
        description = "Datacenter 4"
        parent_id   = module.location_a.id
      }
      
      # Subnets of the global Network for Location A
      module "subnet_dc1" {
        source = "./modules/subnet"
        
        organization_id = module.dc1.id
        network_id      = data.terraform_remote_state.global.outputs.global_network_id
        cidr_block      = "10.0.1.0/24"
        name            = "subnet-dc1"
      }
      
      # More Subnets for DC2, DC3, DC4...
      
      # Outputs for use in other Terraform Projects
      output "location_a_id" {
        value = module.location_a.id
      }
      
      output "dc1_id" {
        value = module.dc1.id
      }
      
      output "subnet_dc1_id" {
        value = module.subnet_dc1.id
      }
      
      # Additional Outputs

      3. Regionale Infrastruktur nach organisatorischer Segmentierung:


      # 3. Regional Datacenter for Location B (Organigramm)
      data "terraform_remote_state" "global" {
        backend = "remote"
        config = {
          organization = "my-org"
          workspaces = {
            name = "global-infrastructure"
          }
        }
      }
      
      module "location_b" {
        source = "./modules/organization"
        
        name        = "location-b-regional-datacenter"
        description = "Regional Datacenter Location B"
        parent_id   = data.terraform_remote_state.global.outputs.global_datacenter_id
      }
      
      # Organisatorische Abteilungen
      module "department_1" {
        source = "./modules/organization"
        
        name        = "department-1"
        description = "Department 1"
        parent_id   = module.location_b.id
      }
      
      module "department_2" {
        source = "./modules/organization"
        
        name        = "department-2"
        description = "Department 2"
        parent_id   = module.location_b.id
      }
      
      module "department_3" {
        source = "./modules/organization"
        
        name        = "department-3"
        description = "Department 3"
        parent_id   = module.location_b.id
      }
      
      module "department_4" {
        source = "./modules/organization"
        
        name        = "department-4"
        description = "Department 4"
        parent_id   = module.location_b.id
      }
      
      # Depmartment-Specific Subnets
      module "subnet_department_1" {
        source = "./modules/subnet"
        
        organization_id = module.department_1.id
        network_id      = data.terraform_remote_state.global.outputs.global_network_id
        cidr_block      = "10.0.10.0/24"
        name            = "subnet-department-1"
      }
      
      # Outputs für weitere Terraform-Projekte
      output "location_b_id" {
        value = module.location_b.id
      }
      
      output "department_1_id" {
        value = module.department_1.id
      }
      
      output "subnet_department_1_id" {
        value = module.subnet_department_1.id
      }

      4. Regionale Infrastruktur nach Sicherheitssegmentierung:


      # 4. Regional Datacenter for Location n (Security Zones)
      data "terraform_remote_state" "global" {
        backend = "remote"
        config = {
          organization = "my-org"
          workspaces = {
            name = "global-infrastructure"
          }
        }
      }
      
      module "location_n" {
        source = "./modules/organization"
        
        name        = "location-n-regional-datacenter"
        description = "Regional Datacenter Location n"
        parent_id   = data.terraform_remote_state.global.outputs.global_datacenter_id
      }
      
      # Sicherheitszonen als Untereinheiten
      module "high" {
        source = "./modules/organization"
        
        name        = "high"
        description = "High Security Zone"
        parent_id   = module.location_n.id
      }
      
      module "med" {
        source = "./modules/organization"
        
        name        = "med"
        description = "Medium Security Zone"
        parent_id   = module.location_n.id
      }
      
      module "low" {
        source = "./modules/organization"
        
        name        = "low"
        description = "Low Security Zone"
        parent_id   = module.location_n.id
      }
      
      module "pub" {
        source = "./modules/organization"
        
        name        = "pub"
        description = "Public Zone"
        parent_id   = module.location_n.id
      }
      
      # Sicherheitszonen-spezifische Subnets
      module "subnet_high" {
        source = "./modules/subnet"
        
        organization_id       = module.high.id
        network_id            = data.terraform_remote_state.global.outputs.global_network_id
        cidr_block            = "10.0.20.0/24"
        name                  = "subnet-high"
        prohibit_public_ip    = true  
      }
      
      # Additional Security Resources like Security Lists and Network Security Groups...
      
      # Outputs
      output "location_n_id" {
        value = module.location_n.id
      }
      
      output "high_security_id" {
        value = module.high.id
      }
      
      output "subnet_high_id" {
        value = module.subnet_high.id
      }

      5. Department- oder Team-spezifische Infrastruktur:


      # 5. Example of resources in a department, which uses remote state information
      data "terraform_remote_state" "standort_b" {
        backend = "remote"
        config = {
          organization = "my-org"
          workspaces = {
            name = "regional-location-b"
          }
        }
      }
      
      module "vm_instances" {
        source = "./modules/compute"
        
        instances = {
          app_server_department_1 = {
            organization_id = data.terraform_remote_state.location_b.outputs.department_1_id
            subnet_id       = data.terraform_remote_state.location_b.outputs.subnet_department_1_id
            size            = "medium"
            image           = var.app_image_id
          }
          # Additional VMs ...
        }
      }

      Dynamische Modulkonfiguration für flexible Deployment-Patterns

      Um die Flexibilität weiter zu erhöhen, können wir auch mit dynamischen Konfigurationen arbeiten. Dies ist besonders nützlich, wenn wir eine Vielzahl ähnlicher Ressourcen verwalten müssen:


      module "regional_datacenters" {
        source   = "./modules/regional_datacenter"
        for_each = var.datacenter_configs
        
        name            = each.key
        description     = each.value["description"]
        parent_id       = data.terraform_remote_state.global.outputs.global_datacenter_id
        network_cidr    = each.value["network_cidr"]
        security_level  = each.value["security_level"]
        subunits        = each.value["subunits"]
      }
      
      # Usage example, how to call this module from a root module
      # # datacenter_configs = { # "europe-west" = { # description = "European Datacenter West Region" # network_cidr = "10.1.0.0/16" # security_level = "high" # subunits = ["prod", "staging", "dev", "test"] # }, # "us-east" = { # description = "US East Coast Datacenter" # network_cidr = "10.2.0.0/16" # security_level = "medium" # subunits = ["prod", "dev"] # } # }

      Vorteile der modularen Struktur


      Diese modulare Struktur ermöglicht es uns, die logische Hierarchie unserer Organisation in separate Terraform-Projekte zu übersetzen, die dennoch Informationen miteinander austauschen können:

      Klare Verantwortlichkeiten: Jedes Team verwaltet seinen eigenen Terraform-Code und seine eigenen States.
      Automatische Informationsvererbung: Das Team, das die globale Infrastruktur verwaltet, kann Änderungen vornehmen, ohne dass die Teams auf regionaler oder Department-Ebene ihren Code anpassen müssen.
      Isolierte Deployments: Fehler in einem Bereich beeinflussen nicht automatisch andere Bereiche.
      Skalierbarkeit: Neue Organisationseinheiten können einfach hinzugefügt werden, ohne bestehende Strukturen zu verändern.
      Konsistente Governance: Durch die Vererbung von Outputs werden konsistente Konfigurationen über alle Ebenen hinweg sichergestellt.

      Beispiel für ein vollständiges Multi-Tenant-Setup

      Hier ein Beispiel, wie diese Muster in einem realen Projekt kombiniert werden könnten:


      module "tenant_environments" {
        source = "./modules/tenant_environment"
        
        for_each = {
          for tenant in var.tenants : tenant.name => tenant
        }
        
        name            = each.key
        parent_id       = module.tenant_root_organization.id
        environments    = each.value["environments"]
        network_cidr    = each.value["network_cidr"]
        security_policy = each.value["security_policy"]
      }
      
      # The tenant_environments may then be used for different customers, 
      # business units or projects.
      # 
      # Example:
      # tenants = [
      #   {
      #     name = "finance-department"
      #     environments = ["prod", "dev", "test"]
      #     network_cidr = "10.10.0.0/16"
      #     security_policy = "high"
      #   },
      #   {
      #     name = "marketing-department"
      #     environments = ["prod", "staging"]
      #     network_cidr = "10.20.0.0/16"
      #     security_policy = "medium"
      #   }
      # ]


      Mit dieser strukturierten Herangehensweise können wir komplexe Multi-Tenancy-Umgebungen in einer Weise verwalten, die sowohl technisch robust als auch organisatorisch sinnvoll ist. Die klare Trennung von Verantwortlichkeiten, kombiniert mit der gezielten Informationsvererbung über Remote States, ermöglicht es uns, auch in großen, verteilten Teams effizient mit Terraform zu arbeiten.

      Sicherheitsüberlegungen bei Remote-State-Informationen

      So hilfreich Remote States auch sind, sie können potenziell sensible Informationen enthalten. Besonders in Multi-Tenancy-Umgebungen ist der Schutz dieser Informationen essenziell.
      Um sicherzustellen, dass nur berechtigte Teams auf Remote-State-Outputs zugreifen können, sollten Sie folgende Maßnahmen umsetzen:

      • Beschränkung des Zugriffs: Nutzen Sie die Zugriffskontrollen Ihres Backends (z.B. IAM-Richtlinien für S3, ACLs für Consul oder rollenbasierte Zugriffssteuerungen in Terraform Cloud/Enterprise).
      • Minimierung von Outputs: Definieren Sie nur die unbedingt erforderlichen Informationen als Output. Jedes zusätzliche Output-Attribut erhöht das Risiko der Offenlegung sensibler Daten.
      • Vermeidung unnötiger Outputs: Outputs mit sicherheitskritischen Werten (z.B. Passwörter, Private Keys) sollten möglichst nicht als Output bereitgestellt, sondern über Geheimnisverwaltungsdienste wie HashiCorp Vault oder Secrets Manager verwaltet werden.
      • Zusammenfassung von Informationen: Statt einzelne Datenbank-Passwörter, API-Keys oder andere sensible Details als Output bereitzustellen, sollten Sie bevorzugt strukturierte Outputs verwenden, die ausschließlich die relevanten Parameter bündeln.
      • Verwendung von Proxy-Statefiles: Filtern Sie die Outputs eines Remote States nach nur für den jeweiligen Tenant relevanten Informationen und stellen Sie diese als neues State bereit. Der Tenant darf dann nur auf das Proxy-State Zugriff erhalten.

      Fehlerbehandlung bei Remote-State-Abhängigkeiten

      Die Nutzung von Remote States in Multi-Tenancy-Architekturen bringt besondere Herausforderungen bei der Fehlerbehandlung mit sich. Fehlerquellen wie nicht verfügbare Backends, inkonsistente Outputs oder unerwartete Änderungen können zu erheblichen Problemen führen. Um diese Risiken zu minimieren, sollten Sie folgende Maßnahmen implementieren:

      • Verfügbarkeitsprüfungen: Nutzen Sie Terraform's terraform_remote_state-Block in Kombination mit Abfragekonstruktionen wie lookup() zur Abmilderung von Fehlern bei fehlenden Outputs.
      • Prüfung auf korrekte Outputs: Ergänzen Sie Tests in ihren Modulen und CI/CD-Pipelines, um sicherzustellen, dass Remote-State-Outputs stets die erwarteten Strukturen und Inhalte enthalten.
      • Fallback-Mechanismen: Falls Remote States temporär nicht erreichbar sind, kann die Nutzung von lokal gecachten Werten (etwa durch Umgebungsvariablen) als temporäre Lösung dienen.

      Beispiel für eine robuste Remote-State-Abfrage mit Fehlerbehandlung:


      data "terraform_remote_state" "network" {
        backend = "remote"
        config = {
          organization = "my-org"
          workspaces = {
            name = "global-infrastructure"
          }
        }
      }
      
      locals {
        private_subnet_id = lookup(data.terraform_remote_state.network.outputs.subnets, "private", null)
      }
      
      resource "example_vm" "app_server" {
        subnet_id = local.private_subnet_id != null ? local.private_subnet_id : var.default_subnet_id
      }

      Im nächsten Abschnitt werfen wir einen Blick auf Remote Backends und schauen uns an, wie man Remote States am besten hierarchisch organisiert.