Ogni risorsa cloud che hai mai creato cliccando in una console web (una VM, un load balancer, un record DNS) si può descrivere anche in un file di testo e creare con un solo comando. Il file vive nel repository git, passa da una pull request, e riproduce lo stesso setup su un secondo account senza che nessuno debba ricordare quali nove impostazioni ha cambiato a mano. È questo che fa Terraform.

Cosa fa davvero Terraform

Terraform è uno strumento open source di HashiCorp: descrivi l’infrastruttura in file di configurazione scritti in HCL (HashiCorp Configuration Language), e lui la crea chiamando le API del cloud provider giusto. Scrivi un blocco resource che descrive il bucket S3, l’istanza EC2 o il record DNS su Cloudflare che vuoi, lanci terraform apply, e Terraform calcola le chiamate API necessarie per far esistere quella risorsa — poi ne tiene traccia, così un secondo apply cambia solo ciò che è cambiato davvero.

Il modello è dichiarativo, non procedurale: descrivi lo stato finale, non i passaggi per raggiungerlo. Non scrivi “crea un bucket, poi imposta la sua policy, poi abilita il versioning”. Scrivi come deve essere il bucket, e Terraform calcola l’ordine delle operazioni, compreso cosa deve esistere prima quando una risorsa dipende da un’altra.

Terraform da solo non sa nulla di AWS, Azure o Cloudflare. Quella conoscenza vive nei provider: plugin che traducono i blocchi HCL in chiamate verso un’API specifica. Il Terraform Registry ne elenca migliaia, ufficiali e mantenuti dalla community, che coprono di tutto: dai tre grandi cloud alle impostazioni dei repository GitHub fino ai monitor di Datadog. Lo stesso meccanismo crea un cluster Kubernetes gestito (google_container_cluster, aws_eks_cluster) oppure un singolo record DNS davanti a una CDN. Se un servizio ha un’API, è probabile che qualcuno abbia già scritto un provider per gestirlo.

Come funzionano terraform plan e apply

Una directory di lavoro minima ha almeno due file.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# terraform.tf — quali provider servono a questa configurazione
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
  required_version = ">= 1.5"
}

provider "aws" {
  region = "eu-west-1"
}
1
2
3
4
5
6
7
8
9
# main.tf — la risorsa vera e propria
resource "aws_s3_bucket" "reports" {
  bucket = "acme-monthly-reports"

  tags = {
    Environment = "production"
    ManagedBy   = "terraform"
  }
}

Quattro comandi coprono il ciclo di lavoro quotidiano:

1
2
3
4
terraform init     # scarica il plugin del provider aws, configura il backend
terraform plan     # mostra cosa cambierebbe, senza toccare nulla
terraform apply    # chiede conferma, poi fa le chiamate API
terraform destroy  # smantella tutto ciò che questa configurazione gestisce

terraform init legge required_providers e scarica i binari dei plugin corrispondenti dentro .terraform/. Lo lanci una volta per directory, e di nuovo ogni volta che aggiungi un provider o un modulo.

terraform plan è il comando che userai più spesso in assoluto. Confronta i tuoi file .tf, lo state registrato e l’infrastruttura reale come la riporta l’API del provider, poi stampa un diff: risorse da creare (+), modificare (~) o distruggere (-). Non succede ancora nulla. È questo che rende sicuro usare Terraform in produzione: leggi il plan prima che qualcosa venga toccato.

terraform apply rilancia il plan, lo mostra di nuovo e aspetta che tu digiti yes prima di chiamare le API del provider nell’ordine di dipendenza corretto. In CI passi -auto-approve oppure, meglio, applichi un file di plan già revisionato: terraform apply tfplan. La stessa pipeline che esegue Costruire e Pubblicare un’Immagine Docker con GitHub Actions a ogni merge può eseguire terraform plan su una pull request e terraform apply una volta che questa finisce su main.

A cosa serve lo state file

Dopo il primo apply, Terraform scrive terraform.tfstate — un file JSON che mappa ogni risorsa della tua configurazione all’oggetto reale che ha creato, ID incluso. Non è contabilità opzionale. È così che plan sa cosa esiste già senza riscansionare tutto il tuo account AWS a ogni esecuzione, ed è così che Terraform collega aws_s3_bucket.reports nel tuo codice al bucket acme-monthly-reports-a8f3 nella realtà.

Da qui derivano due conseguenze dirette.

Lo state può contenere segreti. Una password di database impostata come argomento di una risorsa finisce in chiaro nel file di state, perché Terraform ha bisogno degli attributi completi della risorsa per rilevare drift. Non fare mai il commit di terraform.tfstate su git.

Lo state va condiviso e bloccato. Il default è un file locale, che funziona da solo e si rompe nel momento in cui una seconda persona lancia apply dal proprio laptop: adesso ci sono due fonti di verità. La soluzione è un backend remoto: state salvato su S3, Azure Blob, GCS o Terraform Cloud, con un lock che impedisce a due apply di correre in parallelo.

1
2
3
4
5
6
7
8
9
terraform {
  backend "s3" {
    bucket       = "acme-terraform-state"
    key          = "reports/terraform.tfstate"
    region       = "eu-west-1"
    use_lockfile = true
    encrypt      = true
  }
}

use_lockfile è il locking nativo del backend S3, disponibile stabilmente da Terraform 1.11: scrive un file di lock direttamente nel bucket, quindi sulle versioni attuali non serve più una tabella DynamoDB dedicata al locking.

Una volta che il team punta allo stesso backend, il plan di ognuno riflette l’ultimo apply di tutti gli altri.

Come riutilizzare una configurazione con variabili e moduli

Hardcodare eu-west-1 e un nome di bucket funziona per un esempio di cinque righe, non per un ambiente reale. Terraform separa la forma dell’infrastruttura dai valori che cambiano per ogni ambiente.

1
2
3
4
5
6
7
8
9
# variables.tf
variable "environment" {
  type    = string
  default = "staging"
}

variable "bucket_name" {
  type = string
}
1
2
3
4
5
6
7
8
# main.tf, che referenzia la variabile
resource "aws_s3_bucket" "reports" {
  bucket = var.bucket_name

  tags = {
    Environment = var.environment
  }
}
1
terraform apply -var="bucket_name=acme-prod-reports" -var="environment=production"

Oppure metti i valori in un file terraform.tfvars così non riscrivi i flag a ogni esecuzione. outputs.tf fa il percorso inverso. Espone un valore calcolato da Terraform, come l’IP pubblico di un’istanza o un ARN generato, così un altro strumento o un’altra configurazione Terraform può consumarlo:

1
2
3
output "bucket_arn" {
  value = aws_s3_bucket.reports.arn
}

Quando un insieme di risorse si ripete tra progetti (una VPC standard, uno stack web-app standard), lo incapsuli in un modulo: una directory di file .tf richiamata con un argomento source, che prende input e restituisce output come una funzione. La maggior parte dei team finisce con una manciata di moduli interni e molte configurazioni minime che li richiamano con variabili diverse.

Quando Terraform non è lo strumento giusto

Terraform crea risorse; non configura cosa gira sopra di esse. Creerà un’istanza EC2, ma installare pacchetti, gestire utenti e tenere la configurazione sincronizzata su quell’istanza è un lavoro diverso — tradizionalmente di Ansible, o di uno script cloud-init, o di un’immagine container che l’istanza scarica ed esegue. Far passare la configurazione dell’applicazione attraverso Terraform è la direzione sbagliata. Creare la VPC attraverso Ansible è l’altra direzione sbagliata. La maggior parte dei setup reali usa entrambi, e Terraform passa l’IP dell’istanza direttamente all’inventory di Ansible tramite un output.

Per un bucket che cancellerai tra un’ora, o una VM di test usa e getta, il file di state e il ciclo scrivi-plan-apply costano più di quanto facciano risparmiare. La CLI di AWS o la console sono più veloci per qualcosa che non hai intenzione di mantenere o riprodurre.

Differenze tra Terraform, OpenTofu e Pulumi

Terraform non è più l’unico strumento che legge HCL. Nel 2023 HashiCorp l’ha spostato dalla licenza open-source MPL alla Business Source License, e ora la Linux Foundation mantiene OpenTofu, un fork che ha conservato la licenza MPL ed è rimasto compatibile con la sintassi HCL e il formato dello state di Terraform. Passare da uno all’altro in un secondo momento è una modifica di configurazione, non una riscrittura.

Pulumi va nella direzione opposta: invece di HCL scrivi l’infrastruttura in TypeScript, Python o Go. Ha senso se il tuo team vuole controllo di flusso reale e test unitari sulla logica dell’infrastruttura, invece delle espressioni più limitate di HCL.

Come provare Terraform senza un account cloud

La rete di sicurezza di Terraform è plan, e puoi provare l’intero ciclo gratis prima di puntarlo verso qualcosa che ti costa davvero. I provider random e local non richiedono alcuna credenziale cloud.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
terraform {
  required_providers {
    random = {
      source  = "hashicorp/random"
      version = "~> 3.0"
    }
  }
}

resource "random_pet" "name" {
  length = 2
}

output "generated_name" {
  value = random_pet.name.id
}

Lanci terraform init e terraform apply in una directory vuota e ottieni un file di state reale e una risorsa gestita reale, senza nulla da pagare. Fallo una volta, leggi l’output del plan riga per riga finché i simboli +, ~ e - non significano qualcosa per te, poi punta gli stessi quattro comandi verso un provider che fattura a ore.

Articoli correlati