Terraform AWS Cloud: una sana gestione dell'infrastruttura
Pubblicato: 2022-03-11Scrivere un'applicazione è solo una parte della storia. Affinché sia utile, deve essere implementato in un luogo in grado di scalare, deve funzionare con disponibilità elevata, necessita di backup e così via.
Sempre più sviluppatori devono avere almeno una conoscenza di questo processo di distribuzione. Ciò si manifesta, ad esempio, nel fatto che DevOps sta diventando un ruolo spesso richiesto al giorno d'oggi, man mano che la complessità del sistema cresce. Non possiamo permetterci di ignorare questi cambiamenti e dobbiamo essere consapevoli di come progettare applicazioni per essere facilmente distribuibili.
Questo è anche nell'interesse dei nostri clienti: ci assumono come esperti nei nostri campi e si aspettano che consegniamo l'intero prodotto, spesso dall'inizio alla fine. Hanno requisiti e spesso sono ignari dello stack su cui gira la loro soluzione aziendale. Alla fine, è il valore commerciale del prodotto che conta.
Presentazione di Terraform
La distribuzione e la gestione dell'infrastruttura non sono un processo semplice. Oltre a molte competenze di dominio in continua evoluzione, dobbiamo imparare Yet Another Tool o un nuovo flusso di lavoro. Se hai rimandato questo, questo articolo è un'occasione per farti conoscere un approccio alla gestione dell'infrastruttura. Spero che alla fine sarai più sicuro nell'uso di Terraform e saprai di più sui possibili approcci e sfide. Dovresti essere in grado di utilizzare questo strumento per iniziare almeno a gestire una parte della tua infrastruttura cloud.
Terraform è un livello di astrazione, sì, e le astrazioni perdono, sono d'accordo. Ma alla fine, ci occupiamo della risoluzione dei problemi e della gestione delle astrazioni. Questo mira a fornirci più sanità mentale nelle nostre attività quotidiane.
L'obiettivo. il gol
Spiegherò cos'è Terraform, come si adatta all'intero ecosistema e come si confronta con altri strumenti simili. Quindi ti mostrerò i passaggi necessari per configurare una configurazione Terraform multi-ambiente e pronta per la produzione per un team. Spiegherò le basi della scrittura della configurazione di Terraform: come gestire la complessità e duplicare il codice con moduli condivisibili.
Gli esempi saranno tutti incentrati su un provider cloud: Amazon Web Services (AWS). Questo è solo un cloud con cui ho più esperienza, ma tutte le informazioni dovrebbero applicarsi anche ad altri cloud.
Concluderò con alcune note che vorrei aver saputo quando ho iniziato: alcuni problemi di sintassi, stranezze e casi in cui Terraform non sarebbe il mio strumento preferito.
Non mi concentrerò sui dettagli nitidi della sintassi. Puoi aggiornarti rapidamente leggendo la fantastica documentazione e le guide fornite da HashiCorp per Terraform. Mi piacerebbe concentrarmi su cose che potrebbero non essere ovvie dall'inizio e che avrei voluto sapere prima di iniziare a lavorare con Terraform. Si spera che questo ti indichi nella giusta direzione e ti permetta di considerare Terraform come uno strumento da utilizzare in progetti futuri.
La gestione delle infrastrutture è difficile
Ci sono più passaggi coinvolti nella configurazione di un ambiente per un'app nel cloud. A meno che tu non le scriva tutte come liste di controllo dettagliate e le segua da vicino, in ogni momento, commetterai degli errori; siamo umani, dopotutto. Questi passaggi sono difficili da condividere. È necessario documentare molte procedure manuali e i documenti possono diventare rapidamente obsoleti. Ora moltiplica tutto ciò per il numero totale di ambienti per una singola app: dev , test/qa , stage e prod . Devi anche pensare alla sicurezza per ciascuno di essi.
Non tutti gli strumenti hanno un'interfaccia utente che posso usare e dimenticare la complessità?
Hanno interfacce utente: AWS Management Console è un ottimo esempio. Ma quegli strumenti fanno molto sotto il cofano. Un clic sull'interfaccia utente può effettivamente invocare una cascata di modifiche difficili da comprendere. Di solito non c'è modo di annullare ciò che hai fatto nell'interfaccia utente (i soliti prompt "Sei sicuro?" spesso non sono sufficienti). Inoltre, è sempre una buona idea avere un secondo paio di occhi per controllare le modifiche, ma quando utilizziamo le interfacce utente dovremmo sederci con questa persona insieme o controllare le nostre modifiche dopo che sono state apportate, il che è più un controllo che un revisione. Ogni provider cloud ha la propria interfaccia utente che devi padroneggiare. Le interfacce utente sono progettate per essere facili da usare (non necessariamente semplici!) e in quanto tali sono soggette ad approcci deliranti come "questo è solo un piccolo ritocco" o un rapido aggiornamento rapido di produzione che dimenticherai in 48 ore. Anche questo clic manuale è molto difficile da automatizzare.
E gli strumenti CLI?
Sarebbero migliori degli strumenti dell'interfaccia utente per i nostri casi d'uso. Tuttavia, sei ancora incline a apportare modifiche a mano o a scrivere script bash che possono facilmente sfuggire di mano. Inoltre ogni provider ha i propri strumenti CLI. Con questi strumenti, non puoi vedere le tue modifiche prima di impegnarti. Per fortuna questo non è un problema nuovo e ci sono strumenti che aiutano con questo.
Orchestrazione vs. Configuration Management
Definirò alcune categorie di strumenti e pratiche utilizzate per gestire l'infrastruttura. Si tratta di orchestrazione e gestione della configurazione. Puoi considerarli più o meno come modelli di programmazione dichiarativi e imperativi (pensa a Prolog vs. Python). Ognuno ha i propri pro e contro, ma è meglio conoscerli tutti e applicare lo strumento migliore per un determinato lavoro. Inoltre, l'orchestrazione in genere comporta un livello di astrazione più elevato rispetto alla gestione della configurazione.
Orchestrazione
L'orchestrazione assomiglia più a un paradigma di programmazione dichiarativo. Mi piace pensare che sia il direttore di un'orchestra. Il lavoro di un direttore d'orchestra è ben riassunto da Wikipedia (link) come "l'arte di dirigere l'esecuzione simultanea di più musicisti o cantanti mediante l'uso del gesto". I direttori d'orchestra comunicano il ritmo e il tempo, la dinamica e il cueing della musica, ma il lavoro vero e proprio viene eseguito da singoli musicisti esperti nel suonare i loro strumenti musicali. Senza tale coordinamento, non sarebbero in grado di eseguire un pezzo perfetto.
È qui che si adatta Terraform. Lo usi per condurre una parte dell'infrastruttura IT: gli dici cosa distribuire e Terraform lo collega tutti insieme ed esegue tutte le chiamate API necessarie. Ci sono strumenti simili in questo spazio; uno dei più noti è AWS Cloud Formation. Ha un supporto migliore per il ripristino e i rollback in caso di errori rispetto a Terraform, ma anche, secondo me, una curva di apprendimento più ripida. Inoltre, non è indipendente dal cloud: funziona solo con AWS.
Gestione della configurazione
Il lato complementare di queste pratiche è l'approccio di gestione della configurazione. In questo paradigma, specifichi i passaggi esatti che uno strumento deve compiere per arrivare a una determinata configurazione desiderata. I passaggi stessi potrebbero essere piccoli e supportare più sistemi operativi, ma è necessario pensare attivamente all'ordine della loro esecuzione. Non hanno consapevolezza dello stato attuale e dell'ambiente circostante (a meno che tu non li programmi con quello) e, come tali, eseguiranno ciecamente qualsiasi passo tu gli dai. Ciò potrebbe portare a un problema noto come drift di configurazione , in cui le tue risorse si desincronizzaranno lentamente con ciò che erano destinate a rappresentare inizialmente, soprattutto se hai apportato alcune modifiche manuali ad esse. Sono ottimi per la gestione e il provisioning dei servizi su singole istanze. Esempi di strumenti che eccellono in questo flusso di lavoro sono Chef, Puppet, Ansible e Salt.
L'orchestrazione impone un approccio alla tua infrastruttura in cui tratti le tue risorse come bestiame, non come animali domestici. Invece di "nutrire" manualmente ogni VPS, puoi sostituirlo con una copia esatta quando qualcosa va storto. Non voglio dire che semplicemente non ti interessi e fai ripartire la cosa sperando per il meglio.
Invece, dovresti indagare e risolvere il problema nel codice e quindi distribuirlo.
Ansible (e altri strumenti CM) possono essere utilizzati per gestire l'infrastruttura AWS, ma ciò comporterebbe molto lavoro ed è più soggetto a errori, soprattutto quando l'infrastruttura cambia spesso e aumenta in complessità.
Una cosa importante da ricordare è che gli approcci di orchestrazione e gestione della configurazione non sono in conflitto tra loro. Sono compatibili. È perfettamente corretto avere un gruppo di istanze EC2 (VPS) in un gruppo AutoScaling gestito da Terraform ma che esegue un'AWS Application Image (AMI), che è un'istantanea del disco, che è stata preparata con passaggi imperativi con, ad esempio, Ansible . Terraform ha anche un concetto di "provider" che consentono di eseguire strumenti di provisioning esterni una volta avviata una macchina.
La documentazione di Terraform è un ottimo lavoro per spiegare ulteriormente questo aspetto e aiutarti a posizionare Terraform nell'intero ecosistema.
Cos'è la Terraform?
È uno strumento open source, creato da HashiCorp che ti consente di codificare la tua infrastruttura come file di configurazione dichiarativi che sono versionati e condivisi e possono essere rivisti.
Il nome HashiCorp dovrebbe suonare un campanello: fanno anche Nomad, Vault, Packer, Vagrant e Consul. Se hai utilizzato uno di questi strumenti, conosci già la qualità della documentazione, la comunità vivace e l'utilità che puoi aspettarti dalle loro soluzioni.
Infrastrutture come codice
Terraform è indipendente dalla piattaforma; puoi usarlo per gestire server bare metal o server cloud come AWS, Google Cloud Platform, OpenStack e Azure. Nel gergo Terraform, questi sono chiamati provider . Puoi avere un'idea della scala leggendo un elenco completo di provider supportati. È possibile utilizzare più provider contemporaneamente, ad esempio quando il provider A configura le macchine virtuali per te, ma il provider B configura e delega i record DNS.
Ciò significa che è possibile cambiare provider cloud con una modifica in un file di configurazione? No, non credo nemmeno che lo vorresti, almeno non in modo automatizzato. Il problema è che provider diversi possono avere capacità, offerte, flussi, idee diversi, ecc. diversi. Significa che dovrai utilizzare risorse diverse affinché un provider diverso esprima lo stesso concetto. Tuttavia, tutto ciò può ancora essere eseguito in un'unica sintassi di configurazione familiare ed essere parte di un flusso di lavoro coeso.
Parti essenziali di una configurazione Terraform
- Il binario Terraform stesso, che devi installare
- I file del codice sorgente, ovvero la tua configurazione
- Lo stato (locale o remoto) che rappresenta le risorse gestite da Terraform (ne parleremo più avanti)
Scrittura della configurazione della superficie
Scrivi il codice di configurazione Terraform nei file *.tf
usando il linguaggio HCL. C'è un'opzione per usare il formato JSON ( *.tf.json
), ma è rivolto a macchine e generazione automatica piuttosto che a esseri umani. Ti consiglio di restare con l'HCL. Non mi addentrerò nella sintassi del linguaggio HCL; i documenti ufficiali fanno un ottimo lavoro nel descrivere come scrivere HCL e come utilizzare variabili e interpolazioni. Menzionerò solo il minimo indispensabile per comprendere gli esempi.
All'interno dei file Terraform, hai a che fare principalmente con risorse e origini dati . Le risorse rappresentano componenti della tua infrastruttura, ad esempio un'istanza AWS EC2, un'istanza RDS, un record DNS Route53 o una regola in un gruppo di sicurezza. Consentono di eseguire il provisioning e modificarli all'interno dell'architettura cloud.
Supponendo che tu abbia impostato Terraform, se emetti una terraform apply
, il codice seguente creerebbe un'istanza EC2 completamente funzionante (vengono mostrate solo alcune proprietà):
resource "aws_instance" "bastion" { ami = "ami-db1688a2" # Amazon Linux 2 LTS Candidate AMI 2017.12.0 (HVM), SSD Volume Type - ami-db1688a2 instance_type = "t2.nano" key_name = "${var.key_name}" subnet_ vpc_security_group_ids = ["${aws_security_group.bastion.id}"] monitoring = "false" associate_public_ip_address = "true" disable_api_termination = "true" tags = { Name = "${var.project_tag}-bastion-${var.env}" Env = "${var.env}" Application ApplicationRole = "Bastion Host" Project = "${var.project_tag}" } }
D'altra parte, ci sono fonti di dati che consentono di leggere i dati su determinati componenti senza modificarli. Vuoi ottenere l'ID AWS (ARN) di un certificato emesso da ACM? Utilizzi un'origine dati. La differenza è che le origini dati sono precedute da data_
quando si fa riferimento ad esse nei file di configurazione.
data "aws_acm_certificate" "ssl_cert" { domain = "*.example.com" statuses = ["ISSUED"] }
Quanto sopra fa riferimento a un certificato SSL ACM emesso che può essere utilizzato insieme agli ALB AWS. Prima di fare tutto questo, però, devi configurare il tuo ambiente.
Struttura delle cartelle
Gli ambienti Terraform (ei loro stati) sono separati da directory. Terraform carica tutti i file *.tf
in una directory in uno spazio dei nomi, quindi l'ordine non ha importanza. Consiglio la seguente struttura di directory:
/terraform/ |---> default_variables.tf (1) /stage/ (2) |---> terraform.tfvars (3) |---> default_variables.tf (4) |---> terraform.tf (5) |---> env_variables.tf (6) /prod/ /<env_name>/
-
default_variables.tf
– definisce tutte le variabili di primo livello e, facoltativamente, i loro valori predefiniti. Possono essere riutilizzati in ogni ambiente (directory annidata) con collegamenti simbolici. -
/stage/
– una directory che contiene la configurazione per un intero ambiente separato (qui chiamatostage
, ma può essere qualsiasi cosa). Qualsiasi modifica apportata all'interno di questa cartella è totalmente indipendente da altri ambienti (env, comeprod
) che è qualcosa che si desidera per evitare di rovinare l'ambiente di produzione con le modifiche apportate allostage
! -
terraform.tfvars
– definisce i valori delle variabili. I file.tfvars
sono simili ai file.env
in quanto contengono le coppiekey=val
per le variabili definite. Ad esempio, specifica ilprofile
AWS , AWSkey_name
e AWSkey_path
che utilizzo. Può essere ignorato in Git. -
default_variables.tf
– questo è un collegamento simbolico al file (2), che ci consente di condividere variabili indipendenti dall'ambiente senza ripeterci. -
terraform.tf
– questa è la configurazione principale di ogni ambiente; contiene il bloccoterraform {}
che configura il back-end. Anche qui configuro i provider . -
env_variables.tf
– questo file contiene variabili specifiche di env. Taggo tutte le risorse conEnv=<env_name>
in AWS, quindi questo file di solito definisce solo una variabile:env
.
Naturalmente, questo non è l'unico modo per strutturare il tuo ambiente. Questo è solo qualcosa che ha funzionato bene per me consentendo una chiara separazione delle preoccupazioni.
Configurazione back-end
Ho già menzionato lo stato Terraform. Questa è una parte essenziale del flusso di lavoro di Terraform. Potresti chiederti se lo stato è effettivamente richiesto. Terraform non potrebbe semplicemente interrogare l'API AWS tutto il tempo per ottenere lo stato effettivo dell'infrastruttura? Bene, se ci pensi, Terraform ha bisogno di mantenere una mappatura tra ciò che gestisce nei file di configurazione dichiarativi e ciò a cui corrispondono effettivamente questi file (nell'ambiente del provider di servizi cloud). Nota che, mentre scrivi i file di configurazione di Terraform, non ti interessano gli ID, ad esempio, delle singole istanze EC2 o gli ARN che verranno creati per i gruppi di sicurezza che pubblichi. Internamente, tuttavia, Terraform deve sapere che un determinato blocco di risorse rappresenta una risorsa concreta con un ID/ARN. Questo è necessario per rilevare le modifiche. Inoltre, lo stato viene utilizzato per tenere traccia delle dipendenze tra le risorse (anche qualcosa a cui di solito non devi pensare!). Sono usati per costruire un grafico che può essere (solitamente) parallelizzato ed eseguito. Come sempre, ti consiglio di leggere l'ottima documentazione sullo stato di Terraform e il suo scopo.
Poiché lo stato è l'unica fonte di verità per la tua architettura, devi assicurarti che tu e il tuo team stiate sempre lavorando sulla versione più aggiornata e che non si creino conflitti a causa dell'accesso non sincronizzato allo stato. Non vuoi risolvere i conflitti di unione nel file di stato, credimi.
Per impostazione predefinita, Terraform memorizza lo stato in un file su disco, situato nella directory di lavoro corrente (di ogni env) come file terraform.tfstate
. Questo va bene se sai che sarai l'unico sviluppatore sul lavoro o stai solo imparando e sperimentando Terraform. Tecnicamente, potresti farlo funzionare in un team perché puoi impegnare lo stato in un repository VCS. Ma poi, dovresti assicurarti che tutti lavorino sempre sull'ultima versione dello stato e che nessuno modifichi contemporaneamente! Questo è generalmente un forte mal di testa e lo sconsiglio vivamente. Inoltre, se qualcuno si unisce alla tua operazione single-dev, dovresti comunque configurare un posto alternativo per lo stato.
Fortunatamente, questo è un problema con una buona soluzione integrata in Terraform: il cosiddetto Remote State . Affinché lo stato remoto funzioni, è necessario configurare il back-end utilizzando uno dei provider di back-end disponibili. Il seguente esempio di back-end sarà basato su AWS S3 e AWS DynamoDB (database AWS NoSQL). Potresti usare solo S3 ma poi perdi il meccanismo di blocco dello stato e controllo della coerenza (sconsigliato). Se in precedenza hai utilizzato solo lo stato locale, la configurazione di un back-end remoto ti offrirà un'opzione per migrare il tuo stato la prima volta, in modo da non perdere nulla. Puoi leggere di più sulla configurazione del back-end qui.
Sfortunatamente, c'è un problema di pollo e uova: il bucket S3 e la tabella DynamoDB devono essere creati manualmente. Terraform non può crearli automaticamente poiché non esiste ancora uno stato! Bene, ci sono alcune soluzioni come https://github.com/gruntwork-io/terragrunt che lo automatizzano utilizzando AWS CLI, ma non voglio deviare dall'argomento principale di questo post del blog.
Le cose importanti da sapere sulla configurazione del backend di S3 e DynamoDB sono:
- Abilita il controllo delle versioni sul bucket S3 per essere al sicuro dall'errore umano e dalla legge di Murphy.
- La tabella DynamoDB ha un limite di velocità in lettura e scrittura (chiamato capacità). Se apporti molte modifiche allo stato remoto, assicurati di abilitare DynamoDB AutoScaling per quella tabella o di configurare limiti R/W sufficientemente elevati. In caso contrario, Terraform riceverà errori HTTP 400 dall'API AWS durante l'esecuzione di molte chiamate.
Per riassumere, la seguente configurazione di back-end può essere inserita in terraform.tf
per configurare lo stato remoto su S3 e DynamoDB.
terraform { # Sometimes you may want to require a certain version of Terraform required_version = ">= 0.11.7" # Stores remote state, required for distributed teams # Bucket & dynamoDB table have to be created manually if they do not exist # See: https://github.com/hashicorp/terraform/issues/12780 backend "s3" { bucket = "my-company-terraform-state" key = "app-name/stage" region = "eu-west-1" # 5/5 R/W Capacity might not be enough for heavy, burst work (resulting in 400s). Consider enabling Auto Scaling on the table. # See: http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.ProvisionedThroughput.html dynamodb_table = "terraform-state-lock-table" } }
Questo è molto da prendere in una volta, ma ricorda, lo fai una volta per ogni ambiente e poi puoi dimenticartene. Se hai bisogno di un controllo ancora maggiore sul blocco dello stato, c'è HashiCorp Terraform Enterprise, ma non lo tratterò qui.
Fornitori
Affinché questo back-end sia accessibile e per poter comunicare con il nostro provider cloud, dobbiamo configurare il cosiddetto provider . Il seguente blocco può essere inserito nel file terraform.tf
(per ogni env):
provider "aws" { profile = "${var.profile}" region = "${var.region}" version = "~> 1.23.0" }
Le variabili per il profile
e la region
sono memorizzate nel file terraform.tfvars
, che può essere ignorato. La variabile del profile
si riferisce a un profilo denominato che contiene le credenziali di sicurezza per il cloud AWS utilizzando il file delle credenziali standard. Nota che sto anche impostando alcuni vincoli di versione. Non vuoi che Terraform aggiorni i plug-in del tuo provider a tua insaputa su ogni inizializzazione del back-end. Soprattutto considerando che esiste una versione 2.x del provider AWS che richiede un aggiornamento accurato.

Inizializzazione back-end
Ogni configurazione back-end richiede un passaggio di inizializzazione all'inizio e ogni volta che viene apportata una modifica. L'inizializzazione configura anche i moduli Terraform (ne parleremo più avanti), quindi quando li aggiungi, devi rieseguire anche il passaggio init. Questa operazione è sicura da eseguire più volte. Si noti che, durante l'inizializzazione del back-end, non tutte le variabili possono essere lette da Terraform per configurare lo stato, né dovrebbero esserlo per ragioni di sicurezza (es. chiavi segrete). Per ovviare a questo e, nel nostro caso, utilizzare un profilo AWS diverso da quello predefinito, puoi utilizzare l'opzione -backend-config
con accetta k=v
coppie di variabili. Questa è chiamata configurazione parziale.
terraform init -backend-config=profile=<aws_profile_name>
I file Terraform condividono un ambito vincolato a una determinata directory. Ciò significa che una sottocartella non è collegata direttamente al codice della directory padre. Tuttavia, sta definendo un modulo che consente il riutilizzo del codice, la gestione della complessità e la condivisione.
Il flusso di lavoro
Il flusso di lavoro generale quando si lavora con il codice Terraform è il seguente:
- Scrivi la configurazione per la tua infrastruttura.
- Guarda quali modifiche reali apporterà (
terraform plan
). - Facoltativamente, esegui le modifiche esatte che hai visto nel passaggio 2 (
terraform apply
). - VAI A 1
Piano Terraforma
Il comando del plan
Terraform ti presenterà un elenco di modifiche che verranno apportate alla tua infrastruttura dopo l'emissione del comando apply
. È sicuro emettere il plan
più volte, poiché di per sé non cambia nulla.
Come leggere un piano
Gli oggetti in Terraform (risorse e origini dati) sono facilmente identificabili dai loro nomi completi.
- Nel caso delle risorse, l'ID potrebbe essere simile a:
<resource_type>.<resource_name>
nome_risorsa> —ad es.aws_ecs_service.this
. - Nel caso di risorse all'interno dei moduli, abbiamo un nome modulo aggiuntivo:
module.<module_name>.<resource_type>.<resource_name>
module.my_service_ecs_service_task.aws_ecs_service.this
. - Origini dati (all'interno e all'esterno di un modulo):
(module.<module_name>).data.<resource_type>.<resource_name>
nome_risorsa> —ad es.module.my_service_ecs_service_task.data.aws_ecs_task_definition.this
.
Il tipo di risorsa è specifico per un determinato provider e di solito include il suo nome ( aws_…
). L'intero elenco delle risorse disponibili per AWS è disponibile nei documenti.
Ci sono cinque azioni che un piano mostrerà per determinate risorse:
-
[+]
Aggiungi – Verrà creata una nuova risorsa. -
[-]
Distruggi: una risorsa verrà completamente distrutta. -
[~]
Modifica sul posto: una risorsa verrà modificata e verranno modificati uno o più parametri. Questo è generalmente sicuro. Terraform ti mostrerà quali parametri verranno modificati e come (se possibile). -
[- / +]
– La risorsa verrà rimossa e quindi ricreata con nuovi parametri. Ciò accade se è stata apportata una modifica a un parametro che non può essere modificato sul posto. Terraform ti mostrerà quali modifiche forzano la ricreazione di una risorsa con il seguente commento in rosso:(forces new resource)
. Questo è potenzialmente pericoloso, poiché c'è un periodo in cui la risorsa non esisterà affatto. Può anche interrompere altre dipendenze connesse. Consiglierei di aggirare tali cambiamenti a meno che tu non sappia quali saranno le conseguenze o non ti interessi un tempo di inattività. -
[<=]
– Verrà letta un'origine dati. Questa è un'operazione di sola lettura.
Sopra è un piano di esempio. Le modifiche che ho fatto sono state:
- Modificato il
instance_type
del bastione - Aggiunta una seconda istanza del bastione
- Modificato il nome di un gruppo di sicurezza
Tieni presente che l'ultima modifica è una regola del gruppo di sicurezza che ha rilevato automaticamente una modifica nel nome del gruppo principale. A mio parere, l'intero piano è molto leggibile. Alcuni valori di parametro vengono visualizzati come <computed>
—non significa che verranno modificati, ma piuttosto che non possono essere recuperati e presentati in questa fase (come il nome SG che non è stato ancora creato). Ricorda che solo le risorse modificate verranno mostrate durante il comando del plan
. Tutto ciò che viene omesso non verrà toccato.
Di solito, i comandi Terraform accettano parametri aggiuntivi. Il parametro più importante per il comando plan è l'opzione -out
, che salverà il tuo piano su disco. Questo piano salvato può essere quindi eseguito (esattamente come salvato) dal comando applica. Questo è molto importante, poiché altrimenti potrebbe esserci un cambiamento, fatto, ad esempio, dai colleghi tra l'emissione di un plan
e l'emissione della apply
. Esempio:
- Emetti il
plan
e verifica che sia a posto. - Qualcuno cambia lo stato, a tua insaputa.
- Problema
apply
e... oops, ha fatto qualcosa di diverso da quello che era stato pianificato!
Per fortuna, questo flusso di lavoro è stato migliorato in Terraform v0.11.0. Da questa versione, il comando apply
ti presenta automaticamente un piano che devi poi approvare (digitando esplicitamente yes
). Il vantaggio è che, se applichi questo piano, verrà eseguito esattamente come presentato.
Un'altra opzione utile è -destroy
, che ti mostrerà il piano che comporterà la distruzione di tutte le risorse gestite da Terraform. Puoi anche indirizzare risorse specifiche per la distruzione con l'opzione -target
.
Applicare Terraform
Quando applichiamo un determinato piano, Terraform si spegne e blocca il nostro stato per garantire l'esclusività. Quindi procede a modificare le risorse e, alla fine, invia uno stato aggiornato. Una cosa da notare è che alcune risorse richiedono più tempo per essere completate rispetto ad altre. Ad esempio, la creazione di un'istanza AWS RDS potrebbe richiedere più di 12 minuti e Terraform attenderà il completamento dell'operazione. Ovviamente, Terraform è abbastanza intelligente da non bloccare ogni altra operazione con questo. crea un grafico orientato delle modifiche richieste e, se non ci sono interdipendenze, usa il parallelismo per velocizzare l'esecuzione.
Importazione di risorse
Spesso, Terraform e altre soluzioni di “configurazione come codice” vengono introdotte gradualmente, in un ambiente già esistente. Il passaggio è davvero facile. Fondamentalmente, tutto ciò che non è definito all'interno di Terraform viene lasciato non gestito e invariato. Terraform si occupa solo di ciò che gestisce. Naturalmente, è possibile che ciò introduca problemi, ad esempio se Terraform si basa su un endpoint che esiste al di fuori della sua configurazione e quindi viene distrutto manualmente. Terraform non lo sa e quindi non può ricreare questa risorsa durante i cambiamenti di stato, che risulteranno in errori dall'API durante l'esecuzione del piano. Questo non è qualcosa di cui mi preoccuperei; i vantaggi dell'introduzione di Terraform superano di gran lunga i contro.
Per rendere un po' più semplice il processo di introduzione, Terraform include il comando import
. Viene utilizzato per introdurre risorse esterne già esistenti nello stato Terraform e consentirgli di gestire tali risorse. Sfortunatamente, Terraform non è in grado di generare automaticamente le configurazioni per quei moduli importati, almeno al momento della scrittura. Ci sono piani per questa funzionalità, ma per ora devi prima scrivere la definizione della risorsa in Terraform e poi importare questa risorsa per dire a Terraform di iniziare a gestirla. Una volta importato nello stato (e durante la pianificazione dell'esecuzione), vedrai tutte le differenze tra ciò che hai scritto nei file .tf
e ciò che esiste effettivamente nel cloud. In questo modo, puoi modificare ulteriormente la configurazione. Idealmente, non dovrebbero essere visualizzate modifiche, il che significherebbe che la configurazione Terraform riflette ciò che è già sul cloud 1:1.
Moduli
I moduli sono una parte essenziale della configurazione di Terraform e ti consiglio di abbracciarli e usarli frequentemente. Ti forniscono un modo per riutilizzare determinati componenti. Un esempio potrebbe essere il cluster AWS ECS, utilizzato per eseguire i container Docker. Affinché un tale cluster funzioni, sono necessarie molte risorse separate da configurare: il cluster stesso, la configurazione di avvio che gestirebbe istanze EC2 separate, repository di contenitori per le immagini, il gruppo di scalabilità automatica e la policy e così via. In genere è necessario disporre di cluster separati per ambienti e/o applicazioni separati.
Un modo per superare questo problema sarebbe copiare e incollare la configurazione, ma questa è ovviamente una soluzione miope. Faresti errori eseguendo gli aggiornamenti più semplici.
I moduli consentono di incapsulare tutte quelle risorse separate in un blocco di configurazione (chiamato modulo). I moduli definiscono ingressi e uscite che sono le interfacce attraverso le quali comunica con “il mondo esterno” (o il codice chiamante). Inoltre, i moduli possono essere nidificati all'interno di altri moduli, consentendoti di creare rapidamente interi ambienti separati.
Moduli locali e remoti
Analogamente allo stato, puoi avere moduli locali o moduli remoti. I moduli locali sono archiviati insieme alla configurazione di Terraform (in una directory separata, al di fuori di ogni ambiente ma nello stesso repository). Questo va bene se hai un'architettura semplice e non condividi quei moduli.
Questo, tuttavia, ha dei limiti. È difficile eseguire la versione di quei moduli e condividerli. Il controllo delle versioni è importante perché potresti voler utilizzare la v1.0.0 del tuo modulo ECS in produzione ma vorresti sperimentare la v1.1.0 in un ambiente di staging. Se il modulo è stato archiviato insieme al codice, ogni modifica al codice del modulo si rifletterebbe in ogni ambiente (una volta eseguita l' apply
) che di solito è indesiderabile.
Un approccio pratico al controllo delle versioni dei moduli consiste nel metterli tutti in un repository separato, ad esempio your-company/terraform-modules. Quindi, quando fai riferimento a quei moduli all'interno della tua configurazione Terraform, puoi utilizzare un collegamento VCS come origine:
module "my-module" { source = "[email protected]:your-company/terraform-modules.git//modules/my-module?ref=v1.1.0" ... }
Qui sto facendo riferimento a una v1.1.0 di my-module (percorso specifico) che posso testare indipendentemente da altre versioni dello stesso modulo in ambienti diversi.
Oltre a questo, c'è il problema della rilevabilità e della condivisibilità dei moduli. Dovresti sforzarti di scrivere moduli ben documentati e riutilizzabili. Di solito, avrai diverse configurazioni di Terraform per diverse app e potresti voler condividere lo stesso modulo tra di loro. Senza estrarli in un repository separato, sarebbe molto difficile.
Utilizzo dei moduli
I moduli possono essere facilmente referenziati negli ambienti Terraform definendo uno speciale blocco di moduli. Ecco un esempio di tale blocco per un ipotetico modulo ECS:
module "my_service_ecs_cluster" { source = "../modules/ecs_cluster" cluster_name = "my-ecs-service-${var.env}" repository_names = [ "my-ecs-service-${var.env}/api", "my-ecs-service-${var.env}/nginx", "my-ecs-service-${var.env}/docs", ] service_name = "my-ecs-service-${var.env}" ecs_instance_type = "t2.nano" min_size = "1" max_size = "1" use_autoscaling = false alb_target_group_arn = "${module.my_alb.target_group_arn}" subnets = "${local.my_private_subnets}" security_groups = "${aws_security_group.my_ecs.id}" key_name = "${var.key_name}" env_tag = "${var.env}" project_tag = "${var.project_tag}" application_tag = "${var.api_app_tag}" asg_tag = "${var.api_app_tag}-asg" }
Tutte le opzioni che vengono passate (tranne alcune globali come source
) sono state definite all'interno della configurazione di questo modulo come input (variabili).
Registro dei moduli
Recentemente HashiCorp ha lanciato un registro ufficiale dei moduli Terraform. Questa è un'ottima notizia poiché ora puoi attingere dalla conoscenza della comunità che ha già sviluppato moduli testati in battaglia. Inoltre, alcuni di loro hanno il badge "HashiCorp Verified Module", il che significa che sono controllati e mantenuti attivamente e ti danno maggiore sicurezza.
In precedenza, dovevi scrivere i tuoi moduli da zero (e imparare dai tuoi errori) o utilizzare moduli pubblicati su GitHub e altri posti, senza alcuna garanzia sul loro comportamento (a parte leggere il codice!)
Condivisione dei dati tra ambienti
Idealmente, gli ambienti dovrebbero essere completamente separati, anche utilizzando account AWS diversi. In realtà, ci sono casi in cui un ambiente Terraform potrebbe utilizzare alcune informazioni in un altro ambiente. This is especially true if you are gradually converting your architecture to use Terraform. One example might be that you have a global
env that provides certain resources to other envs.
Let's say env global
shares data with stage
. For this to work, you can define outputs at the main level of the environment like so:
output "vpc_id" { value = "${module.network.vpc_id}" }
Then, in the stage
environment, you define a datasource that points to the remote state of global
:
data "terraform_remote_state" "global" { backend = "s3" config { bucket = "my-app-terraform-state" key = "terraform/global" region = "${var.region}" dynamodb_table = "terraform-state-lock-table" profile = "${var.profile}" } }
Now, you can use this datasource as any other and access all the values that were defined in global
's outputs:
vpc_
Words of Caution
Terraform has a lot of pros. I use it daily in production environments and consider it stable enough for such work. Having said that, Terraform is still under active development. Thus, you will stumble on bugs and quirks.
Where to Report Issues and Monitor Changes
First of all, remember: Terraform has a separate core repo and repositories for each provider (eg, AWS). If you encounter issues, make sure to check both the core repo and the separate provider repositories for issues and/or opened pull requests with fixes. GitHub is really the best place to search for bugs and fixes as it is very active and welcoming.
This also means that provider plugins are versioned separately, so make sure you follow their changelogs as well as the core one. Most of the bugs I have encountered were resolved by upgrading the AWS provider which already had a fix.
Can't Cheat Your Way out of Cloud Knowledge
You cannot use Terraform to configure and manage infrastructure if you have no knowledge of how a given provider works. I would say this is a misconception and not a downside, since Terraform has been designed to augment and improve the workflow of configuration management and not to be some magic dust that you randomly sprinkle around and—poof! Environments grow! You still need a solid knowledge of a security model of each cloud, how to write, eg, AWS policies, what resources are available, and how they interact.
Prefer Separate Resources That Are Explicitly linked
There are certain resources—for example, the AWS security group or AWS route table—that allow you to configure the security rules and routes respectively, directly inside their own block. This is tempting, as it looks like less work but in fact will cause you trouble. The problems start when you are changing those rules on subsequent passes. The whole resource will be marked as being changed even if only one route/security rule is being introduced. It also gives implicit ordering to those rules and makes it harder to follow the changes. Thankfully, mixing those both approaches is not allowed now (see the note).
Best-practice example, with explicitly linked resources:
resource "aws_security_group" "my_sg" { name = "${var.app_tag}-my-sg" ... } resource "aws_security_group_rule" "rule_one" { security_group_ ... } resource "aws_security_group_rule" "rule_two" { security_group_ ... }
Terraform plan
Doesn't Always Detect Issues and Conflicts
I already mentioned this in the case where you were managing resources with Terraform that were relying on other, unmanaged infrastructure. But there are more trivial examples—for example, you will get an error if your EC2 instance has Termination Protection enabled, even though plan
would show you it's OK to destroy it. You can argue that this is what Termination Protection has been designed for, and I agree, but there are more examples of things you can do in theory/on plan but when executed will deadlock or error out. For example, you cannot remove a network interface if something is using it—you get a deadlock without an option to gracefully recover.
Syntax Quirks
There are also quirks related to how HCLv1 (the syntax language Terraform uses) has been designed. It has a couple of frustrating quirks. There is work underway to provide an improved version of the parser for HCLv2. The best way to read on the current limitations and the plan to overcome them is this fantastic blog series. In the meantime, there are workarounds for most of those issues. They are not pretty and they will fail once v0.12 comes out, but hey, it is what it is.
When State Update Fails
It sometimes happens that Terraform is not able to correctly push an updated state. This is usually due to underlying network issues. The solution is to retry the state update instead of running apply again, which will fork the state .
Another issue might happen when state lock (the synchronization primitive that prevents multiple users to update the same state) fails to be taken down by Terraform. This involves running terraform force-unlock
with the lock ID to take it down manually.
Thankfully, in case of such problems, Terraform provides you with a good description and steps you need to make to fix it.
Not Everything Is Fun to Manage Through Terraform
There are certain cases where Terraform is not my tool of choice. For example, configuring AWS CodePipeline and CodeBuild projects (AWS equivalent of CI/CD pipeline) is cumbersome when done through Terraform. You need to define each step through very verbose configuration blocks and things like “Login via GitHub” are a lot more complicated than using the UI. Of course, it's still possible if you prefer to have it codified. Well, I guess it's a good candidate for a well-written module!
Same thing goes for managing AWS API Gateway endpoints. In this case, using a dedicated serverless framework would be a better option.
When configuring AWS resources with Terraform, you will find yourself writing a lot of policies. Policies that would otherwise often be auto-generated for you (when using the UI). For those, I'd recommend the AWS Visual Editor and then copying the resulting policy JSON into Terraform.
Conclusione
Using Terraform has been fun and I'll continue doing so. Initial steps can be a bumpy ride, but there are more and more resources that help to ease you in.
I'd definitely recommend taking Terraform for a spin and simply playing with it. Remember, though—be safe and test it out on a non-essential account. If you are eligible for AWS Free Tier, use it as a 12-month free trial. Just be aware it has limitations as to what you can provision. Otherwise, just make sure you spin the cheapest resources, like t3.nano instances.
I highly recommend extensions for Terraform support in various code editors. For Visual Studio Code, there is one with syntax highlighting, formatting, validation and linting support.
It's always valuable to learn new things and evaluate new tools. I found that Terraform helped me immensely in managing my infrastructure. I think working with Terraform will only get easier and more fun, especially once v0.12.0 ships with a major upgrade to the HCL syntax and solve most of the quirks. The traction and community around Terraform are active and vibrant. You can find a lot of great resources on things I didn't manage to cover in a single blogs post, eg, a detailed guide on how to write modules.