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

      Terraform @ Scale - Parte 1b: Esempio di architettura Multi-Tenancy per infrastrutture cloud modulari

      Nel precedente articolo di questa serie, abbiamo spiegato i fondamenti del concetto di Remote State in Terraform e come questo possa essere utilizzato per l'ereditarietà delle informazioni in ambienti Multi-Tenancy. Ora vogliamo illustrare questo concetto attraverso un esempio concreto di architettura.

      Introduzione

      Un'architettura Multi-Tenancy efficace dovrebbe seguire la struttura organizzativa di un'azienda, tenendo al contempo conto delle best practice del cloud. Le moderne piattaforme cloud offrono diverse possibilità per rappresentare strutture organizzative gerarchiche - che si tratti di Compartments (OCI), Organizations e Accounts (AWS), gruppi di risorse (Azure) o progetti e cartelle (GCP).

      Presentazione del concetto architetturale

      Datacenter View

      Nel nostro esempio di architettura vediamo un "Datacenter globale del cliente" come unità organizzativa principale. All'interno di questa unità esistono diverse aree:

      Sistemi e servizi globali: Questi includono IAM, monitoraggio, billing e altri servizi centrali.
      Datacenter regionali: Infrastrutture distribuite geograficamente per esigenze diverse.
      Sottodivisioni organizzative: Ad esempio, per dipartimenti, zone di sicurezza o altri criteri.

      La particolarità di questo modello risiede nel fatto che ogni unità organizzativa rappresenta un'area logica separata con responsabilità definite. Queste unità creano confini naturali tra diversi tenant o unità organizzative e, al contempo, forniscono un quadro per l'ereditarietà delle autorizzazioni e delle configurazioni.

      Organizzazione gerarchica come modello Multi-Tenancy naturale

      La strutturazione gerarchica dell'infrastruttura cloud offre diversi vantaggi per scenari Multi-Tenancy:

      Organizzazione gerarchica: Le unità possono essere annidate, analogamente a una struttura a cartelle.
      Ereditarietà delle policy: Le autorizzazioni vengono ereditate dai livelli superiori a quelli inferiori.
      Gestione delle risorse: I limiti possono essere definiti per ciascuna unità organizzativa.
      Rilevazione dei costi: La fatturazione e il metering avvengono a livello delle unità organizzative.

      Un esempio: un'azienda potrebbe avere un'unità di livello superiore per ciascuna unità aziendale. All'interno di queste unità potrebbero esistere ulteriori sottounità per produzione, sviluppo e test. Al livello più basso potrebbero seguire unità specifiche per progetto.
      Questa gerarchia riflette la struttura organizzativa e facilita al contempo la gestione dei diritti di accesso e delle risorse. Ad esempio, gli amministratori possono ottenere accesso a una determinata unità dipartimentale senza avere accesso ad altri dipartimenti.

      Differenti livelli di segmentazione

      Possiamo segmentare la nostra infrastruttura lungo diverse dimensioni:

      Segmentazione tecnica: Ad esempio, "Datacenter regionale Sede A" con diverse zone tecniche (DC1, DC2, DC3, DC4).
      Segmentazione organizzativa: Ad esempio, "Datacenter regionale Sede B" con diversi dipartimenti (Dip. 1, Dip. 2, ecc.).
      Aree di sicurezza: Ad esempio, "Datacenter regionale Sede n" con zone ad alta sicurezza, media, bassa e pubblica.
      Value Streams: Processi aziendali che possono attraversare altre strutture.

      Queste diverse dimensioni di segmentazione possono sovrapporsi e completarsi a vicenda. La sfida consiste nel rappresentarle in Terraform in modo pulito e, allo stesso tempo, consentire il flusso di informazioni tra di esse.
      Particolarmente importante è l'ereditarietà dei dati mediante Remote State. Qui possiamo vedere come le informazioni fluiscono dai livelli superiori (Datacenter globale) ai livelli inferiori (datacenter regionali e relative sottounità). Questo corrisponde esattamente al pattern del Remote State discusso nella sezione precedente.

      Implementazione pratica tramite moduli Terraform

      L'implementazione di un'architettura di questo tipo in Terraform richiede un approccio strutturato ai moduli. Per ciascun livello della gerarchia creiamo progetti Terraform dedicati.

      Nota: La configurazione precisa della Remote-State-Data-Source varia a seconda del backend utilizzato. Per le opzioni più comuni come S3, GCS o Consul, sono richieste opportune personalizzazioni.

      1. Modulo root per l'infrastruttura globale:

      Glabl Services DE


      # 1. Infrastruttura globale (livello root)
      module "global_datacenter" {
        source = "./modules/organization"
        
        name        = "global-datacenter-customer"
        description = "Datacenter globale del cliente"
        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"
      }
      
      # Sistemi e servizi globali necessari
      module "global_services" {
        source = "./modules/organization"
        
        name        = "global-services"
        description = "Sistemi e servizi globali richiesti per il cliente"
        parent_id   = module.global_datacenter.id
      }
      
      # I moduli per IAM, Monitoraggio e Servizi di Fatturazione verrebbero implementati qui...
      
      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. Infrastruttura regionale secondo segmentazione tecnica:

      Rrgional Datacenter IT


      # 2. Datacenter regionale nella Sede A (Molteplici DC)
      data "terraform_remote_state" "global" {
        backend = "remote"
        config = {
          organization = "my-org"
          workspaces = {
            name = "global-infrastructure"
          }
        }
      }
      
      module "location_a" {
        source = "./modules/organization"
        
        name        = "location-a-datacenter-regionale"
        description = "Datacenter regionale Sede A"
        parent_id   = data.terraform_remote_state.global.outputs.global_datacenter_id
      }
      
      # Provisioning dei 4 DC come sotto-unità
      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
      }
      
      # Subnet della rete globale per la Sede 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"
      }
      
      # Altre subnet per DC2, DC3, DC4...
      
      # Outputs per l'utilizzo in altri progetti Terraform
      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
      }
      
      # Ulteriori Outputs

      3. Infrastruttura regionale secondo segmentazione organizzativa:

      Regional Organizations IT


      # 3. Datacenter regionale per la Sede B (Organigramma)
      data "terraform_remote_state" "global" {
        backend = "remote"
        config = {
          organization = "my-org"
          workspaces = {
            name = "global-infrastructure"
          }
        }
      }
      
      module "location_b" {
        source = "./modules/organization"
        
        name        = "location-b-datacenter-regionale"
        description = "Datacenter regionale Sede B"
        parent_id   = data.terraform_remote_state.global.outputs.global_datacenter_id
      }
      
      # Dipartimenti organizzativi
      module "department_1" {
        source = "./modules/organization"
        
        name        = "department-1"
        description = "Dipartimento 1"
        parent_id   = module.location_b.id
      }
      
      module "department_2" {
        source = "./modules/organization"
        
        name        = "department-2"
        description = "Dipartimento 2"
        parent_id   = module.location_b.id
      }
      
      module "department_3" {
        source = "./modules/organization"
        
        name        = "department-3"
        description = "Dipartimento 3"
        parent_id   = module.location_b.id
      }
      
      module "department_4" {
        source = "./modules/organization"
        
        name        = "department-4"
        description = "Dipartimento 4"
        parent_id   = module.location_b.id
      }
      
      # Subnet specifiche per i dipartimenti
      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 per ulteriori progetti Terraform
      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. Infrastruttura regionale secondo segmentazione della sicurezza:

      REgional Security Zones IT


      # 4. Datacenter regionale per la Sede n (Zone di sicurezza)
      data "terraform_remote_state" "global" {
        backend = "remote"
        config = {
          organization = "my-org"
          workspaces = {
            name = "global-infrastructure"
          }
        }
      }
      
      module "location_n" {
        source = "./modules/organization"
        
        name        = "location-n-datacenter-regionale"
        description = "Datacenter regionale Sede n"
        parent_id   = data.terraform_remote_state.global.outputs.global_datacenter_id
      }
      
      # Zone di sicurezza come sotto-unità
      module "high" {
        source = "./modules/organization"
        
        name        = "high"
        description = "Zona ad alta sicurezza"
        parent_id   = module.location_n.id
      }
      
      module "med" {
        source = "./modules/organization"
        
        name        = "med"
        description = "Zona a media sicurezza"
        parent_id   = module.location_n.id
      }
      
      module "low" {
        source = "./modules/organization"
        
        name        = "low"
        description = "Zona a bassa sicurezza"
        parent_id   = module.location_n.id
      }
      
      module "pub" {
        source = "./modules/organization"
        
        name        = "pub"
        description = "Zona pubblica"
        parent_id   = module.location_n.id
      }
      
      # Subnet specifiche per le zone di sicurezza
      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  
      }
      
      # Risorse di sicurezza aggiuntive come Security Lists e 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. Infrastruttura specifica per dipartimenti o team:

      Department Datacenter IT


      # 5. Esempio di risorse in un dipartimento che utilizza informazioni da remote state
      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
          }
          # Ulteriori VM ...
        }
      }

      Configurazione dinamica dei moduli per modelli di deployment flessibili

      Per aumentare ulteriormente la flessibilità, possiamo anche lavorare con configurazioni dinamiche. Questo è particolarmente utile quando dobbiamo gestire una varietà di risorse simili:


      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"]
      }
      
      # Esempio d'uso, come richiamare questo modulo da un modulo root
      # # datacenter_configs = { # "europe-west" = { # description = "Datacenter europeo nella regione ovest" # network_cidr = "10.1.0.0/16" # security_level = "high" # subunits = ["prod", "staging", "dev", "test"] # }, # "us-east" = { # description = "Datacenter della costa est degli Stati Uniti" # network_cidr = "10.2.0.0/16" # security_level = "medium" # subunits = ["prod", "dev"] # } # }

      Vantaggi della struttura modulare


      Questa struttura modulare ci consente di tradurre la gerarchia logica della nostra organizzazione in progetti Terraform separati, che tuttavia possono scambiarsi informazioni tra loro:

      Responsabilità chiare: Ogni team gestisce il proprio codice Terraform e i propri States.
      Ereditarietà automatica delle informazioni: Il team che gestisce l'infrastruttura globale può apportare modifiche senza che i team a livello regionale o dipartimentale debbano adattare il loro codice.
      Deployment isolati: Gli errori in una determinata area non influenzano automaticamente le altre.
      Scalabilità: È possibile aggiungere facilmente nuove unità organizzative senza modificare le strutture esistenti.
      Governance coerente: Tramite l'ereditarietà degli Outputs si garantiscono configurazioni coerenti a tutti i livelli.

      Esempio di una configurazione Multi-Tenant completa

      Ecco un esempio di come questi modelli possono essere combinati in un progetto reale:


      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"]
      }
      
      # Il modulo tenant_environments può quindi essere utilizzato per diversi clienti,
      # unità aziendali o progetti.
      # 
      # Esempio:
      # 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"
      #   }
      # ]


      Con questo approccio strutturato, possiamo gestire ambienti Multi-Tenancy complessi in modo tecnicamente solido e organizzativamente sensato. La chiara separazione delle responsabilità, combinata con l'ereditarietà mirata delle informazioni tramite Remote States, ci consente di lavorare in modo efficiente con Terraform anche in team distribuiti di grandi dimensioni.

      Considerazioni sulla sicurezza delle informazioni nei Remote States

      Per quanto utili siano i Remote States, possono contenere informazioni potenzialmente sensibili. La protezione di queste informazioni è particolarmente essenziale negli ambienti Multi-Tenancy.
      Per garantire che solo i team autorizzati possano accedere agli Outputs dei Remote States, è necessario implementare le seguenti misure:

      • Limitazione dell'accesso: Utilizzate i controlli di accesso del vostro backend (ad es. policy IAM per S3, ACL per Consul o controlli di accesso basati sui ruoli in Terraform Cloud/Enterprise).
      • Minimizzazione degli Outputs: Definite solo le informazioni strettamente necessarie come Output. Ogni attributo aggiuntivo negli Outputs aumenta il rischio di esposizione di dati sensibili.
      • Evitare Outputs non necessari: Gli Outputs contenenti valori critici per la sicurezza (ad es. password, private keys) non dovrebbero essere esposti come Output, ma gestiti tramite servizi di gestione dei segreti come HashiCorp Vault o Secrets Manager.
      • Raggruppamento delle informazioni: Invece di fornire singolarmente password di database, API Keys o altri dettagli sensibili come Output, è preferibile utilizzare Outputs strutturati che raggruppino esclusivamente i parametri rilevanti.
      • Utilizzo di Proxy-Statefiles: Filtrate gli Outputs di un Remote State includendo solo le informazioni pertinenti per il rispettivo tenant e fornite queste informazioni in un nuovo State. Il tenant deve avere accesso esclusivamente a questo Proxy-State.

      Gestione degli errori nelle dipendenze dei Remote State

      L'utilizzo dei Remote States nelle architetture Multi-Tenancy comporta sfide particolari nella gestione degli errori. Fonti di errore come backend non disponibili, Outputs incoerenti o modifiche inattese possono portare a problemi significativi. Per ridurre al minimo questi rischi, è opportuno implementare le seguenti misure:

      • Verifica della disponibilità: Utilizzate il blocco terraform_remote_state di Terraform in combinazione con costrutti di query come lookup() per mitigare errori in caso di Outputs mancanti.
      • Verifica della correttezza degli Outputs: Aggiungete test nei vostri moduli e pipeline CI/CD per garantire che i Remote-State-Outputs contengano sempre le strutture e i contenuti previsti.
      • Meccanismi di fallback: Se i Remote States non sono temporaneamente disponibili, l'utilizzo di valori memorizzati in cache localmente (ad esempio tramite variabili d'ambiente) può fungere da soluzione temporanea.

      Esempio di una query Remote-State robusta con gestione degli errori:


      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
      }

      Nella prossima sezione daremo uno sguardo ai Remote Backends e vedremo come organizzare al meglio i Remote States in modo gerarchico.