Modulo 06

Terraform

Finora abbiamo versionato codice, automatizzato test e orchestrato container. Terraform fa la stessa cosa con l'infrastruttura: server, reti, database e cluster diventano file di testo, versionabili come qualunque altro codice.

Cos'è Terraform

Terraform è uno strumento open source di Infrastructure as Code (IaC): invece di creare risorse cloud a mano dal pannello di controllo di un provider, le descrivi in file di configurazione dichiarativi. Terraform legge quei file e si occupa di creare, modificare o eliminare le risorse necessarie per portare l'infrastruttura reale allo stato descritto.

Vantaggi principali:

Installazione

Scarica Terraform dal sito ufficiale: developer.hashicorp.com/terraform/install. Su macOS, con Homebrew:

brew tap hashicorp/tap
brew install hashicorp/tap/terraform

Per verificare l'installazione:

terraform -version

Creare il progetto

mkdir terraform-demo
cd terraform-demo

Un progetto Terraform è semplicemente una cartella con uno o più file .tf. Creiamo il file principale:

main.tf

I provider

Un provider è il plugin che permette a Terraform di parlare con un servizio esterno: AWS, Google Cloud, Azure, ma anche GitHub, Docker o Cloudflare. Ogni provider espone le risorse che si possono creare.

Dichiariamo il provider all'inizio di main.tf:

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}

provider "aws" {
  region = "eu-west-1"
}

required_providers indica quale plugin scaricare e in che versione, provider "aws" lo configura, ad esempio con la regione da usare.

Inizializzare il progetto

terraform init

Questo comando scarica i provider dichiarati e prepara la cartella di lavoro. Va eseguito ogni volta che si aggiunge o cambia un provider.

Dopo init, Terraform crea una cartella .terraform e un file .terraform.lock.hcl: il primo va ignorato da Git, il secondo va versionato perché blocca le versioni esatte dei provider usati.

Definire una risorsa

Una resource è la singola cosa che vuoi creare: un server, un bucket, un database. Aggiungiamo a main.tf un server EC2 di esempio:

resource "aws_instance" "web" {
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = "t2.micro"

  tags = {
    Name = "terraform-demo"
  }
}

La sintassi generale è resource "tipo" "nome_locale" { ... }: il tipo indica cosa creare, il nome locale è solo un riferimento interno al progetto, non il nome reale della risorsa nel cloud.

Pianificare le modifiche

terraform plan

Mostra un'anteprima di cosa Terraform sta per fare, senza applicare nulla: cosa verrà creato (+), modificato (~) o distrutto (-). È il passaggio più importante prima di ogni apply, perché permette di controllare le modifiche prima che diventino reali.

Applicare le modifiche

terraform apply

Terraform mostra di nuovo il piano e chiede conferma prima di procedere. Digitando yes, crea davvero le risorse nel provider configurato.

Attenzione: terraform apply agisce su infrastruttura reale, spesso a pagamento. Controlla sempre l'output di plan prima di confermare.

Il file di stato

Dopo ogni apply, Terraform salva lo stato attuale dell'infrastruttura in un file terraform.tfstate. Questo file è la mappa tra ciò che è scritto nei file .tf e ciò che esiste davvero nel cloud: senza di esso, Terraform non saprebbe cosa ha già creato.

Il file terraform.tfstate non va mai versionato su Git: può contenere dati sensibili e, se più persone lo modificano in parallelo, si rischiano conflitti gravi. In un progetto di squadra si usa uno stato remoto condiviso (ad esempio un bucket S3 con blocco tramite DynamoDB), invece del file locale.

Variabili

Le variabili permettono di riutilizzare la stessa configurazione con valori diversi. Creiamo variables.tf:

variable "instance_type" {
  description = "Tipo di istanza EC2"
  type        = string
  default     = "t2.micro"
}

E usiamola in main.tf:

resource "aws_instance" "web" {
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = var.instance_type
}

Per sovrascrivere un valore da riga di comando:

terraform apply -var="instance_type=t3.micro"

Oppure creando un file terraform.tfvars con i valori, che Terraform carica automaticamente:

instance_type = "t3.micro"

Output

Gli output espongono valori utili dopo l'apply, ad esempio l'indirizzo IP di un server appena creato. Creiamo outputs.tf:

output "instance_ip" {
  description = "Indirizzo IP pubblico del server"
  value       = aws_instance.web.public_ip
}

Dopo terraform apply, il valore viene stampato a schermo; per rivederlo in qualsiasi momento:

terraform output

Distruggere l'infrastruttura

terraform destroy

Elimina tutte le risorse gestite da quel progetto Terraform. Utile per ambienti di test che non devono restare accesi, evitando costi inutili.

Organizzare un progetto reale

Nei progetti più grandi, i file .tf si dividono per responsabilità invece di stare tutti in main.tf:

main.tf         # risorse principali
variables.tf    # dichiarazione delle variabili
outputs.tf      # valori esposti dopo l'apply
terraform.tfvars # valori reali delle variabili (non versionato se sensibile)
.gitignore       # esclude .terraform/ e i file di stato

.gitignore consigliato

.terraform/
*.tfstate
*.tfstate.backup
.terraform.lock.hcl
Il lock file, in realtà, molti team scelgono di versionarlo per garantire build riproducibili: valuta in base al tuo progetto se escluderlo o meno.

Moduli

Un modulo è un gruppo di file Terraform riutilizzabile, come una funzione per l'infrastruttura. Si richiama così:

module "rete" {
  source = "./modules/network"

  cidr_block = "10.0.0.0/16"
}

Questo permette di scrivere una configurazione di rete una sola volta e riutilizzarla in più ambienti (sviluppo, staging, produzione) semplicemente passando parametri diversi.

Terraform nella pipeline CI/CD

Proprio come i test in GitHub Actions, anche i comandi Terraform possono girare automaticamente ad ogni push. Un workflow tipico:

name: Terraform CI

on:
  pull_request:
    branches:
      - main

jobs:
  terraform:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout repository
        uses: actions/checkout@v5

      - name: Setup Terraform
        uses: hashicorp/setup-terraform@v3

      - name: Terraform Init
        run: terraform init

      - name: Terraform Plan
        run: terraform plan

In questo modo, ogni pull request mostra automaticamente cosa cambierebbe nell'infrastruttura, prima ancora che qualcuno esegua apply a mano.

Cosa abbiamo imparato

Installare Terraform, dichiarare provider e risorse, distinguere plan da apply, capire il ruolo del file di stato, usare variabili e output, organizzare un progetto in più file e moduli, e collegare Terraform a una pipeline CI. Con questo modulo il percorso è completo: codice versionato con Git, pubblicato su GitHub, testato con GitHub Actions, containerizzato con Docker, orchestrato con Kubernetes e l'infrastruttura sotto di tutto descritta e riproducibile con Terraform.