Best practice per l'utilizzo di Terraform

Mantieni tutto organizzato con le raccolte Salva e classifica i contenuti in base alle tue preferenze.

Questo documento fornisce linee guida e consigli per uno sviluppo efficace con Terraform tra più membri del team e flussi di lavoro.

Questa guida non costituisce un'introduzione a Terraform. Per un'introduzione all'utilizzo di Terraform con Google Cloud, consulta la guida introduttiva all'utilizzo di Terraform.

Linee guida generali su stile e struttura

I seguenti suggerimenti illustrano lo stile e la struttura di base per le tue configurazioni Terraform. I suggerimenti si applicano ai moduli Terraform riutilizzabili e alle configurazioni principali.

Seguire una struttura di moduli standard

  • I moduli Terraform devono seguire la struttura standard del modulo.
  • Inizia ogni modulo con un file main.tf, in cui le risorse si trovano per impostazione predefinita.
  • In ogni modulo, includi un file README.md nel formato Markdown. Nel file README.md, includi la documentazione di base sul modulo.
  • Inserisci gli esempi in una cartella examples/, con una sottodirectory separata per ogni esempio. Per ogni esempio, includi un file README.md dettagliato.
  • Crea raggruppamenti logici di risorse con i propri file e nomi descrittivi, ad esempio network.tf, instances.tf o loadbalancer.tf.
    • Evita di assegnare a ogni risorsa il proprio file. Raggruppare le risorse in base allo scopo condiviso. Ad esempio, combina google_dns_managed_zone e google_dns_record_set in dns.tf.
  • Nella directory radice del modulo, includi solo file di metadati Terraform (*.tf) e repository (come README.md e CHANGELOG.md).
  • Inserisci qualsiasi documentazione aggiuntiva in una sottodirectory docs/.

Utilizzare una convenzione di denominazione

  • Assegna un nome a tutti gli oggetti di configurazione utilizzando i trattini bassi per limitare più parole. Questa pratica garantisce la coerenza con la convenzione di denominazione per i tipi di risorse, i tipi di origini dati e altri valori predefiniti. Questa convenzione non si applica agli argomenti sui nomi.

    Consigliato:

    resource "google_compute_instance" "web_server" {
      name = "web-server"
    }
    

    Sconsigliato:

    resource "google_compute_instance" "web-server" {
      name = "web-server"
    }
    
  • Per semplificare i riferimenti a una risorsa che è l'unica di questo tipo (ad esempio, un singolo bilanciatore del carico per un intero modulo), assegna il nome main alla risorsa.

    • Occorre più impegno mentale per ricordare some_google_resource.my_special_resource.id rispetto a some_google_resource.main.id.
  • Per distinguere le risorse dello stesso tipo tra loro (ad esempio, primary e secondary), fornisci nomi significativi delle risorse.

  • Rendi i nomi delle risorse singolari.

  • Nel nome della risorsa, non ripetere il tipo di risorsa. Ad esempio:

    Consigliato:

    resource "google_compute_global_address" "main" { ... }
    

    Sconsigliato:

    resource "google_compute_global_address" "main_global_address" { … }
    

Utilizzare le variabili con attenzione

  • Dichiara tutte le variabili in variables.tf.
  • Assegna alle variabili nomi descrittivi che siano pertinenti al loro utilizzo o al loro scopo.
    • Gli input, le variabili locali e gli output che rappresentano valori numerici, ad esempio le dimensioni del disco o la RAM, devono essere denominati con unità (come ram_size_gb). Le API di Google Cloud non hanno unità standard, pertanto l'assegnazione di nomi alle variabili rende l'unità di input prevista chiara per i gestori della configurazione.
    • Per le unità di archiviazione, utilizza i prefissi binari (Potenza di 1024) (kilo, mega, giga). Per tutte le altre unità di misura, utilizza i prefissi decimali (potenza di 1000) Questo utilizzo corrisponde all'utilizzo di Google Cloud.
    • Per semplificare la logica condizionale, assegna alle variabili booleane nomi validi (ad esempio, enable_external_access).
  • Le variabili devono avere descrizioni. Le descrizioni vengono incluse automaticamente nella documentazione generata automaticamente di un modulo. Le descrizioni aggiungono un ulteriore contesto ai nuovi sviluppatori che i nomi descrittivi non sono in grado di fornire.
  • Assegna variabili di tipo definito.
  • Se opportuno, fornisci valori predefiniti:
    • Per le variabili che hanno valori indipendenti dall'ambiente (come la dimensione del disco), fornisci valori predefiniti.
    • Per le variabili che hanno valori specifici dell'ambiente (come project_id), non fornire valori predefiniti. In questo modo, il modulo di chiamata deve fornire valori significativi.
  • Utilizza valori predefiniti vuoti per le variabili (ad esempio stringhe o elenchi vuoti) solo quando lasciare la variabile vuota è una preferenza valida che le API sottostanti non rifiutano.
  • Presta attenzione nell'utilizzo delle variabili. Parametri solo dei valori che devono variare per ogni istanza o ambiente. Quando decidi se esporre una variabile, assicurati di avere un caso d'uso concreto per modificarla. Se c'è solo una piccola possibilità che potrebbe essere necessaria una variabile, non esporla.
    • L'aggiunta di una variabile con un valore predefinito è compatibile con le versioni precedenti.
    • La rimozione di una variabile non è compatibile con le versioni precedenti.
    • Se un valore letterale viene riutilizzato in più posizioni, puoi utilizzare un valore locale senza esporlo come variabile.

Esponi output

  • Organizza tutti gli output in un file outputs.tf.
  • Fornisci descrizioni significative per tutti gli output.
  • Documenta le descrizioni degli output nel file README.md. Genera automaticamente descrizioni all'interno di commit con strumenti come terraform-docs.
  • Output di tutti i valori utili a cui i moduli radice potrebbero dover fare riferimento o che possono essere condivisi. In particolare per i moduli open source o utilizzati in modo intensivo, esporre tutti gli output che potrebbero potenzialmente consumare.
  • Non passare gli output direttamente attraverso le variabili di input, perché così facendo si impedisce che vengano aggiunti correttamente al grafico delle dipendenze. Per garantire che vengano create le dipendenze implicite, assicurati che gli output restituiscano attributi di riferimento delle risorse. Invece di fare riferimento direttamente a una variabile di input per un'istanza, trasmetti l'attributo come mostrato qui:

    Consigliato:

    output "name" {
      description = "Name of instance"
      value       = google_compute_instance.main.name
    }
    

    Sconsigliato:

    output "name" {
      description = "Name of instance"
      value       = var.name
    }
    

Utilizzare le origini dati

  • Inserisci le origini dati accanto alle risorse a cui queste fanno riferimento. Ad esempio, se recuperi un'immagine da utilizzare per avviare un'istanza, posizionala accanto all'istanza anziché raccogliere le risorse di dati nel suo file.
  • Se il numero di origini dati aumenta, prendi in considerazione la possibilità di spostarli in un file data.tf dedicato.
  • Per recuperare i dati relativi all'ambiente attuale, utilizza l'interpolazione di variabili o risorse.

Limita l'uso di script personalizzati

  • Utilizza gli script solo quando necessario. Lo stato delle risorse create tramite script non viene considerato o gestito da Terraform.
    • Se possibile, evita script personalizzati. Utilizzale solo quando le risorse Terraform non supportano il comportamento desiderato.
    • Tutti gli script personalizzati utilizzati devono avere un motivo chiaramente documentato per un piano esistente e idealmente obsoleto.
  • Terraform può chiamare script personalizzati tramite provisioner, incluso il provisioner locale-exec.
  • Inserisci gli script personalizzati chiamati da Terraform in una directory scripts/.

Includi script di supporto in una directory separata

  • Organizza gli script di supporto che non vengono chiamati da Terraform in una directory helpers/.
  • Documenta gli script di supporto nel file README.md con le spiegazioni e le chiamate di esempio.
  • Se gli script di supporto accettano argomenti, fornisci il controllo degli argomenti e l'output di --help.

Inserisci i file statici in una directory separata

  • I file statici a cui Terraform fa riferimento, ma che non viene eseguito (ad esempio gli script di avvio caricati sulle istanze di Compute Engine), devono essere organizzati in una directory files/.
  • Inserisci lunghi file di Documenti Google in file esterni separati da quelli di HCL. Fai riferimento a questi elementi con la funzione file().
  • Per i file letti utilizzando la funzione Terraform templatefile, utilizza l'estensione di file .tftpl.
    • I modelli devono essere inseriti in una directory templates/.

Proteggi le risorse stateful

Per le risorse stateful, come i database, assicurati che sia abilitata la protezione da eliminazione. Ad esempio:

resource "google_sql_database_instance" "main" {
  name = "primary-instance"
  settings {
    tier = "D0"
  }

  lifecycle {
    prevent_destroy = true
  }
}

Utilizzare la formattazione integrata

Tutti i file Terraform devono essere conformi agli standard di terraform fmt.

Limita la complessità delle espressioni

  • Limita la complessità di singole espressioni interpolate. Se sono necessarie molte funzioni in una singola espressione, prendi in considerazione di suddividerla in più espressioni utilizzando i valori locali.
  • Non inserire mai più di un'operazione continua in un'unica riga. Utilizza invece più valori locali per creare la logica.

Utilizza count per valori condizionali

Per creare un'istanza di una risorsa in modo condizionale, utilizza il meta argomento count. Ad esempio:

variable "readers" {
  description = "..."
  type        = list
  default     = []
}

resource "resource_type" "reference_name" {
  // Do not create this resource if the list of readers is empty.
  count = length(var.readers) == 0 ? 0 : 1
  ...
}

Evita di utilizzare variabili specificate dall'utente per impostare la variabile count per le risorse. Se viene fornito un attributo della risorsa per una variabile di questo tipo (come project_id) e la risorsa non esiste ancora, Terraform non può generare un piano. Terraform segnala l'errore value of count cannot be computed. In questi casi, utilizza una variabile enable_x separata per calcolare la logica condizionale.

Utilizza for_each per le risorse iterate

Se vuoi creare più copie di una risorsa basata su una risorsa di input, utilizza il meta argomento for_each.

Pubblicare moduli in un registro

Moduli riutilizzabili

Per i moduli destinati a essere riutilizzati, utilizza le seguenti linee guida in aggiunta a quelle precedenti.

Attiva le API richieste nei moduli

I moduli Terraform possono attivare qualsiasi servizio richiesto utilizzando la risorsa google_project_service o il modulo project_services. L'inclusione dell'attivazione dell'API semplifica le dimostrazioni.

  • Se l'attivazione delle API è inclusa in un modulo, allora l'attivazione delle API deve essere disattivata esponendo una variabile enable_apis che per impostazione predefinita è true.
  • Se l'attivazione delle API è inclusa in un modulo, l'attivazione delle API deve impostare disable_services_on_destroy su false, perché questo attributo può causare problemi quando lavori con più istanze del modulo.

    Ad esempio:

    module "project-services" {
      source  = "terraform-google-modules/project-factory/google//modules/project_services"
      version = "~> 12.0"
    
      project_id  = var.project_id
      enable_apis = var.enable_apis
    
      activate_apis = [
        "compute.googleapis.com",
        "pubsub.googleapis.com",
      ]
      disable_services_on_destroy = false
    }
    

Includi un file dei proprietari

Per tutti i moduli condivisi, includi un file OWNERS (o CODEOWNERS su GitHub) che documenti chi è responsabile del modulo. Prima di unire tutte le richieste di pull, è necessario che un proprietario le approvi.

Rilascia versioni con tag

A volte i moduli richiedono modifiche che provocano errori e devi comunicare gli effetti agli utenti in modo che possano fissare le configurazioni su una versione specifica.

Assicurati che i moduli condivisi siano conformi alla versione SemVer v2.0.0 quando vengono taggate o rilasciate nuove versioni.

Quando fai riferimento a un modulo, utilizza un vincolo di versione per bloccare la versione principale. Ad esempio:

module "gke" {
  source  = "terraform-google-modules/kubernetes-engine/google"
  version = "~> 20.0"
}

Non dichiarare provider o backend

I moduli condivisi non devono dichiarare provider o backend. Dichiara invece provider e backend nei moduli root.

Per i moduli condivisi, definisci le versioni minime dei provider in un blocco required_providers, come segue:

terraform {
  required_providers {
    google = {
      source  = "hashicorp/google"
      version = ">= 4.0.0"
    }
  }
}

Salvo diversa indicazione, si suppone che le nuove versioni del fornitore funzionino.

Esporre etichette come variabile

Consenti flessibilità nell'etichettatura delle risorse tramite l'interfaccia del modulo. Valuta la possibilità di fornire una variabile labels con il valore predefinito di una mappa vuota, come segue:

variable "labels" {
  description = "A map of labels to apply to contained resources."
  default     = {}
  type        = "map"
}

Esponi gli output per tutte le risorse

Le variabili e gli output ti consentono di dedurre le dipendenze tra moduli e risorse. Senza output, gli utenti non possono ordinare correttamente il modulo in relazione alle configurazioni di Terraform.

Per ogni risorsa definita in un modulo condiviso, includi almeno un output che faccia riferimento alla risorsa.

Utilizzare sottomoduli in linea per una logica complessa

  • I moduli in linea ti consentono di organizzare moduli Terraform complessi in unità più piccole e di deduplicare le risorse comuni.
  • Inserisci i moduli in linea in modules/$modulename.
  • Considerare i moduli in linea come privati e non per essere utilizzati da moduli esterni, a meno che la documentazione del modulo condiviso non corrisponda diversamente.
  • Terraform non monitora le risorse sottoposte a refactoring. Se inizi con varie risorse nel modulo di primo livello, per poi eseguirne il push in sottomoduli, Terraform cerca di ricreare tutte le risorse sottoposte a refactoring. Per ridurre questo comportamento, utilizza i blocchi moved durante il refactoring.
  • Gli output definiti da moduli interni non vengono automaticamente esposti. Per condividere gli output dai moduli interni, esportali di nuovo.

Moduli radice Terraform

Le configurazioni radice (moduli radice) sono le directory di lavoro da cui esegui l'interfaccia a riga di comando di Terraform. Assicurati che le configurazioni radice rispettino i seguenti standard (e le linee guida Terraform precedenti, se applicabili). I consigli espliciti per i moduli principali sostituiscono le linee guida generali.

Riduci al minimo il numero di risorse in ogni modulo principale

È importante evitare che le dimensioni di una singola configurazione root diventino troppo grandi, perché sono archiviate troppe risorse nella stessa directory e nello stesso stato. Tutte le risorse in una particolare configurazione root vengono aggiornate ogni volta che Terraform viene eseguito. Questo può causare un'esecuzione lenta se sono incluse troppe risorse in un unico stato. Una regola generale: non includere più di 100 risorse (e idealmente solo poche decine) in un unico stato.

Utilizzare directory separate per ogni applicazione

Per gestire applicazioni e progetti in modo indipendente, inserisci le risorse per ogni applicazione e progetto nelle rispettive directory Terraform. Un servizio potrebbe rappresentare una determinata applicazione o un servizio comune come il networking condiviso. Nidificare tutto il codice Terraform per un determinato servizio in una directory (incluse le sottodirectory).

Suddividi le applicazioni in sottodirectory specifiche dell'ambiente

Quando esegui il deployment di servizi in Google Cloud, dividi la configurazione Terraform per il servizio in due directory di primo livello: una directory modules che contiene la configurazione effettiva per il servizio e una directory environments che contiene le configurazioni principali per ogni ambiente.

-- SERVICE-DIRECTORY/
   -- OWNERS
   -- modules/
      -- <service-name>/
         -- main.tf
         -- variables.tf
         -- outputs.tf
         -- provider.tf
         -- README
      -- ...other…
   -- environments/
      -- dev/
         -- backend.tf
         -- main.tf

      -- qa/
         -- backend.tf
         -- main.tf

      -- prod/
         -- backend.tf
         -- main.tf

Utilizza directory di ambiente

Ogni directory di ambiente (dev, qa, prod) corrisponde a un'area di lavoro Terraform predefinita ed esegue il deployment di una versione del servizio in quell'ambiente. Utilizza solo l'area di lavoro predefinita. Le aree di lavoro da sole non sono sufficienti per creare modelli di ambienti diversi.

Per condividere il codice in più ambienti, fai riferimento ai moduli di riferimento. In genere, questo potrebbe essere un modulo di servizio che include la configurazione Terraform condivisa di base per il servizio. Nei moduli di servizio, hard-coded degli input comuni e richiedono solo input specifici dell'ambiente come variabili.

Questa directory dell'ambiente deve contenere i seguenti file:

  • Un file backend.tf, che dichiara la posizione dello stato di backend di Terraform (in genere, Cloud Storage)
  • Un file main.tf che crea un'istanza del modulo di servizio

Esponi gli output tramite stato remoto

Esporta come informazioni di output da un modulo principale in cui gli altri moduli principali potrebbero dipendere. In particolare, assicurati di esportare nuovamente gli output dei moduli nidificati che sono utili come stato remoto.

Altri ambienti e applicazioni Terraform possono fare riferimento solo agli output a livello di modulo principale.

Utilizzando lo stato remoto, puoi fare riferimento agli output del modulo root. Per consentire l'utilizzo da parte di altre app dipendenti per la configurazione, esporta in informazioni sullo stato remoto relative agli endpoint di un servizio.

A volte, ad esempio, quando si richiama un modulo di servizio condiviso dalle directory dell'ambiente, è opportuno esportare nuovamente l'intero modulo secondario come segue:

output "service" {
  value       = module.service
  description = "The service module outputs"
}

Fissa nelle versioni del provider secondario

Nei moduli root, dichiara ogni provider e fissali in una versione secondaria. In questo modo è possibile eseguire l'upgrade automatico alle nuove release delle patch mantenendo un target stabile. Per coerenza, denomina il file delle versioni versions.tf.

terraform {
  required_providers {
    google = {
      source  = "hashicorp/google"
      version = "~> 4.0.0"
    }
  }
}

Archivia le variabili in un file tfvars

Per i moduli principali, fornisci le variabili utilizzando un file delle variabili .tfvars. Per coerenza, assegna un nome ai file delle variabili terraform.tfvars.

Non specificare le variabili utilizzando opzioni alternative della riga di comando var-files o var='key=val'. Le opzioni della riga di comando sono temporanee e facili da dimenticare. Un file delle variabili predefinito è più prevedibile.

Controlla il file .terraform.lock.hcl

Per i moduli root, il file del blocco di dipendenza .terraform.lock.hcl deve essere controllato nel controllo del codice sorgente. Questo consente il monitoraggio e il controllo delle modifiche nelle selezioni del provider per una determinata configurazione.

Comunicazione incrociata

Un problema comune che si verifica quando si utilizza Terraform è la modalità di condivisione delle informazioni tra le diverse configurazioni Terraform (possibilmente gestita da team diversi). In genere, le informazioni possono essere condivise tra le configurazioni senza richiedere che siano archiviate in un'unica directory di configurazione (o anche in un singolo repository).

Il modo consigliato per condividere informazioni tra diverse configurazioni Terraform è utilizzare lo stato remoto per fare riferimento ad altri moduli root. Cloud Storage o Terraform Enterprise sono i backend di stato preferiti.

Per eseguire query su risorse non gestite da Terraform, utilizza le origini dati provenienti dal provider di Google. Ad esempio, l'account di servizio predefinito di Compute Engine può essere recuperato utilizzando un'origine dati. Non utilizzare le origini dati per eseguire query su risorse gestite da un'altra configurazione Terraform. Questo potrebbe creare dipendenze implicite nei nomi e nelle strutture delle risorse che le normali operazioni di Terraform potrebbero causare involontariamente.

Utilizzo delle risorse Google Cloud

Le best practice per il provisioning delle risorse Google Cloud con Terraform sono integrate nei moduli Cloud Foundation Toolkit gestiti da Google. Questa sezione illustra alcune di queste best practice.

Immagini delle macchine virtuali di cucina

In generale, ti consigliamo di cucinare immagini di macchine virtuali utilizzando uno strumento come Packer. Terraform deve solo avviare le macchine utilizzando le immagini preconfezionate.

Se le immagini prepreparate non sono disponibili, Terraform può trasferire nuove macchine virtuali a uno strumento di gestione della configurazione con un blocco provisioner. Ti consigliamo di evitare questo metodo e di utilizzarlo solo come ultima risorsa. Per rimuovere lo stato precedente associato all'istanza, i provisioner che richiedono la logica di eliminazione devono utilizzare un blocco provisioner con when = destroy.

Terraform dovrebbe fornire informazioni sulla configurazione della VM alla gestione della configurazione con i metadati delle istanze.

Gestisci Identity and Access Management

Durante il provisioning delle associazioni IAM con Terraform, sono disponibili diverse risorse:

  • google_*_iam_policy (ad esempio google_project_iam_policy)
  • google_*_iam_binding (ad esempio google_project_iam_binding)
  • google_*_iam_member (ad esempio google_project_iam_member)

google_*_iam_policy e google_*_iam_binding creano associazioni IAM autorevoli in cui le risorse Terraform fungono da unica risorsa attendibile per le autorizzazioni che possono essere assegnate alla risorsa pertinente.

Se le autorizzazioni cambiano all'esterno di Terraform, Terraform all'esecuzione successiva sovrascrive tutte le autorizzazioni per rappresentare il criterio come definito nella configurazione. Questo potrebbe essere utile per risorse gestite interamente da una specifica configurazione di Terraform, ma significa che i ruoli gestiti automaticamente da Google Cloud vengono rimossi, interrompendo potenzialmente la funzionalità di alcuni servizi.

Per evitarlo, ti consigliamo di utilizzare direttamente le risorse google_*_iam_member o il modulo IAM di Google.

Controllo delle versioni

Come per altre forme di codice, archivia il codice dell'infrastruttura nel controllo della versione per preservare la cronologia e consentire rollback semplici.

Utilizza una strategia di diramazione predefinita

Per tutti i repository che contengono il codice Terraform, utilizza la seguente strategia per impostazione predefinita:

  • Il ramo main è il ramo principale di sviluppo e rappresenta l'ultimo codice approvato. Il ramo main è protetto.
  • Lo sviluppo avviene su rami di funzionalità e correzioni di bug che si diramano dal ramo main.
    • Assegna segmenti di nome feature/$feature_name.
    • Assegna ai fix/$bugfix_name rami di correzione dei bug.
  • Quando una funzionalità o una correzione di bug sono state completate, uniscila di nuovo al ramo main con una richiesta di pull.
  • Per evitare conflitti di unione, reintegra i rami prima di unirli.

Utilizzare i rami di ambiente per le configurazioni radice

Per i repository che includono configurazioni radice di cui è stato eseguito il deployment diretto in Google Cloud, è necessaria una strategia di implementazione sicura. Ti consigliamo di avere un ramo separato per ogni ambiente. Pertanto, le modifiche alla configurazione di Terraform possono essere promosse unindo le modifiche tra i diversi rami.

Ramo separato per ogni ambiente

Consenti ampia visibilità

Rendi il codice sorgente e i repository Terraform ampiamente visibili e accessibili tra le organizzazioni di progettazione, per i proprietari dell'infrastruttura (ad esempio SRE) e le parti interessate nell'infrastruttura (ad esempio gli sviluppatori). Questo garantisce che le parti interessate nell'infrastruttura possano comprendere meglio l'infrastruttura da cui dipendono.

Incoraggia gli stakeholder delle infrastrutture a inviare richieste di unione come parte del processo di richiesta di modifica.

Non eseguire mai il commit dei secret

Non eseguire mai il commit dei secret per il controllo del codice sorgente, neanche nella configurazione Terraform. Caricali invece in un sistema come Secret Manager e fai riferimento a questi ultimi utilizzando le origini dati.

Ricorda che tali valori sensibili potrebbero comunque finire nel file di stato e potrebbero essere esposti anche come output.

Organizza i repository in base ai confini del team

Anche se puoi utilizzare directory separate per gestire i limiti logici tra le risorse, i confini dell'organizzazione e la logistica determinano la struttura del repository. In generale, usa il principio della progettazione che prevede che le configurazioni con requisiti di approvazione e gestione diversi siano separate in repository di controllo delle origini diversi. Per illustrare questo principio, di seguito sono riportate alcune possibili configurazioni di repository:

  • Un repository centrale: in questo modello, tutto il codice Terraform viene gestito a livello centrale da un singolo team della piattaforma. Questo modello funziona in modo ottimale quando esiste un team di infrastruttura dedicato alla gestione del cloud e approva le modifiche richieste da altri team.

  • Repository di team: in questo modello, ogni team è responsabile del proprio repository Terraform in cui gestisce tutto ciò che riguarda l'infrastruttura di cui è proprietario. Ad esempio, il team di sicurezza potrebbe avere un repository in cui sono gestiti tutti i controlli di sicurezza e i team applicativi hanno ciascuno un repository Terraform per il deployment e la gestione dell'applicazione.

    L'organizzazione dei repository in base ai confini del team è la struttura migliore per la maggior parte degli scenari aziendali.

  • Repository disaccoppiati: in questo modello, ogni componente Terraform logico è suddiviso in un proprio repository. Ad esempio, potresti avere un repository dedicato di networking e un repository di fabbrica di progetto separato per la creazione e la gestione dei progetti. Questa soluzione funziona meglio in ambienti altamente decentralizzati, dove le responsabilità si spostano spesso tra i team.

Esempio di struttura del repository

Puoi combinare questi principi per suddividere la configurazione Terraform tra diversi tipi di repository:

  • Solide basi
  • Specifico per l'applicazione e per il team
Repository fondamentale

Un repository base che contiene componenti centrali principali, come cartelle o IAM dell'organizzazione. Questo repository può essere gestito dal team cloud centrale.

  • In questo repository, includi una directory per ogni componente principale, ad esempio cartelle, reti e così via.
  • Nelle directory del componente, includi una cartella separata per ogni ambiente (riflette le indicazioni sulla struttura della directory menzionate in precedenza).

Struttura del repository di base

Repository specifici per applicazione e team

Esegui il deployment di repository specifici per applicazioni e team separati per ogni team, in modo da gestire la configurazione Terraform specifica per le applicazioni specifiche.

Struttura di repository specifica per applicazione e team

Operazioni

Mantenere la tua infrastruttura sicura dipende da un processo stabile e sicuro per l'applicazione degli aggiornamenti Terraform.

Pianifica sempre prima

Genera sempre prima un piano per le esecuzioni di Terraform. Salva il piano in un file di output. Quando il proprietario di un'infrastruttura lo approva, esegui il piano. Anche quando gli sviluppatori creano prototipazioni locali, devono generare un piano ed esaminare le risorse da aggiungere, modificare ed eliminare prima di applicarlo.

Implementare una pipeline automatizzata

Per garantire un contesto di esecuzione coerente, esegui Terraform tramite strumenti automatizzati. Se un sistema di compilazione (come Jenkins) è già in uso e ampiamente adottato, utilizzalo per eseguire automaticamente i comandi terraform plan e terraform apply. Se non è disponibile alcun sistema esistente, adotta Cloud Build o Terraform Cloud.

Usa le credenziali dell'account di servizio per l'integrazione continua

Quando Terraform viene eseguito da una macchina in una pipeline CI/CD, deve ereditare le credenziali dell'account di servizio dal servizio che esegue la pipeline. Dove possibile, esegui le pipeline CI su Google Cloud perché Cloud Build, Google Kubernetes Engine o Compute Engine ti consentono di utilizzare strumenti come Workload Identity per inserire le credenziali senza scaricare le chiavi degli account di servizio. Per le pipeline in esecuzione altrove, preferisci la federazione delle identità per i carichi di lavoro per ottenere le credenziali. Utilizza le chiavi degli account di servizio scaricate come ultima risorsa.

Evita di importare le risorse esistenti

Dove possibile, evita di importare le risorse esistenti (utilizzando terraform import), perché così facendo, può rendere difficile comprendere appieno la provenienza e la configurazione delle risorse create manualmente. Crea invece nuove risorse tramite Terraform ed elimina quelle precedenti.

Nei casi in cui l'eliminazione di vecchie risorse creerebbe un lavoro impegnativo, utilizza il comando terraform import con l'approvazione esplicita. Dopo aver importato una risorsa in Terraform, gestiscila esclusivamente con Terraform.

Google fornisce uno strumento che puoi utilizzare per importare le tue risorse Google Cloud in stato Terraform. Per ulteriori informazioni, vedi Importare le risorse Google Cloud nello stato di Terraform.

Non modificare manualmente lo stato di Terraform

Il file di stato di Terraform è fondamentale per mantenere la mappatura tra la configurazione Terraform e le risorse di Google Cloud. La corruzione può portare a gravi problemi dell'infrastruttura. Quando sono necessarie modifiche allo stato di Terraform, usa il comando terraform state.

Rivedi regolarmente i segnaposto delle versioni

La limitazione delle versioni garantisce la stabilità, ma impedisce che le correzioni di bug e altri miglioramenti vengano incorporati nella configurazione. Pertanto, esamina regolarmente i PIN della versione per Terraform, provider Terraform e moduli.

Per automatizzare questo processo, utilizza uno strumento come Dependabot.

Usa credenziali predefinite dell'applicazione durante l'esecuzione locale

Quando gli sviluppatori eseguono l'iterazione locale della configurazione Terraform, devono autenticarsi eseguendo gcloud auth application-default login per generare le credenziali predefinite dell'applicazione. Non scaricare le chiavi degli account di servizio, perché le chiavi scaricate sono più difficili da gestire e sicure.

Impostazione di alias su Terraform

Per semplificare lo sviluppo locale, puoi aggiungere alias al tuo profilo della shell di comando:

  • alias tf="terraform"
  • alias terrafrom="terraform"

Sicurezza

Terraform richiede l'accesso sensibile all'infrastruttura cloud. Seguire queste best practice per ridurre al minimo i rischi associati e migliorare la sicurezza complessiva del tuo cloud.

Usa stato remoto

Per i clienti Google Cloud, consigliamo di utilizzare il backend dello stato di Cloud Storage. Questo approccio consente di bloccare lo stato per consentire la collaborazione come team. Inoltre, separa lo stato e tutte le informazioni potenzialmente sensibili dal controllo della versione.

Assicurati che solo il sistema di compilazione e gli amministratori con privilegi elevati possano accedere al bucket utilizzato per lo stato remoto.

Per evitare di eseguire accidentalmente il commit dello stato di sviluppo per il controllo del codice sorgente, utilizza gitignore per i file di stato di Terraform.

Cripta stato

Anche se i bucket Google Cloud sono criptati at-rest, puoi utilizzare le chiavi di crittografia fornite dal cliente per fornire un ulteriore livello di protezione. A tal fine, utilizza la variabile di ambiente GOOGLE_ENCRYPTION_KEY. Anche se nel file di stato non deve essere presente alcun secret, cripta sempre lo stato come misura di difesa aggiuntiva.

Non archiviare i segreti nello stato

In Terraform sono presenti molte risorse e provider di dati che archiviano valori segreti in testo non crittografato nel file di stato. Se possibile, evita di archiviare secret in stato. Di seguito sono riportati alcuni esempi di provider che memorizzano secret in testo non crittografato:

Contrassegna gli output sensibili

Invece di tentare di criptare manualmente i valori sensibili, utilizza il supporto integrato di Terraform per la gestione dello stato sensibile. Quando esporti valori sensibili in output, assicurati che siano contrassegnati come sensibili.

Assicurati di separare i compiti

Se non riesci a eseguire Terraform da un sistema automatico a cui non possono accedere gli utenti, utilizza una separazione dei compiti separando autorizzazioni e directory. Ad esempio, un progetto di rete corrisponde a un account di servizio o a un utente Terraform di rete il cui accesso è limitato a questo progetto.

Eseguire controlli di pre-applicazione

Quando esegui Terraform in una pipeline automatizzata, utilizza uno strumento come gcloud terraform vet per controllare l'output del piano in base ai criteri prima che venga applicato. In questo modo è possibile rilevare le regressioni della sicurezza prima che si verifichino.

Esecuzione di controlli continui

Dopo l'esecuzione del comando terraform apply, esegui i controlli di sicurezza automatici. Questi controlli possono contribuire a garantire che l'infrastruttura non entri in uno stato non sicuro. I seguenti strumenti sono scelte valide per questo tipo di controllo:

Test

Il test di moduli e configurazioni Terraform a volte segue pattern e convenzioni diversi rispetto al test del codice dell'applicazione. Mentre il test del codice dell'applicazione implica principalmente il test della logica di business delle applicazioni, il test completo del codice dell'infrastruttura richiede il deployment di risorse cloud reali per ridurre al minimo il rischio di errori di produzione. Durante l'esecuzione dei test Terraform ci sono alcune considerazioni:

  • L'esecuzione di un test Terraform crea, modifica ed elimina l'infrastruttura reale, rendendo potenzialmente dispendiosi in termini di tempo e denaro i test.
  • Non puoi limitarti a testare l'unità un'architettura end-to-end. L'approccio migliore è suddividere la tua architettura in moduli e testarli singolarmente. I vantaggi di questo approccio includono uno sviluppo iterativo più rapido grazie a un tempo di test più rapido, costi ridotti per ogni test e una riduzione delle probabilità di insuccesso da fattori al di fuori del controllo.
  • Se possibile, evita di riutilizzare lo stato. In alcuni casi, i test vengono effettuati con configurazioni che condividono dati con altre configurazioni, ma idealmente ogni test dovrebbe essere indipendente e non deve riutilizzare lo stato tra test.

Utilizza prima metodi di test meno costosi

Esistono diversi metodi che puoi utilizzare per testare Terraform. In ordine crescente di costi, tempi di esecuzione e profondità, includono quanto segue:

  • Analisi statica: verifica la sintassi e la struttura della configurazione senza eseguire il deployment di risorse, utilizzando strumenti come compilatori, linter e prove. Per farlo, utilizza terraform validate.
  • Test di integrazione dei moduli: per assicurarti che i moduli funzionino correttamente, testa i singoli moduli separatamente. Il test di integrazione per i moduli prevede il deployment del modulo in un ambiente di test e la verifica della creazione delle risorse previste. Esistono diversi framework di test che semplificano la scrittura dei test, come indicato di seguito:
  • Test end-to-end: estendendo l'approccio del test di integrazione a un intero ambiente, puoi verificare che più moduli funzionino insieme. In questo approccio, esegui il deployment di tutti i moduli che costituiscono l'architettura in un nuovo ambiente di test. Idealmente, l'ambiente di test è il più simile possibile al tuo ambiente di produzione. È uno strumento costoso, ma con la massima certezza che le modifiche non interrompano il tuo ambiente di produzione.

Inizia con poco

Assicurati che i test si sviluppino l'uno sull'altro in modo iterativo. Valuta la possibilità di eseguire prima test più piccoli e poi di eseguire test più complessi utilizzando un approccio non veloce.

Ordini casualmente gli ID progetto e i nomi delle risorse

Per evitare conflitti di denominazione, assicurati che le configurazioni abbiano un ID progetto univoco globale e nomi di risorse non sovrapposti all'interno di ogni progetto. A tale scopo, utilizza gli spazi dei nomi per le tue risorse. A questo scopo, Terraform ha un provider casuale integrato.

Utilizzare un ambiente separato per i test

Durante i test vengono create ed eliminate molte risorse. Assicurati che l'ambiente sia isolato dai progetti di sviluppo o produzione per evitare eliminazioni accidentali durante la pulizia delle risorse. L'approccio migliore è fare in modo che ogni test crei un progetto o una cartella aggiornati. Per evitare errori di configurazione, valuta la possibilità di creare account di servizio appositamente per ogni esecuzione del test.

Eseguire la pulizia di tutte le risorse

Verificare il codice dell'infrastruttura significa eseguire il deployment delle risorse effettive. Per evitare che ti vengano addebitati dei costi, valuta la possibilità di eseguire una procedura di pulizia.

Per eliminare tutti gli oggetti remoti gestiti da una determinata configurazione, usa il comando terraform destroy. Alcuni framework di test prevedono una fase di pulizia integrata. Ad esempio, se utilizzi Terratest, aggiungi defer terraform.Destroy(t, terraformOptions) al test. Se utilizzi Kitchen-Terraform, elimina l'area di lavoro utilizzando terraform kitchen delete WORKSPACE_NAME.

Dopo aver eseguito il comando terraform destroy, esegui anche ulteriori procedure di pulizia per rimuovere le risorse che Terraform non è riuscito a eliminare. Per farlo, elimina tutti i progetti utilizzati per l'esecuzione del test oppure utilizza uno strumento come cloud-nuke.

Ottimizza il tempo di test

Per ottimizzare i tempi di esecuzione dei test, utilizza i seguenti approcci:

  • Esegui test in parallelo. Alcuni framework di test supportano l'esecuzione di più test Terraform contemporaneamente.
    • Ad esempio, con Terratest puoi farlo aggiungendo t.Parallel() dopo la definizione della funzione di test.
  • Test in più fasi. Separa i test in configurazioni indipendenti che possono essere testate separatamente. Questo approccio elimina la necessità di superare tutte le fasi dell'esecuzione di un test e accelera il ciclo di sviluppo iterativo.
    • Ad esempio, in Kitchen-Terraform, dividi i test in suite separate. Durante l'iterazione, esegui ogni suite in modo indipendente.
    • Analogamente, con Terratest, aggrega ogni fase del test con stage(t, STAGE_NAME, CORRESPONDING_TESTFUNCTION). Imposta le variabili di ambiente che indicano quali test eseguire. Ad esempio, SKIPSTAGE_NAME="true".
    • Il framework di test per i progetti supporta l'esecuzione graduale.

Passaggi successivi