Home/Terraform & OpenTofu
Laboratorio · Manuale di Terraform & OpenTofu

Terraform & OpenTofu

L'architetto che progetta città digitali.

22 esercizi6 Parti5 appendici
Il codice colore indica il livello:FondamentaleIntermedioAvanzatoCloud Architect
scarica gli esercizi
git clone --filter=blob:none --sparse https://github.com/calmict/book_labs.git
cd book_labs
git sparse-checkout set terraform-opentofu/ed1
calm@calmict:~$ cat terraform/argomenti
Parte 1

Fondamenta concettuali

  • 01Il fiocco di neve e la mandriaFondamentale
  • 02La ricetta e la fotografiaFondamentale
  • 03Ristrutturare o ricostruireFondamentale
  • 04Il capocantiere invisibileFondamentale
Parte 2

Linguaggio e primo utilizzo

  • 05La scheda tecnica del grattacieloFondamentale
  • 06La prima pietraFondamentale
  • 07Il registro delle versioniFondamentale
  • 08Un traduttore, due cantieriFondamentale
Parte 3

Risorse, dati e stato

  • 09Argomenti, attributi e l'arte di chiudere un occhioIntermedio
  • 10Il catastoIntermedio
  • 11Il taccuino e i suoi segretiIntermedio
  • 12Un solo taccuino, con lucchettoIntermedio
  • 13Le porte tagliafuocoIntermedio
Parte 4

Astrazione e riuso

  • 14Le tre porteIntermedio
  • 15La flotta: per numero o per nomeIntermedio
  • 16Il banco di provaIntermedio
  • 17Il prefabbricatoIntermedio
Parte 5

Evoluzione e manutenzione

  • 18Le carte, non i palazziAvanzato
  • 19Il cassetto o la stanzaAvanzato
Parte 6

Ecosistema, qualità e produzione

  • 20I gemelli e il lucchettoCloud Architect
  • 21La piramide dei collaudiCloud Architect
  • 22Il nastro trasportatoreCloud Architect
calm@calmict:~$ ls terraform/esercizi/
01Il fiocco di neve e la mandriaFondamentale

Cosa costruisci

Prima di imparare la sintassi, devi *sentire* il problema. In questo esercizio costruisci due "server" a mano, come si faceva (e purtroppo si fa ancora) col click-ops: scoprirai che divergono subito. Poi li descrivi come codice: stessa fotografia del risultato per entrambi. Da lì in poi torturi l'infrastruttura — la modifichi a mano di notte, ne cancelli un pezzo, la radi al suolo — e ogni volta un solo comando la riporta esattamente al modello. Alla fine il "server" non è più un animale da compagnia con un nome e una storia: è un capo di bestiame con un'etichetta, sostituibile in ogni momento.

I "server" qui sono semplici file di configurazione sul tuo disco: zero cloud, zero costi, ma i concetti — drift, idempotenza, convergenza, immutabilità dell'identità — sono esattamente gli stessi che incontrerai in produzione.

Obiettivi

  • riconoscere il drift configurativo e spiegare perché il click-ops lo produce inevitabilmente;
  • distinguere l'approccio imperativo ("esegui questi passi") da quello dichiarativo ("questo è il risultato che voglio");
  • osservare l'idempotenza in azione: applicare due volte non cambia nulla;
  • vedere la convergenza: la realtà modificata a mano torna al modello;
  • spiegare la differenza tra pet e cattle con un esempio concreto.

Come testarlo

Completa i TODO nei file di partenza, poi lancia il test della soluzione:

cd terraform-opentofu/ed1/cap01/solution
./run.sh

Esito atteso

  • La Fase 2 risponde esattamente: No changes. Your infrastructure matches the configuration.
  • Dopo il sabotaggio della Fase 3, un apply riporta debug_mode a off senza che tu abbia toccato il file a mano.
  • Dopo destroy + apply (Fase 5) i due file esistono di nuovo, identici tra loro, con una herd_tag diversa da quella annotata prima.
  • Hai risposto alle tre domande in answers.md.
02La ricetta e la fotografiaFondamentale

Cosa costruisci

Una ricetta elenca i passi: rompi le uova, scalda la padella, versa. Una fotografia mostra il piatto finito. Nel capitolo 1 hai *sentito* il drift; qui metti le mani sulla frattura che lo genera: scrivi davvero uno script imperativo di provisioning, lo guardi esplodere al secondo giro, lo ripari aggiungendo le guardie ("se esiste, salta") — e poi scopri il suo difetto fatale: le guardie rendono lo script ri-eseguibile, ma *cieco*. Un file vandalizzato passa la guardia indisturbato, perché la guardia controlla che il file *esista*, non che sia *giusto*.

Poi fotografi la stessa flotta in un main.tf e la torturi da quattro punti di partenza diversi — cantiere vuoto, mezzo costruito, vandalizzato, già finito — sempre con lo stesso identico comando. I passi non li scrivi più tu: li calcola lo strumento, ogni volta, confrontando la realtà con il modello.

Obiettivi

  • spiegare perché uno script di passi funziona solo dalla partenza che il suo autore aveva in mente;
  • costruire a mano l'idempotenza con le guardie, e misurarne il costo;
  • distinguere ri-eseguibilità e convergenza: la prima la ottieni con le guardie, la seconda no;
  • osservare lo stesso comando produrre piani diversi da partenze diverse, e lo stesso risultato da tutte;
  • riconoscere i compiti per cui la ricetta resta lo strumento giusto.

Come testarlo

Completa i TODO nei file di partenza, poi lancia il test della soluzione:

cd terraform-opentofu/ed1/cap02/solution
./run.sh

Esito atteso

  • Il provision.sh riparato regge due giri consecutivi senza errori.
  • Dopo il sed della Fase 2, lo script con guardie LASCIA il debug a on (è il comportamento atteso: è la dimostrazione, non un bug).
  • Dalla partenza mezzo costruita, tofu plan propone esattamente 2 risorse da ricreare, e dopo l'apply l'inventario contiene di nuovo i tre server.
  • L'ultimo apply risponde: No changes.
  • Hai risposto alle tre domande in answers.md.
03Ristrutturare o ricostruireFondamentale

Cosa costruisci

Davanti a un edificio da cambiare, l'architetto ha due strade: ristrutturare (l'edificio resta in piedi, si cambia un impianto) o demolire e ricostruire (un edificio nuovo prende il posto del vecchio). L'infrastruttura funziona allo stesso modo, e la cosa notevole è che *non decidi tu* quale strada si prende: la conosce il provider, attributo per attributo — e il piano te la annuncia sempre in anticipo, con una segnaletica precisa che in questo esercizio impari a leggere.

Qui il "server" è per la prima volta una cosa viva: un container Docker con dentro nginx. Gli cambi la memoria e lo guardi restare lo stesso oggetto (ristrutturazione, in-place). Poi gli cambi la versione dell'immagine e lo guardi *morire e rinascere* (ricostruzione, replace): dentro quel container nessuno è mai entrato ad aggiornare nginx — questo è, alla lettera, l'immutabilità. Infine prendi in mano il blocco lifecycle e governi la sostituzione: prima inverti l'ordine (costruisci il nuovo, poi demolisci il vecchio), poi metti il fermo di sicurezza che blocca qualunque demolizione — e scopri che blocca anche più di quanto pensassi.

Obiettivi

  • leggere nel piano la strada scelta: la tilde dell'update in-place, il -/+ del replace, il marcatore che dice esattamente *quale* attributo forza la sostituzione;
  • spiegare perché è il provider a decidere la strada, attributo per attributo;
  • invertire l'ordine della sostituzione con create_before_destroy, e dire quale condizione sull'identità lo rende possibile;
  • usare prevent_destroy come fermo di sicurezza, sapendo che blocca anche i replace;
  • collegare drift (cap. 1), convergenza (cap. 2) e immutabilità in un unico filo.

Come testarlo

Completa i TODO nei file di partenza, poi lancia il test della soluzione:

cd terraform-opentofu/ed1/cap03/solution
./run.sh

Esito atteso

  • Dopo la Fase 1 l'ID del container è identico a prima dell'apply (lo hai verificato con docker inspect).
  • Nel piano della Fase 2 hai individuato il marcatore "# forces replacement" e l'annuncio destroy and then create replacement.
  • Nel piano della Fase 3 l'ordine è invertito: create replacement and then destroy.
  • Nella Fase 4 sia il destroy sia il piano del cambio versione falliscono con Instance cannot be destroyed.
  • Hai risposto alle tre domande in answers.md.
04Il capocantiere invisibileFondamentale

Cosa costruisci

Nei tre capitoli scorsi una domanda è rimasta aperta: quando le risorse sono tante e collegate, chi decide *in che ordine* costruirle? Non tu — non hai mai scritto un ordine da nessuna parte. Lo decide un capocantiere invisibile: il grafo delle dipendenze, che lo strumento costruisce leggendo il tuo codice.

In questo esercizio lo rendi visibile con lo strumento più onesto che c'è: il cronometro. Costruisci tre piani da 5 secondi l'uno *senza dirgli che sono una torre*: salgono tutti insieme, 5 secondi totali — fisicamente assurdo, ma il modello non sa che un piano poggia sull'altro finché il codice non glielo dice. Poi incateni i piani coi riferimenti e riguardi il cronometro: 15 secondi, uno alla volta. Stesse tre risorse, nessun "ordine" scritto: solo archi nati dai riferimenti. Infine guardi il grafo in faccia con tofu graph, osservi la demolizione procedere all'incontrario, e provi a costruire l'unica cosa che il capocantiere rifiuta: il ciclo — la gallina che nasce dall'uovo che è deposto dalla gallina.

Obiettivi

  • spiegare da dove nascono gli archi del grafo: il riferimento è l'arco (dipendenza implicita), depends_on è l'arco dichiarato a mano (esplicita);
  • misurare la parallelizzazione: perché ciò che non è collegato viaggia insieme, e ciò che è collegato aspetta;
  • leggere l'output di tofu graph e trovare i tuoi archi;
  • prevedere l'ordine di demolizione: lo stesso grafo, percorso al contrario;
  • riconoscere il ciclo proibito e spiegare perché viene rifiutato *prima* di toccare la realtà.

Come testarlo

Completa i TODO nei file di partenza, poi lancia il test della soluzione:

cd terraform-opentofu/ed1/cap04/solution
./run.sh

Esito atteso

  • L'apply della Fase 0 è durato circa 5 secondi; quello della Fase 1 circa 15 (li hai misurati con time).
  • In tofu graph hai individuato i tre archi tuoi: floor_2 -> floor_1, floor_3 -> floor_2, certificate -> floor_3.
  • Nella demolizione l'ordine era inverso: certificato e floor_3 prima, floor_1 ultimo.
  • tofu validate in cycle/ fallisce con Error: Cycle e i nomi delle due risorse.
  • Hai risposto alle tre domande in answers.md.
05La scheda tecnica del grattacieloFondamentale

Cosa costruisci

Nei primi quattro capitoli l'HCL l'hai *letto*, guidato dai commenti. Da questo capitolo lo *scrivi*. Niente più torture all'infrastruttura: qui il lavoro è da architetti a tavolino — compilare la scheda tecnica di un grattacielo usando, uno per uno, tutti i tipi di dato del linguaggio: i primitivi per l'anagrafica, la list per i materiali (dove un duplicato volutamente infilato ti mostrerà la differenza col set), la map per le superfici, l'object per l'indirizzo, la tuple per le coordinate. Poi assembli tutto in un blocco di testo con l'interpolazione: la scheda vera e propria, che l'apply deposita in un file.

Chiude il capitolo l'attrezzo più umile e più usato del mestiere: tofu fmt, messo alla prova su un file scritto da un collega sciatto — valido ma illeggibile. Scoprirai che cosa fmt sistema sempre (la forma) e che cosa non tocca mai (il significato).

Obiettivi

  • distinguere a colpo d'occhio un blocco (tipo, etichette, corpo) da un argomento (nome = espressione), e riconoscere i blocchi annidati;
  • scegliere il tipo giusto: list quando l'ordine conta e i duplicati sono ammessi, set quando no; map per chiavi omogenee, object per strutture miste, tuple per il posizionale;
  • usare le quattro sintassi di accesso: local.x, local.obj.campo, local.mappa["chiave"], local.tupla[0];
  • scrivere un heredoc con <<-EOT e riempirlo di interpolazioni;
  • usare tofu fmt (-diff, -check) e dire esattamente che cosa può cambiare e che cosa no.

Come testarlo

Completa i TODO nei file di partenza, poi lancia il test della soluzione:

cd terraform-opentofu/ed1/cap05/solution
./run.sh

Esito atteso

  • tofu validate è passato a ogni TODO completato (il file non è mai stato rotto).
  • Negli output: "steel" compare due volte in materials e una sola in unique_materials, che è ordinato alfabeticamente ed etichettato toset.
  • datasheet.txt contiene le righe attese, con i valori estratti da object, map e tuple con le rispettive sintassi di accesso.
  • Il secondo apply risponde No changes.
  • Dopo tofu fmt, tofu fmt -check non segnala più nulla.
  • Hai risposto alle tre domande in answers.md.
06La prima pietraFondamentale

Cosa costruisci

Cinque capitoli di concetti e di esercizi guidati: adesso posi la prima pietra tua. In questo esercizio scrivi da zero — riga per riga, non più a segnaposto — la tua prima configurazione completa: il blocco terraform che dichiara i traduttori, il blocco provider che li configura, le risorse, un output. Il risultato non è un file su disco: è un servizio web vero, raggiungibile col browser, acceso da codice.

E soprattutto vivi il ciclo di vita al rallentatore, guardando dove finora sei passato di corsa: che cosa scarica *davvero* init (andrai a pesare il binario del provider dentro .terraform: sorpresa), che cos'è un piano salvato e perché eseguirlo non chiede conferma, quali domande quotidiane trovano risposta in state list, show e output, e che cosa il destroy demolisce — e che cosa invece lascia in piedi.

Obiettivi

  • spiegare "due binari, un linguaggio": dove finisce il binario che hai installato tu e dove cominciano i provider che installa init;
  • scrivere una configurazione completa: terraform, provider, resource, output;
  • usare il piano salvato (plan -out + apply del file) e dire perché non chiede conferma;
  • rispondere alle tre domande quotidiane con state list, show e output;
  • dire con precisione che cosa rimuove destroy e che cosa no.

Come testarlo

Completa i TODO nei file di partenza, poi lancia il test della soluzione:

cd terraform-opentofu/ed1/cap06/solution
./run.sh

Esito atteso

  • Hai scritto tu l'intero main.tf, e tofu validate è passato dopo ogni blocco.
  • Hai trovato e pesato il binario del provider dentro .terraform (decine di MB) e visto nascere .terraform.lock.hcl.
  • tofu apply first.plan è partito senza chiedere conferma, e curl sulla 8087 ha risposto con la pagina di benvenuto.
  • Il cambio di porta ha prodotto un replace (marcatore # forces replacement), non un update.
  • Dopo il destroy: state list vuoto, ma .terraform e lock file ancora presenti.
  • Hai risposto alle tre domande in answers.md.
07Il registro delle versioniFondamentale

Cosa costruisci

Un cantiere serio ha un capitolato: quali norme si applicano, quali fornitori sono ammessi, e un registro di *esattamente* quali materiali sono stati scelti. Nel progetto, il capitolato è il blocco terraform — e in questo esercizio lo metti alla prova rompendolo e riparandolo: un required_version impossibile ti chiude il cancello in faccia (ed è un bene: scoprirai da che cosa protegge), un pin esatto ti mostra la nascita del lock file, e poi il gioco si fa sottile — allarghi il vincolo con l'operatore ~> e scopri che *non cambia niente*, finché non sei tu a chiederlo con init -upgrade.

È la divisione dei poteri che regge il lavoro in squadra: il *vincolo* nel codice è il recinto (che cosa sarebbe accettabile), il *lock* è la scelta (che cosa usiamo davvero, tutti, oggi). Chiude l'esercizio il collega di luglio: cancella il registro, rilancia init, e ottiene un traduttore diverso dal tuo — stesso codice, mesi dopo, provider diverso. È il drift dei capitoli 1 e 2, risalito dal mondo dei server al mondo degli attrezzi.

Obiettivi

  • spiegare a che cosa serve il blocco terraform e da che cosa protegge required_version;
  • leggere e scegliere gli operatori semver, e dire che cosa promette (e che cosa vieta) ~> 3.5;
  • raccontare la divisione dei poteri: vincolo = recinto, lock = scelta;
  • usare init -upgrade come gesto deliberato, e leggere l'errore di conflitto tra vincolo e lock;
  • dire perché in un progetto vero il lock file va committato (e perché in questo repo di esercizi, eccezionalmente, è gitignorato).

Come testarlo

Completa i TODO nei file di partenza, poi lancia il test della soluzione:

cd terraform-opentofu/ed1/cap07/solution
./run.sh

Esito atteso

  • L'init della Fase 0 è fallito con Unsupported OpenTofu Core version, e dopo il TODO 1 è passato.
  • Nel lock file hai individuato versione, vincolo e hash.
  • Dopo il TODO 2 (~> 3.5), init ha risposto Reusing previous version ... from the dependency lock file, restando su 3.5.1.
  • Dopo init -upgrade il diff del lock mostra la versione nuova.
  • Il conflitto della Fase 4 ha prodotto l'errore con l'indicazione must use tofu init -upgrade.
  • Senza lock (Fase 5), init ha installato direttamente l'ultima 3.x.
  • Hai risposto alle tre domande in answers.md.
08Un traduttore, due cantieriFondamentale

Cosa costruisci

Nel capitolo 6 hai pesato il traduttore: un binario da decine di megabyte dentro .terraform. Ma il binario da solo non basta: bisogna dirgli *con quale mondo* parlare — e questo è il mestiere del blocco provider. Qui lo scopri nel modo più concreto possibile: costruisci un secondo datacenter sulla tua macchina (un Docker dentro Docker: un engine vero, separato, raggiungibile via rete) e configuri *due istanze dello stesso traduttore* — la linea di default verso il cantiere di Milano e una linea con alias verso quello di Francoforte. Poi piazzi lo stesso nginx in entrambi, decidendo la destinazione risorsa per risorsa con una sola riga: provider =.

La seconda metà del capitolo è da leggere, non da eseguire: una galleria di blocchi provider veri — AWS coi ruoli, vSphere con la password nel posto sbagliato e poi in quello giusto — per arrivare alla regola d'oro: il codice dice *dove* e *come* connettersi, mai *chi sei*: i segreti vivono fuori dal codice, sempre.

Obiettivi

  • distinguere il provider-binario (il traduttore installato da init) dal blocco provider (la linea configurata verso un sistema reale);
  • dichiarare più istanze dello stesso provider con alias, e piazzare ogni risorsa col meta-argomento provider =;
  • leggere un blocco provider AWS con assume_role e spiegare perché i ruoli battono le chiavi statiche;
  • riconoscere al volo il peccato capitale (credenziali nel codice) e la sua correzione;
  • dire che cosa distrugge tofu destroy in uno scenario multi-provider — e che cosa non tocca.

Come testarlo

Completa i TODO nei file di partenza, poi lancia il test della soluzione:

cd terraform-opentofu/ed1/cap08/solution
./run.sh

Esito atteso

  • I due engine rispondevano separatamente (docker info locale e via tcp://127.0.0.1:23750).
  • tofu state list mostrava 4 risorse; docker ps ne vedeva una per engine (più il dind, lato Milano).
  • Entrambi i curl (8091 e 8092) rispondevano con la pagina di nginx.
  • Dopo il destroy: engine di Francoforte vuoto, ma container cap08-frankfurt-dc ancora vivo — rimosso poi a mano.
  • Hai letto i due .tf.example e sai indicare il peccato capitale e la sua correzione.
  • Hai risposto alle tre domande in answers.md.
09Argomenti, attributi e l'arte di chiudere un occhioIntermedio

Cosa costruisci

La risorsa è il mattone di tutto, e questo capitolo la mette sul tavolo operatorio. La prima scoperta è che ha due facce: gli *argomenti* — ciò che scrivi tu, l'ingresso — e gli *attributi* — ciò che la risorsa ti restituisce una volta nata, l'uscita: l'id, l'indirizzo IP che Docker le ha assegnato, i valori che nessuno conosceva prima dell'apply. Costruisci un dossier che consuma proprio quelle uscite, e nel piano vedi la scia che lasciano: (known after apply), l'ignoto dichiarato che viaggia lungo il grafo.

La seconda scoperta completa il lifecycle del capitolo 3 col suo pezzo più sottile: ignore_changes. La squadra notturna cambia un'impostazione del tuo container a mano; il piano — fedele ai capitoli 1 e 2 — vuole riconvergerla. Ma stavolta il cambiamento è *legittimo*: quella manopola appartiene a un altro processo. Firmerai il contratto dell'occhio chiuso, e imparerai quando è saggezza e quando è solo una pezza.

Obiettivi

  • distinguere argomenti (ingresso) e attributi (uscita), e dire quando gli attributi nascono;
  • leggere (known after apply) come un valore che esiste ma non è ancora conoscibile — e vederlo propagarsi lungo i riferimenti;
  • usare ignore_changes per tollerare per contratto un drift specifico, e spiegarne i rischi;
  • elencare i meta-argomenti già incontrati (provider, depends_on, lifecycle) e il mestiere di ciascuno;
  • leggere gli esempi estesi AWS e vSphere riconoscendo ingressi, uscite e archi.

Come testarlo

Completa i TODO nei file di partenza, poi lancia il test della soluzione:

cd terraform-opentofu/ed1/cap09/solution
./run.sh

Esito atteso

  • Nel piano della Fase 1 il content del dossier era (known after apply); dopo l'apply dossier.txt contiene id e IP reali.
  • Il piano della Fase 2 mostrava ~ restart "unless-stopped" -> "no".
  • Dopo il TODO 2: plan risponde No changes E docker inspect mostra ancora unless-stopped (il drift c'è, il contratto lo tollera).
  • Sai indicare nel tuo main.tf almeno tre argomenti e tre attributi.
  • Hai risposto alle tre domande in answers.md.
10Il catastoIntermedio

Cosa costruisci

Nessun cantiere parte su un terreno vergine: c'è la rete del quartiere, l'acquedotto, il catasto che registra ciò che esiste. Finora ogni cosa nel tuo modello la creavi tu; in questo capitolo impari a *consultare* — leggere ciò che esiste, appartiene ad altri, e non è affar tuo gestire.

La squadra piattaforma ha creato una rete Docker (a mano: Fase 0, sei tu a interpretarla). Tu la leggi con un blocco data — le spie annunciate nella galleria del capitolo 9 — e ci appoggi il tuo container: costruisci *su* ciò che non possiedi. Lungo la strada scopri il rovescio del capitolo 9: gli attributi del data source sono noti *già al plan* — l'esistente si consulta subito, non c'è nulla da aspettare — tranne quando il data dipende da una risorsa che deve ancora nascere: allora la lettura slitta all'apply, e l'ignoto torna. Vedrai i due casi fianco a fianco, nello stesso piano. E al destroy, la prova che chiude il capitolo: ciò che leggi non è tuo — la rete della piattaforma sopravvive intatta.

Obiettivi

  • scrivere un blocco data e spiegare in che cosa differisce da una resource (leggere vs possedere);
  • costruire risorse tue sopra oggetti altrui, senza gestirli;
  • prevedere quando un data source viene letto al plan e quando slitta all'apply — e riconoscere i due casi nel piano;
  • dire che cosa compare in state list con prefisso data. e che fine fa al destroy;
  • citare qualche sorgente dati classica dei mondi veri (la galleria).

Come testarlo

Completa i TODO nei file di partenza, poi lancia il test della soluzione:

cd terraform-opentofu/ed1/cap10/solution
./run.sh

Esito atteso

  • Nel piano della Fase 1: Read complete durante il plan, e il content della netcard già risolto (nessun known after apply).
  • Il container risulta agganciato a cap10-platform-net con IP nella 172.28.x.
  • Nello stesso piano della Fase 3: netcard risolta E freshcard (known after apply) — sai spiegare la differenza.
  • Dopo il destroy: le tue 5 risorse sparite, cap10-platform-net ancora in docker network ls.
  • Hai risposto alle tre domande in answers.md.
11Il taccuino e i suoi segretiIntermedio

Cosa costruisci

Da dieci capitoli usi plan e apply, e c'è un personaggio che lavora nell'ombra a ogni comando: il taccuino dove lo strumento annota che cosa ha costruito e come si chiamava nel codice. In questo capitolo lo apri e lo leggi: terraform.tfstate, la mappatura tra gli indirizzi del modello e gli oggetti reali — con dentro perfino gli archi del grafo.

Poi tre scoperte in crescendo. La prima scotta: crei una password marcata sensitive, l'output te la nasconde — e il taccuino la custodisce *in chiaro*: chi legge lo state legge ogni segreto. La seconda è il gioco delle tre fonti di verità: codice, memoria, realtà — cancelli un container alle spalle del modello e impari il comando che sincronizza *solo la memoria* (plan e apply -refresh-only), separando "aggiornare il taccuino" da "toccare il mondo". La terza è il finale che prepara il capitolo 12: un collega clona il tuo codice ma non la tua memoria — il suo piano vuole ricostruire tutto, il suo apply si schianta sulla realtà che già esiste, e il suo taccuino resta a metà. Stesso codice, due memorie, una sola realtà: è il problema che solo lo stato *condiviso* risolve.

Obiettivi

  • spiegare quale problema risolve lo stato: il legame indirizzo-nel-codice ↔ oggetto-reale che né il codice né la realtà contengono;
  • orientarti dentro terraform.tfstate: version, serial, lineage, resources, attributes, dependencies;
  • dimostrare che sensitive protegge l'*output*, non lo *state* — e trarne le conseguenze operative;
  • usare plan/apply -refresh-only per riallineare la memoria senza toccare la realtà;
  • raccontare con un esempio concreto perché due memorie sullo stesso mondo portano alla collisione.

Come testarlo

Completa i TODO nei file di partenza, poi lancia il test della soluzione:

cd terraform-opentofu/ed1/cap11/solution
./run.sh

Esito atteso

  • Sai indicare nel tfstate: serial, lineage, la mappatura docker_container.web → id reale, e la voce dependencies.
  • Hai visto la stessa password <sensitive> nell'output e in chiaro nello state.
  • Il plan -refresh-only ha mostrato "has been deleted" e l'apply -refresh-only ha aggiornato SOLO la memoria (state list senza container, realtà intatta).
  • Il collega: piano "3 to add", apply fallito con Conflict, state list parziale (password e immagine).
  • Hai risposto alle tre domande in answers.md.
12Un solo taccuino, con lucchettoIntermedio

Cosa costruisci

Il capitolo 11 si è chiuso su un incidente: due colleghi, due taccuini, una realtà contesa. La soluzione ha un nome — backend — ed è la risposta alla domanda "dove vive lo stato?". In questo esercizio la risposta la costruisci: la squadra piattaforma (sempre tu, col casco) accende la bacheca di cantiere — un Consul in container, un backend remoto vero — e tu *trasferisci* il tuo taccuino lì dentro con la manovra ufficiale: il blocco backend più init -migrate-state. Andrai a verificare di persona che il file locale si è svuotato e che lo stato ora abita nel backend (con dentro, sempre in chiaro, quello che sai dal capitolo 11: la casa è cambiata, le regole di custodia no).

Poi il collega torna in scena — e stavolta la storia è diversa: si aggancia allo stesso backend e il suo primo plan dice No changes: *vede le tue risorse*, perché legge la tua stessa memoria. E il gran finale: mentre un tuo apply è in corso, lui prova a lavorare — e il lucchetto glielo impedisce, per nome e cognome: Error acquiring the state lock, con scritto chi lo tiene e per quale operazione. Il caos del capitolo 11 è diventato una coda ordinata.

Obiettivi

  • spiegare che cosa decide il blocco backend (dove vive lo stato, chi può leggerlo, come si serializzano le scritture);
  • migrare uno stato locale in un backend remoto con init -migrate-state, e verificare l'esito da entrambi i lati;
  • agganciare un secondo collaboratore allo stesso stato e dimostrare che l'incidente del capitolo 11 non può più accadere;
  • leggere l'errore di lock (ID, path, operazione, chi) e sapere che esiste force-unlock come vetro da rompere in emergenza;
  • orientarti tra i backend diffusi (s3, azurerm, gcs, consul, pg, http).

Come testarlo

Completa i TODO nei file di partenza, poi lancia il test della soluzione:

cd terraform-opentofu/ed1/cap12/solution
./run.sh

Esito atteso

  • Dopo la migrazione: state list ok, terraform.tfstate locale a zero byte, e la chiave book-labs/cap12 presente nel KV di Consul.
  • Il collega si è agganciato senza migrazione e il suo primo plan ha detto No changes vedendo le tue risorse.
  • Durante il tuo apply lento, il suo plan è fallito con Error acquiring the state lock e il cartellino (ID, Operation, Who).
  • A apply concluso, il suo plan è tornato a funzionare.
  • Hai risposto alle tre domande in answers.md.
13Le porte tagliafuocoIntermedio

Cosa costruisci

Un palazzo senza porte tagliafuoco brucia tutto insieme. Un progetto con un solo stato, anche: in questo esercizio costruisci prima il *monolite* — rete e applicazione nello stesso taccuino — e misuri il raggio dell'esplosione: la squadra rete rinomina la propria rete, e il piano mostra l'incendio che si propaga fino al container dell'app; nel frattempo, un solo lucchetto mette in coda chiunque, qualunque cosa stia facendo.

Poi installi le porte tagliafuoco: due configurazioni, due taccuini (nel Consul che conosci dal capitolo 12), e il canale ufficiale per farli parlare — terraform_remote_state, il data source che legge gli *output* di un altro stato. Chiudi con le due prove del contenimento: il comando più distruttivo che esista, lanciato nella stanza dell'app, non vede nemmeno la rete; e un apply lento dell'app non blocca più il plan della rete — due code, due lucchetti, due squadre che lavorano davvero in parallelo.

Obiettivi

  • spiegare il problema del monolite: raggio dell'esplosione, lock unico, plan sempre più lenti;
  • riconoscere le linee di taglio classiche (per componente, per ambiente) e il criterio per sceglierle;
  • far comunicare due stati con terraform_remote_state, e dire perché il canale sono gli *output* (un contratto, non un accesso libero);
  • dimostrare il contenimento: destroy-scope limitato alla stanza, lock indipendenti;
  • tirare le fila della Parte 3: risorse, dati, stato — chi fa che cosa.

Come testarlo

Completa i TODO nei file di partenza, poi lancia il test della soluzione:

cd terraform-opentofu/ed1/cap13/solution
./run.sh

Esito atteso

  • Nel monolite, il rename della rete produceva un piano con DUE replace (rete e container).
  • Il plan dell'app mostrava la lettura del remote_state (Read complete) e il nome rete già risolto.
  • tofu plan -destroy nella stanza app elencava solo container e immagine.
  • Il plan della rete è passato (No changes) MENTRE l'apply lento dell'app era in corso.
  • Hai risposto alle tre domande in answers.md.
14Le tre porteIntermedio

Cosa costruisci

Finora hai scritto configurazioni con i valori *cablati* dentro: il nome del container, la porta, tutto fisso nel file. Funziona per un palazzo solo. Ma lo stesso progetto deve servire dev, staging e prod — e riscrivere il file per ognuno è la fotocopia che il capitolo 1 ci ha insegnato a temere.

Questo capitolo installa tre porte nella configurazione. La porta d'ingresso — le *variabili* — lascia entrare i valori dall'esterno: chi usa il modulo decide environment e external_port senza toccare il codice. La porta di servizio — gli *output* — mostra all'esterno solo ciò che promette: qui, l'URL dove risponde il servizio. E la cucina interna — i *locals* — non ha porta sul mondo: è dove il nome del container si *deriva* una volta sola (cap14-web-${var.environment}) e si riusa ovunque.

Obiettivi

  • dichiarare variabili di input con type, description e default, e distinguere una variabile *obbligatoria* (senza default) da una opzionale;
  • mettere un buttafuori sull'input con un blocco validation (condizione + messaggio d'errore);
  • passare un valore da tre sorgenti — -var, TF_VAR_, terraform.tfvars — e prevedere quale vince quando confliggono;
  • derivare valori interni con locals e spiegare perché non sono variabili;
  • esporre un risultato con output, e dire perché l'output è l'unica porta di servizio (il contratto del capitolo 13 nasce qui).

Come testarlo

Completa i TODO nei file di partenza, poi lancia il test della soluzione:

cd terraform-opentofu/ed1/cap14/solution
./run.sh

Esito atteso

  • Il piano senza environment si fermava con *No value for required variable*.
  • -var environment=banana veniva respinto dalla validation con il tuo messaggio.
  • Avevi visto lo stesso valore entrare da -var, TF_VAR_ e terraform.tfvars, e previsto chi vince (-var, poi file, poi env).
  • apply stampava url = "http://localhost:8095 (<env>)", e la pagina rispondeva.
  • Cambiare environment produceva un replace del container (# forces replacement).
  • Hai risposto alle tre domande in answers.md.
15La flotta: per numero o per nomeIntermedio

Cosa costruisci

Finora ogni risorsa era un pezzo unico, scritto a mano. Ma un quartiere ha cento case uguali, e nessuno le scrive una per una. Questo capitolo ti dà i due modi per *moltiplicare* una risorsa — e ti mostra perché la scelta tra i due è una delle più importanti che farai.

Il primo modo conta per numero: count. Dai un numero, e ottieni quella quantità di copie, indicizzate [0], [1], [2]. Comodo, immediato — e con una trappola nascosta. L'identità di ogni copia è la sua *posizione*: la casa numero 2. Togli una casa in mezzo alla fila, e tutte quelle dopo *scalano di numero*: la 3 diventa la 2, la 4 diventa la 3. Terraform, che lega l'identità alla posizione, crede che tu abbia rinominato mezza flotta — e la ricostruisce. Lo vedrai col tuo piano: una sola rimozione, e l'incendio si propaga alla coda.

Obiettivi

  • moltiplicare una risorsa con count e leggere i suoi indirizzi indicizzati ([0], [1]…);
  • spiegare e *dimostrare* la trappola dell'indice fragile: perché togliere un elemento in mezzo scatena una cascata di replace;
  • moltiplicare per identità con for_each, e dire perché vuole un set o una mappa (non una lista) — di qui il toset();
  • far esistere o sparire una risorsa con un condizionale (? 1 : 0);
  • generare blocchi annidati con un blocco dynamic a partire da una collezione.

Come testarlo

Completa i TODO nei file di partenza, poi lancia il test della soluzione:

cd terraform-opentofu/ed1/cap15/solution
./run.sh

Esito atteso

  • Con count, gli indirizzi erano web[0], [1], [2]; togliere bravo produceva un replace + un destroy (la cascata).
  • Dopo il TODO 1, gli indirizzi erano web["alpha"] ecc.; togliere bravo toccava *solo* bravo (0 add, 0 change, 1 destroy).
  • Con il TODO 2, canary_enabled=true produceva 1 to add; false, nessun canary.
  • Dopo il TODO 3, docker inspect su cap15-alpha mostrava le etichette team e tier.
  • Hai risposto alle tre domande in answers.md.
16Il banco di provaIntermedio

Cosa costruisci

Il capitolo scorso si chiudeva con una promessa: le collezioni che dai a count e for_each vanno spesso *preparate* prima — pulite, trasformate, filtrate. Questo capitolo ti dà gli attrezzi per prepararle, e un banco su cui provarli prima di montarli.

Gli attrezzi sono le funzioni: HCL ne ha un centinaio, già pronte (non puoi scriverne di tue nel modo classico — prendi quelle che ci sono). Trasformano una stringa (lower, trimspace, split), contano e combinano collezioni (length, merge, keys), le convertono (toset, tolist, jsonencode). Una funzione prende input tra parentesi e restituisce un valore: nient'altro, nessun effetto collaterale.

Obiettivi

  • dire che cos'è una funzione HCL e riconoscere le famiglie principali (stringa, collezione, conversione, codifica);
  • usare tofu console per provare un'espressione senza toccare stato né infrastruttura;
  • trasformare una lista con un'espressione for (list comprehension) e ripulirla con le funzioni;
  • costruire una *mappa* con un'espressione for (map comprehension), e *filtrarla* con un if;
  • collegare la collezione trasformata a un for_each (l'eredità del capitolo 15) e vedere il dedup all'opera.

Come testarlo

Completa i TODO nei file di partenza, poi lancia il test della soluzione:

cd terraform-opentofu/ed1/cap16/solution
./run.sh

Esito atteso

  • Sul banco (console): lower(trimspace(" Web-01 ")) dava "web-01" e split("-", "web-01")[0] dava "web".
  • Dopo il TODO 1, da 4 host grezzi nascevano 3 container (dedup nel toset): gli indirizzi erano host["api-02"], ["db-03"], ["web-01"].
  • host_roles (TODO 2) associava web-01=web, api-02=api, db-03=db.
  • web_hosts (TODO 3) conteneva solo web-01.
  • inventory.json conteneva il JSON con hosts, roles e web.
  • Hai risposto alle tre domande in answers.md.
17Il prefabbricatoIntermedio

Cosa costruisci

Da sedici capitoli scrivi lo stesso schema: variabili in cima, risorse in mezzo, output in fondo. E ogni volta lo riscrivi da capo, cartella dopo cartella. L'architetto vero non ridisegna la stessa palazzina per ogni lotto: la progetta una volta come prefabbricato, e poi la cala nella città quante volte serve, ognuna con le sue rifiniture.

Il prefabbricato, qui, è il modulo: una cartella con dei file .tf, ma vista come una *scatola con delle porte*. Le porte d'ingresso sono le sue variabili (nome, ambiente, porta); il macchinario dentro sono le risorse (immagine e container); le porte d'uscita sono i suoi output (l'URL dove risponde). Chi usa la scatola non guarda dentro: passa gli input alle porte d'ingresso, e legge i risultati dalle porte d'uscita. Sono esattamente le variabili e gli output del capitolo 14 — ma promossi a *interfaccia* di un componente riusabile.

Obiettivi

  • dire che cos'è un modulo e riconoscere le sue tre parti (variabili = porte d'ingresso, risorse = macchinario, output = porte d'uscita);
  • scrivere un modulo locale e chiamarlo dalla radice con un blocco module (source + input);
  • istanziare lo stesso modulo più volte con for_each, ognuna isolata, e aggregarne gli output;
  • spiegare perché un modulo eredita il provider dalla root, e quando invece va passato esplicito (il provider aliased, 17.4);
  • riconoscere un modulo remoto dal Registry (source + version) e perché la versione va fissata.

Come testarlo

Completa i TODO nei file di partenza, poi lancia il test della soluzione:

cd terraform-opentofu/ed1/cap17/solution
./run.sh

Esito atteso

  • Il modulo non aveva un blocco provider "docker" {}: ereditava il provider dalla radice.
  • Dopo il TODO 2, tofu init stampava "webapp in modules/webapp".
  • Gli indirizzi in state avevano il prefisso module.webapp["blog"]/["shop"].
  • tofu output urls dava blog → 8101 (dev) e shop → 8102 (prod).
  • Togliendo shop dalla mappa, il piano distruggeva solo l'istanza shop.
  • Hai risposto alle tre domande in answers.md.
18Le carte, non i palazziAvanzato

Cosa costruisci

Il capitolo 17 si è chiuso con un tranello: incapsulando le risorse in un modulo ne hai cambiato l'*indirizzo*, e il capitolo 15 ci aveva avvertiti che l'indirizzo *è* l'identità. Rinomina una risorsa nel codice, e Terraform non vede un cambio di targa: vede una risorsa sparita e una nuova nata — demolisce e ricostruisce. Per un container è un fastidio; per un database in produzione è un disastro.

Ma il taccuino del capitolo 11 — lo stato — è solo una *mappa* tra indirizzi nel codice e oggetti reali. E una mappa si può correggere senza toccare il territorio. Questo capitolo ti dà quattro modi per cambiare le carte senza toccare i palazzi:

Obiettivi

  • spiegare perché rinominare una risorsa, ingenuamente, provoca distruzione e ricreazione (l'indirizzo è l'identità);
  • rinominare in sicurezza con un blocco moved, e verificare che la risorsa non è stata toccata;
  • smettere di gestire una risorsa senza distruggerla con un blocco removed;
  • adottare una risorsa esistente, creata fuori da Terraform, con un blocco import;
  • usare i comandi tofu state (list, show, mv, rm) come bisturi manuale, e sapere quando servono ancora.

Come testarlo

Completa i TODO nei file di partenza, poi lancia il test della soluzione:

cd terraform-opentofu/ed1/cap18/solution
./run.sh

Esito atteso

  • Nella Fase 0, il rename ingenuo produceva un piano con destroy + create.
  • Con moved (TODO 1), il piano era 0 to add, 0 to change, 0 to destroy, e l'ID del container restava invariato.
  • Con removed (TODO 2), la cache spariva dallo state ma il container restava in stato running.
  • Con import (TODO 3), il volume orfano entrava nello state (1 to import) e il piano successivo diceva No changes.
  • Hai usato tofu state list/show e provato un state mv andata e ritorno.
  • Hai risposto alle tre domande in answers.md.
19Il cassetto o la stanzaAvanzato

Cosa costruisci

Dev, staging, prod: lo stesso progetto, tre città diverse. Copiare e incollare la configurazione tre volte è la fotocopia che il capitolo 1 ci ha insegnato a temere — ma un ambiente non è solo "lo stesso codice con un nome diverso". Un ambiente è una *copia isolata* della stessa infrastruttura, con le sue impostazioni e — soprattutto — il suo stato. E il capitolo 13 ce lo ha già gridato: gli ambienti non devono mai condividere il raggio dell'esplosione. Un errore in dev non deve poter mettere in coda, corrompere o distruggere la prod.

Questo capitolo mette a confronto le due strategie principali, e le fa toccare con mano.

Obiettivi

  • dire che cos'è davvero un ambiente (impostazioni diverse, stato separato, raggi non condivisi);
  • usare i workspace: terraform.workspace, workspace new/select/list, e capirne il rischio;
  • usare le directory separate con un modulo condiviso, e dimostrarne l'isolamento;
  • confrontare le due strategie su DRY contro isolamento, e scegliere con la testa;
  • riconoscere il ruolo di Terragrunt (DRY *sopra* le directory separate).

Come testarlo

Completa i TODO nei file di partenza, poi lancia il test della soluzione:

cd terraform-opentofu/ed1/cap19/solution
./run.sh

Esito atteso

  • Con i workspace, terraform.workspace guidava il nome, e dev/prod avevano stati separati in terraform.tfstate.d/.
  • workspace list mostrava default, dev, prod, con l'asterisco sul corrente.
  • Con le directory separate, dev e prod avevano ognuno il proprio stato e il proprio init.
  • Distruggendo dev, il container di prod restava vivo e il plan di prod diceva No changes.
  • Hai riconosciuto, nell'esempio, che cosa genera Terragrunt (backend per-ambiente + contorno comune).
  • Hai risposto alle tre domande in answers.md.
20I gemelli e il lucchettoCloud Architect

Cosa costruisci

Per diciannove capitoli abbiamo detto "un linguaggio, due binari": ogni comando tofu ha il suo gemello terraform. Questo capitolo mantiene la promessa e le mette il confine. Perché i due binari *sono* gemelli — nati dallo stesso codice — ma da un certo punto in poi hanno preso strade diverse.

La storia in breve (20.1): nel 2023 HashiCorp cambiò la licenza di Terraform, da open source a una licenza restrittiva (la BSL). La comunità reagì con un *fork* — una copia del codice che riparte per conto suo — messo sotto la Linux Foundation e ribattezzato OpenTofu, con licenza aperta (MPL). Da lì, due binari gemelli che condividono quasi tutto e divergono su poco.

Obiettivi

  • raccontare perché esiste OpenTofu (il cambio di licenza del 2023 e il fork);
  • verificare il 95%: la stessa configurazione gira identica su tofu e terraform;
  • cifrare lo stato con la cifratura nativa di OpenTofu, tenendo la passphrase *fuori* dal codice (variabile d'ambiente);
  • dimostrare il 5%: uno stato cifrato è illeggibile senza passphrase, e illeggibile del tutto per terraform;
  • scegliere tra i due binari con criterio.

Come testarlo

Completa i TODO nei file di partenza, poi lancia il test della soluzione:

cd terraform-opentofu/ed1/cap20/solution
./run.sh

Esito atteso

  • Nella Fase 0, bcrypt_hash compariva in chiaro nello stato non cifrato.
  • La stessa configurazione girava identica con tofu e con terraform (Fase 1).
  • Con TF_ENCRYPTION impostata, lo stato diventava un envelope cifrato: nessun segreto in chiaro.
  • Senza passphrase, tofu rifiutava di leggere lo stato; terraform lo rifiutava del tutto (Unsupported state file format).
  • Hai risposto alle tre domande in answers.md.
21La piramide dei collaudiCloud Architect

Cosa costruisci

Un architetto non consegna un progetto perché "sembra giusto": lo fa passare per una scala di collaudi, dai più economici e frequenti ai più costosi e rari. È la piramide della validazione, e ha quattro piani.

Alla *base*, larga e istantanea, ci sono due controlli che lanci di continuo: fmt verifica che il disegno sia leggibile (la forma), validate che sia internamente coerente (i riferimenti tornano, i tipi combaciano). Costano millisecondi, li fai a ogni salvataggio.

Obiettivi

  • descrivere la piramide della validazione e perché ha quella forma;
  • usare fmt e validate come rete di sicurezza istantanea (la base);
  • riconoscere una regola di policy as code e che cosa boccia (il piano di mezzo);
  • scrivere test di comportamento con tofu test: run, assert, expect_failures;
  • vedere un test *bocciare* una regressione — la ragione per cui i test esistono.

Come testarlo

Completa i TODO nei file di partenza, poi lancia il test della soluzione:

cd terraform-opentofu/ed1/cap21/solution
./run.sh

Esito atteso

  • fmt -check e validate passavano sulla configurazione di start.
  • Hai riconosciuto, nell'esempio di policy, quale configurazione verrebbe bocciata (immagine non pinnata) e perché.
  • Dopo i TODO 1 e 2, tofu test dava Success! con tutti i run verdi, incluso quello con expect_failures.
  • Rompendo il nome nella Fase 3, tofu test bocciava con Test assertion failed, e tornava verde una volta ripristinato.
  • Hai risposto alle tre domande in answers.md.
22Il nastro trasportatoreCloud Architect

Cosa costruisci

Per ventuno capitoli hai lanciato tu i comandi: plan, apply, test. Questo capitolo — l'ultimo — li toglie dalle tue mani e li mette su un nastro trasportatore. Da un lato entra un commit; dall'altro esce infrastruttura in produzione. E lungo il nastro, in automatico, scatta la piramide del capitolo 21: forma, coerenza, sicurezza, comportamento. Nessuno applica a mano; nessuno dimentica un controllo.

Il nastro ha due tratti. Il primo è la CI (Continuous Integration): a ogni *proposta* di modifica — una pull request — il nastro esegue i controlli e produce un *plan*, la diffusione di ciò che cambierebbe. È il cancello: se un controllo fallisce, la porta resta chiusa, e nessuno discute con la macchina. Il secondo è la CD (Continuous Delivery/Deployment): quando la modifica viene *approvata e unita* al ramo principale, il nastro fa l'apply. Proporre e consegnare diventano due gesti separati e automatici — plan sulla PR, apply sul merge.

Obiettivi

  • distinguere CI e CD, e mappare "plan sulla PR / apply sul merge" sui due tratti;
  • leggere e completare una pipeline (GitHub Actions) che automatizza la piramide del capitolo 21;
  • spiegare GitOps e *dimostrare* la correzione del drift: la realtà riportata a git;
  • dire perché OIDC sostituisce le credenziali statiche, e riconoscerne la forma nella pipeline;
  • collocare gli strumenti (le pipeline, un cenno ad Atlantis) nel quadro.

Come testarlo

Completa i TODO nei file di partenza, poi lancia il test della soluzione:

cd terraform-opentofu/ed1/cap22/solution
./run.sh

Esito atteso

  • Hai completato, in pipeline.yml.example, i quattro passi del job plan (TODO 1) e la guardia del job deploy (TODO 2).
  • In locale, la sequenza fmt/init/validate/plan passava, e apply consegnava il container su 8140.
  • Cancellando il container a mano, tofu plan diceva 1 to add, e apply lo riportava (correzione del drift).
  • Hai riconosciuto, nella pipeline, permissions: id-token e il ruolo assunto via OIDC (nessuna chiave statica).
  • Hai risposto alle tre domande in answers.md.