I Remote States sono uno strumento potente per condividere informazioni in modo controllato tra team e tenant. Soprattutto in ambienti cloud complessi con molteplici ambiti di responsabilità, essi creano trasparenza, riutilizzabilità e scalabilità. Allo stesso tempo, comportano dei rischi: stati errati, problemi di accesso e dipendenze non risolte possono compromettere la stabilità dell’intera infrastruttura. Questo articolo mostra come evitare queste sfide e come creare una base per un'infrastruttura affidabile e automatizzata attraverso strutture chiare e pratiche collaudate.
State-Locking in ambienti Multi-Tenant
In un ambiente con più team che lavorano contemporaneamente su diverse parti dell’infrastruttura, il meccanismo di state-locking è indispensabile. Senza il locking, può accadere che più applicazioni Terraform tentino di aggiornare in parallelo lo stesso state, sovrascrivendosi a vicenda. E questo finisce quasi sempre in un disastro. È quindi assolutamente necessario un meccanismo che riservi in esclusiva un file di stato a un solo utente, fino a quando questi non ha completato le operazioni di scrittura e lo rilascia per altri processi di accesso.
Best Practice per lo State-Locking
- Scegliere un backend con un meccanismo di locking robusto:
Come già spiegato nell'ultima parte di questa serie di articoli, oltre all’utilizzo di un file locale in un ambiente single-user, solo l’uso di Consul come backend remoto per i file di stato, così come Terraform Cloud ed Enterprise, è supportato e ufficialmente certificato. Terraform supporta anche altri backend come HTTPS o S3, ma questi non rientrano nei contratti di supporto, il loro utilizzo avviene espressamente a proprio rischio e responsabilità, e non tutti supportano un locking robusto e privo di errori. Consul e Terraform Enterprise offrono invece meccanismi di locking affidabili a livello enterprise.
Con l’uso di Consul, Terraform imposta automaticamente una sessione per proteggere lo state durante i processi di plan e apply.
terraform { backend "consul" { address = "consul.example.com:8500" path = "terraform/customer-a" lock = true # Attivazione esplicita del locking } }
Questa funzione di locking non richiede quindi alcuna configurazione manuale di sessioni o altri meccanismi - Terraform se ne occupa automaticamente.
- Riconoscere e risolvere i lock orfani:
I lock orfani si verificano spesso quando i processi Terraform si interrompono inaspettatamente (ad es. a causa dell’interruzione di una pipeline CI/CD, di un guasto di rete o di un’interruzione da parte dell’utente).
Per eliminare automaticamente i lock orfani, si dovrebbe implementare un processo di cleanup preventivo. Nella pratica, è sufficiente un semplice controllo prima dell'esecuzione di Terraform:
consul lock -delete "terraform/customer-a" || true
Questo comando rimuove i lock esistenti prima che inizi un nuovo processo Terraform - in modo efficiente e senza un inutile onere gestionale. - Configurare automaticamente i timeout di lock: Nella configurazione di Consul (
consul.hcl
) è possibile definire valori di timeout per le sessioni di lock. Questo garantisce la rimozione automatica dei lock bloccati.
Valori consigliati per ambienti di produzione sono:
session_ttl_min = "15m" session_ttl_max = "1h"
In questo modo si evita che lock orfani blocchino a lungo le risorse.
- Considerare i conflitti di lock nelle pipeline CI/CD:
Negli ambienti CI/CD possono verificarsi conflitti se più pipeline cercano contemporaneamente di impostare lo stesso lock.
Un pattern collaudato in questi casi è un meccanismo di retry con una strategia di backoff:
for i in {1..5}; do terraform apply && break echo "Rilevato conflitto di lock. Nuovo tentativo tra $((i * 10)) secondi..." sleep $((i * 10)) done
Questo meccanismo garantisce che il deployment rimanga stabile anche in presenza di lock temporaneamente bloccati. Una spiegazione di cosa fa lo script:
- Esegue un ciclo cinque volte: (for i in {1..5})
- Ad ogni iterazione tenta di eseguire terraform apply
- Se terraform apply ha successo (&&), interrompe il ciclo con break
- Se terraform apply fallisce, attende un certo tempo (sleep) prima di tentare nuovamente
- Il tempo di attesa aumenta ad ogni tentativo ($((i * 10))), cioè 10, 20, 30, 40 e 50 secondi
In questo modo il provisioning tramite Terraform viene ripetuto automaticamente nel caso si verifichino problemi temporanei come errori di rete, limiti delle API o conflitti di risorse che potrebbero causare il fallimento di un singolo tentativo.
Un sistema di locking robusto riduce il rischio di errori e impedisce che le esecuzioni Terraform si blocchino a vicenda. Con le misure descritte, proteggete in modo affidabile il vostro ambiente da lock orfani e ritardi inutili.
Versioning dello State
Una solida gestione delle versioni è indispensabile. In caso di problemi, consente di tornare facilmente a versioni precedenti. Terraform Cloud/Enterprise offrono questa funzionalità di default; con Consul è invece necessario implementare meccanismi aggiuntivi di backup (snapshot).
Gestione delle dipendenze tra State e prevenzione dei cicli
Le dipendenze tra State possono rapidamente diventare complesse e, nel peggiore dei casi, portare a dipendenze circolari che rendono impossibile l’aggiornamento dell’infrastruttura.
Poiché le dipendenze da Remote State in terraform_remote_state sono statiche, le configurazioni dipendenti devono essere aggiornate manualmente ogni volta che cambiano gli output rilevanti.
Best Practice per prevenire i cicli:
- Struttura gerarchica delle dipendenze: Risorse globali → Risorse regionali → Risorse specifiche del tenant → Risorse specifiche dell'applicazione. Questa chiara dipendenza unidirezionale previene i cicli.
- Utilizzare riferimenti indiretti: Se un riferimento diretto causa un ciclo, passare l'informazione attraverso un livello intermedio:
# Invece di un riferimento diretto da A → C e C → A:
# A → B → C (con B come intermediario)# Nella configurazione B:
output "information_from_a" {
value = data.terraform_remote_state.a.outputs.needed_value
}
# Nella configurazione C:
data "terraform_remote_state" "b" {
# ...
}local {
value_from_a = data.terraform_remote_state.b.outputs.information_from_a
} - Utilizzare Data Sources invece di Remote State: Se possibile, preferire le Data Sources native. Queste sono più dinamiche e riducono le dipendenze tra State.
# Invece di: data "terraform_remote_state" "network" { # ... } # Meglio, se possibile: data "oci_core_subnet" "app_subnet" { subnet_id = "ocid1.subnet.oc1..." }
Tuttavia: utilizzare le Data Sources in modo mirato ed evitare il loro uso in for_each o altri cicli, poiché generano una chiamata API ad ogni esecuzione e quindi scalano male. L’uso di Data Sources all’interno dei moduli base non è quindi una buona pratica nella maggior parte dei casi. Ma anche i moduli richiamati dai root module non dovrebbero accedere ai file di stato. Per questo motivo, utilizzate le vostre Data Sources anche a livello di root module.
Minimizzazione delle informazioni nello State per migliorare le performance
Più grande è il vostro file di stato, più tempo richiede ogni terraform plan. Terraform legge l’intero State, e in ambienti complessi ciò può rallentare sensibilmente l’esecuzione.
Best Practice per l’ottimizzazione dello State:
- Suddivisione granulare dello State: Una buona regola empirica: uno State non dovrebbe contenere più di 100 - 250 risorse, per garantire tempi di pianificazione accettabili. Applicate il Goldilocks-Principle (principio di Goldilocks) - ne parleremo più avanti in un altro articolo di questa serie.
- Attenzione agli output complessi: Limitate gli output a ciò che è essenziale e necessario:
# Da evitare: output "entire_vcn" { value = oci_core_vcn.main } # Meglio: output "vcn_essential_info" { value = { id = oci_core_vcn.main.id cidr_block = oci_core_vcn.main.cidr_block } }
- Evitare output sensibili, se non necessari: Essi aumentano la dimensione dello State e Terraform memorizza metadati aggiuntivi. Inoltre, gli output sensibili non possono essere utilizzati in altri moduli, quindi in genere è possibile evitarli.
Debugging di dipendenze complesse nello State
Il debugging di problemi relativi al Remote State può essere complicato. Ecco alcuni suggerimenti:
- Attivare log dettagliati:
export TF_LOG=DEBUG export TF_LOG_PATH=./terraform.log
- Integrazione della validazione dello State nelle pipeline CI/CD:
terraform state pull | jq '.outputs.network_config.value | has("vcn_id")'
- Utilizzare terraform console:
$ terraform console > data.terraform_remote_state.network.outputs.subnet_ids
Provider Aliasing per scenari complessi
Quando si lavora con più account (o con più regioni all’interno dello stesso account), il Provider Aliasing semplifica notevolmente la configurazione:
provider "oci" { alias = "global" region = "eu-frankfurt-1" } provider "oci" { alias = "customer_a" region = "eu-amsterdam-1" } module "customer_a_instance" { source = "./modules/instance" provider = oci.customer_a subnet_id = module.global.outputs.subnet_id }
Conclusione
Padroneggiare queste best practice vi aiuterà a gestire un’infrastruttura multi-tenant in modo stabile ed efficiente. Con l’esperienza, vi renderete conto che questi modelli non solo evitano problemi, ma migliorano anche la collaborazione tra i team e la qualità complessiva della vostra infrastruttura.