I punti critici di Terraform

I punti critici di Terraform
Evidenziamo alcuni punti critici, inclusi quelli legati a cicli, espressioni if e metodologie di distribuzione, oltre a problemi più generali riguardanti Terraform nel suo complesso:

  • i parametri count e for_each hanno limitazioni;
  • limitazioni nelle distribuzioni con zero tempo di inattività;
  • anche un buon piano può rivelarsi un fallimento;
  • il refactoring può presentare insidie;
  • la coerenza ritardata si accorda... con il ritardo.

I parametri count e for_each hanno limitazioni

Negli esempi di questo capitolo, il parametro count e l'espressione for_each sono attivamente utilizzati in cicli e logica condizionale. Funzionano bene, ma presentano due importanti limitazioni di cui è necessario essere a conoscenza.

  • In count e for_each non è possibile fare riferimento a variabili di output di risorsa.
  • count e for_each non possono essere utilizzati nella configurazione del modulo.

In count e for_each non è possibile fare riferimento a variabili di output di risorsa

Immagina di dover distribuire più server EC2 e per qualche motivo non vuoi usare ASG. Il tuo codice potrebbe essere il seguente:

resource "aws_instance" "example_1" {
   count             = 3
   ami                = "ami-0c55b159cbfafe1f0"
   instance_type = "t2.micro"
}

Esaminiamoli uno per uno.

Poiché il parametro count ha un valore statico, questo codice funzionerà senza problemi: quando esegui il comando apply, verranno creati tre server EC2. Ma se desideri distribuire un server in ciascuna zona di disponibilità (Availability Zone o AZ) all'interno della regione AWS corrente? Puoi far sì che il tuo codice carichi l'elenco delle zone da una fonte di dati aws_availability_zones e poi 'ciclare' attraverso ciascuna di esse creando un server EC2 in ognuna, utilizzando il parametro count e l'accesso all'array tramite indice:

resource "aws_instance" "example_2" {
   count                   = length(data.aws_availability_zones.all.names)
   availability_zone   = data.aws_availability_zones.all.names[count.index]
   ami                     = "ami-0c55b159cbfafe1f0"
   instance_type       = "t2.micro"
}

data "aws_availability_zones" "all" {}

Anche questo codice funzionerà bene, poiché il parametro count può riferirsi senza problemi alle fonti di dati. Ma cosa succede se il numero di server che devi creare dipende dall'output di una risorsa? Per dimostrarlo, è più semplice prendere la risorsa random_integer, che, come si può intuire dal nome, restituisce un numero intero casuale:

resource "random_integer" "num_instances" {
  min = 1
  max = 3
}

Questo codice genera un numero casuale da 1 a 3. Vediamo cosa succede se proviamo a utilizzare l'output result di questa risorsa nel parametro count della risorsa aws_instance:

resource "aws_instance" "example_3" {
   count             = random_integer.num_instances.result
   ami                = "ami-0c55b159cbfafe1f0"
   instance_type = "t2.micro"
}

Se esegui questo codice terraform plan, otterrai il seguente errore:

Error: Invalid count argument

   on main.tf line 30, in resource "aws_instance" "example_3":
   30: count = random_integer.num_instances.result

Il valore "count" dipende dagli attributi della risorsa che non possono essere determinati fino all'applicazione, quindi Terraform non può prevedere quanti istanze verranno create. Per aggirare questo, usa l'argomento -target per applicare prima solo le risorse da cui dipende il count.

Terraform richiede che count e for_each vengano calcolati nella fase di pianificazione, prima di creare o modificare qualsiasi risorsa. Ciò significa che count e for_each possono fare riferimento a letterali, variabili, sorgenti dati e persino elenchi di risorse (a condizione che la loro lunghezza possa essere determinata durante la pianificazione), ma non a variabili di output calcolate dalla risorsa.

count e for_each non possono essere utilizzati nella configurazione del modulo

Potresti avere la tentazione di aggiungere il parametro count nelle configurazioni del modulo:

modulo "count_example" {
     source = "../../../../modules/services/webserver-cluster"

     count = 3

     cluster_name = "terraform-up-and-running-example"
     server_port = 8080
     instance_type = "t2.micro"
}

Questo codice cerca di utilizzare count all'interno del modulo per creare tre copie della risorsa webserver-cluster. Oppure, potresti voler rendere l'inclusione del modulo opzionale in base a una qualche condizione booleana, assegnando il valore 0 al parametro count. Questo codice sembra ragionevole, tuttavia, eseguendo terraform plan riceverai il seguente errore:

Errore: Nome dell'argomento riservato nel blocco del modulo

   su main.tf riga 13, nel modulo "count_example":
   13: count = 3

Il nome "count" è riservato per un uso in una futura versione di Terraform.

Sfortunatamente, al momento del rilascio di Terraform 0.12.6, l'uso di count o for_each nelle risorse del modulo non è supportato. Secondo le note di rilascio di Terraform 0.12 (http://bit.ly/3257bv4), HashiCorp prevede di aggiungere questa funzionalità in futuro, quindi, a seconda di quando leggi questo libro, potrebbe già essere disponibile. Per saperne di più, controlla il changelog di Terraform qui.

Limitazioni delle distribuzioni con tempo di inattività nullo

L'utilizzo del blocco create_before_destroy insieme ad ASG è una soluzione eccellente per gestire i deployment senza tempi di inattività, considerando un'unica eccezione: le regole di scalabilità automatica non vengono supportate. In altre parole, questo ripristina la dimensione dell'ASG a min_size ad ogni deployment, il che può diventare un problema se hai utilizzato le regole di scalabilità automatica per aumentare il numero di server in esecuzione.

Ad esempio, il modulo webserver-cluster contiene un paio di risorse aws_autoscaling_schedule che alle 9 del mattino aumentano il numero di server nel cluster da due a dieci. Se esegui un deployment, diciamo, alle 11 del mattino, il nuovo gruppo ASG si caricherà non con dieci, ma solo con due server e rimarrà in questo stato fino alle 9 del mattino del giorno successivo.

Questa limitazione può essere aggirata in diversi modi.

  • Cambia il parametro recurrence in aws_autoscaling_schedule da 0 9 * * * («esegui alle 9 del mattino») a qualcosa come 0-59 9-17 * * * («esegui ogni minuto dalle 9 del mattino alle 5 di pomeriggio»). Se ci sono già dieci server nell'ASG, rieseguire questa regola di auto-scaling non cambierà nulla, ed è proprio ciò di cui abbiamo bisogno. Tuttavia, se il gruppo ASG è stato appena distribuito, questa regola garantisce che entro un minuto il numero di server raggiunga dieci. Questo non è esattamente un approccio elegante, e i grandi salti da dieci a due server e viceversa possono causare problemi agli utenti.
  • Crea uno script personalizzato che utilizza l'API AWS per determinare il numero di server attivi nell'ASG, chiamalo tramite una fonte di dati esterna (vedi il punto «Fonte di dati esterna» a pagina 249) e assegna al parametro desired_capacity del gruppo ASG il valore restituito da questo script. In questo modo, ogni nuova istanza dell'ASG verrà sempre avviata con la stessa capacità, come nel nostro codice Terraform, rendendo la sua manutenzione più complessa.

Certo, idealmente Terraform dovrebbe avere supporto integrato per le distribuzioni con zero downtime, ma a maggio 2019 il team di HashiCorp non pianificava di aggiungere questa funzionalità (dettagli — qui).

Un piano corretto può essere implementato in modo errato

A volte, eseguendo il comando plan, si ottiene un piano di distribuzione apparentemente corretto, ma il comando apply restituisce un errore. Prova, ad esempio, ad aggiungere una risorsa aws_iam_user con lo stesso nome che hai usato per l'utente IAM creato in precedenza nel capitolo 2:

resource "aws_iam_user" "existing_user" {
   # Inserisci qui il nome già esistente dell'utente IAM,
   # per esercitarti con il comando terraform import
   name = "yevgeniy.brikman"
}

Ora, se esegui il comando plan, Terraform produrrà un piano di distribuzione apparentemente ragionevole:

Terraform eseguirà le seguenti azioni:

   # aws_iam_user.existing_user sarà creato
   + resource "aws_iam_user" "existing_user" {
         + arn                  = (conosciuto dopo l'applicazione)
         + force_destroy   = false
         + id                    = (conosciuto dopo l'applicazione)
         + name               = "yevgeniy.brikman"
         + path                 = "/"
         + unique_id         = (conosciuto dopo l'applicazione)
      }

Piano: 1 da aggiungere, 0 da modificare, 0 da distruggere.

Se si esegue il comando apply, si verificherà il seguente errore:

Errore: Errore durante la creazione dell'utente IAM yevgeniy.brikman: EntityAlreadyExists:
L'utente con nome yevgeniy.brikman esiste già.

   su main.tf riga 10, nella risorsa "aws_iam_user" "existing_user":
   10: risorsa "aws_iam_user" "existing_user" {

Il problema, ovviamente, è che esiste già un utente IAM con quel nome. E questo può accadere non solo con gli utenti IAM, ma anche con quasi qualsiasi risorsa. È possibile che qualcuno abbia creato questa risorsa manualmente o tramite la riga di comando, ma in ogni caso, la corrispondenza degli identificatori porta a conflitti. Ci sono molte varianti di questo errore che spesso sorprendono i neofiti di Terraform.

Punto chiave è che il comando terraform plan considera solo le risorse indicate nel file di stato di Terraform. Se le risorse sono state create in un altro modo (ad esempio, manualmente, cliccando nel pannello di controllo AWS), non verranno incluse nel file di stato e, di conseguenza, Terraform non le considererà durante l'esecuzione del comando plan. Di conseguenza, un piano che appare corretto a prima vista risulterà fallito.

Da ciò possiamo trarre due lezioni.

  • Se hai già iniziato a lavorare con Terraform, non usare nient'altro. Se una parte della tua infrastruttura è gestita tramite Terraform, non puoi più modificarla manualmente. Altrimenti, rischi di ottenere errori strani da Terraform e vanifichi molti dei vantaggi di IaC, poiché il codice non sarà più una rappresentazione accurata della tua infrastruttura.
  • Se hai già un'infrastruttura, usa il comando import. Se stai iniziando a utilizzare Terraform con un'infrastruttura preesistente, puoi aggiungerla nel file di stato usando il comando terraform import. In questo modo, Terraform saprà quale infrastruttura gestire. Il comando import accetta due argomenti. Il primo è l'indirizzo della risorsa nei tuoi file di configurazione. Qui si utilizza la stessa sintassi delle referenze alle risorse: _. (come aws_iam_user.existing_user). Il secondo argomento è l'identificativo della risorsa da importare. Ad esempio, nell'ID della risorsa aws_iam_user, il nome utente (come yevgeniy.brikman) rappresenta l'ID, mentre nell'ID della risorsa aws_instance, sarà l'identificativo del server EC2 (come i-190e22e5). Le istruzioni su come importare una risorsa sono generalmente indicate nella documentazione in fondo alla sua pagina.

    Di seguito è riportato il comando import che consente di sincronizzare la risorsa aws_iam_user, che hai aggiunto alla tua configurazione Terraform insieme all'utente IAM nel capitolo 2 (naturalmente, invece di yevgeniy.brikman, inserisci il tuo nome):

    $ terraform import aws_iam_user.existing_user yevgeniy.brikman

    Terraform si interfaccia con l'API AWS per trovare il tuo utente IAM e creare nel file di stato un collegamento tra esso e la risorsa aws_iam_user.existing_user nella tua configurazione Terraform. Da questo momento in poi, quando esegui il comando plan, Terraform saprà che l'utente IAM esiste già e non tenterà di crearne un altro.

    È importante notare che se hai già molte risorse che desideri importare in Terraform, scrivere manualmente il codice e importare ciascuna di esse uno alla volta può diventare un compito laborioso. Pertanto, vale la pena considerare uno strumento come Terraforming (http://terraforming.dtan4.net/), che può importare automaticamente il codice e lo stato dal tuo account AWS.

    Il refactoring può avere le sue insidie

    Refactoring Il refactoring è una pratica comune nella programmazione in cui si modifica la struttura interna del codice lasciando invariato il comportamento esterno. Questo è necessario per rendere il codice più chiaro, ordinato e facile da mantenere. Il refactoring è una tecnica indispensabile che dovrebbe essere applicata regolarmente. Tuttavia, quando si parla di Terraform o di qualsiasi altro strumento IaC, è fondamentale prestare particolare attenzione a cosa si intende per "comportamento esterno" di un segmento di codice, altrimenti possono sorgere problemi imprevisti.

    Ad esempio, un comune tipo di refactoring consiste nella sostituzione dei nomi di variabili o funzioni con nomi più comprensibili. Molti IDE offrono supporto integrato per il refactoring e possono rinominare automaticamente variabili e funzioni in tutto il progetto. Nei linguaggi di programmazione generali, questa è una procedura banale di cui non preoccuparsi, ma in Terraform è necessario essere estremamente cauti, altrimenti si può incorrere in interruzioni del servizio.

    Ad esempio, il modulo webserver-cluster ha una variabile di ingresso cluster_name:

    variable "cluster_name" {
       description = "Il nome da usare per tutte le risorse del cluster"
       type          = string
    }

    Immagina di iniziare a utilizzare questo modulo per distribuire un microservizio chiamato foo. In seguito, desideri rinominare il tuo servizio in bar. Questa modifica potrebbe sembrare banale, ma in realtà può causare interruzioni del servizio.

    Il fatto è che il modulo webserver-cluster utilizza la variabile cluster_name in un certo numero di risorse, comprese il parametro name di due gruppi di sicurezza e l'ALB:

    resource "aws_lb" "example" {
       name                    = var.cluster_name
       load_balancer_type = "application"
       subnets = data.aws_subnet_ids.default.ids
       security_groups      = [aws_security_group.alb.id]
    }

    Se cambi il parametro name in una risorsa, Terraform eliminerà la versione precedente di quella risorsa e ne creerà una nuova. Ma se tale risorsa è un ALB, nel periodo tra la sua eliminazione e il caricamento della nuova versione, non avrai un meccanismo per reindirizzare il traffico al tuo server web. Allo stesso modo, se viene eliminato un gruppo di sicurezza, i tuoi server inizieranno a rifiutare qualsiasi traffico di rete finché non verrà creato un nuovo gruppo.

    Un altro tipo di refactoring che potrebbe interessarti è la modifica dell'identificatore Terraform. Prendiamo come esempio la risorsa aws_security_group nel modulo webserver-cluster:

    resource "aws_security_group" "instance" {
      # (...)
    }

    L'identificatore di questa risorsa si chiama instance. Immagina che durante il refactoring tu abbia deciso di cambiarlo in un nome più chiaro (secondo te) cluster_instance:

    resource "aws_security_group" "cluster_instance" {
       # (...)
    }

    Cosa succederà alla fine? Esatto: un'interruzione del servizio.

    Terraform associa l'ID di ogni risorsa all'identificatore del provider cloud. Ad esempio, iam_user è legato all'ID utente IAM in AWS, e aws_instance è legato all'ID del server AWS EC2. Se modifichi l'identificatore di una risorsa (diciamo, da instance a cluster_instance, come nel caso di aws_security_group), per Terraform sembrare che tu abbia rimosso la vecchia risorsa e aggiunto una nuova. Se applichi queste modifiche, Terraform eliminerà il vecchio gruppo di sicurezza e creerà un altro, mentre i tuoi server inizieranno a rifiutare qualsiasi traffico di rete.

    Ecco quattro lezioni principali che dovresti trarre da questa discussione.

    • Utilizza sempre il comando plan. Questo ti permette di identificare eventuali problemi. Controlla attentamente il suo output e presta attenzione ai casi in cui Terraform programma di rimuovere risorse che non dovrebbero essere eliminate.
    • Crea prima di eliminare. Se desideri sostituire una risorsa, valuta attentamente se è necessario creare una sostituzione prima di rimuovere l'originale. Se la risposta è positiva, puoi utilizzare create_before_destroy. Puoi ottenere lo stesso risultato manualmente eseguendo due passaggi: prima aggiungi la nuova risorsa nella configurazione e avvia il comando apply, quindi rimuovi la risorsa obsoleta dalla configurazione e utilizza nuovamente il comando apply.
    • La modifica degli identificatori richiede un aggiornamento dello stato. Se desideri cambiare l'identificatore associato a una risorsa (ad esempio, rinominare aws_security_group da instance a cluster_instance) evitando di eliminare la risorsa e crearne una nuova versione, è necessario aggiornare il file di stato di Terraform di conseguenza. Non farlo mai manualmente: utilizza invece il comando terraform state. Quando rinomini gli identificatori, devi eseguire il comando terraform state mv, che ha la seguente sintassi:
      terraform state mv

      ORIGINAL_REFERENCE è un'espressione che fa riferimento alla risorsa nella sua forma attuale, mentre NEW_REFERENCE è il luogo in cui desideri spostarla. Ad esempio, quando rinomini il gruppo aws_security_group da instance a cluster_instance, devi eseguire il seguente comando:

      $ terraform state mv 
         aws_security_group.instance 
         aws_security_group.cluster_instance

      In questo modo comunicherai a Terraform che lo stato, che in precedenza si riferiva a aws_security_group.instance, ora deve essere associato a aws_security_group.cluster_instance. Se dopo aver rinominato e eseguito questo comando terraform plan non mostra alcuna modifica, significa che hai fatto tutto correttamente.

    • Alcuni parametri non possono essere modificati. I parametri di molte risorse sono immutabili. Se provi a modificarli, Terraform eliminerà la vecchia risorsa e ne creerà una nuova al suo posto. Di solito, nella pagina di ogni risorsa è indicato cosa accade quando si modifica un certo parametro, quindi non dimenticare di consultare la documentazione. Usa sempre il comando plan e considera la fattibilità della strategia create_before_destroy.

    La coerenza ritardata è… ritardata

    Le API di alcuni fornitori di cloud, come AWS, sono asincrone e presentano coerenza ritardata. L'asincronicità significa che l'interfaccia può restituire immediatamente una risposta senza attendere il completamento dell'azione richiesta. La coerenza ritardata implica che potrebbero essere necessari del tempo per propagare le modifiche in tutto il sistema; durante questo processo, le tue risposte potrebbero essere incoerenti e dipendere da quale replica della fonte dati risponde alle tue chiamate API.

    Immaginate, ad esempio, di effettuare una chiamata API ad AWS per creare un server EC2. L'API restituirà una risposta "successo" (201 Created) praticamente istantaneamente, senza attendere la creazione effettiva del server. Se provate immediatamente a connettervi, probabilmente non riuscirete, poiché in quel momento AWS sta ancora inizializzando le risorse oppure il server non è ancora avviato. Inoltre, se effettuate un'altra chiamata per ottenere informazioni su questo server, potrebbe arrivare un errore (404 Not Found). Il fatto è che le informazioni su questo server EC2 potrebbero essere ancora in fase di diffusione attraverso AWS, e sarà necessario attendere alcuni secondi affinché diventino disponibili ovunque.

    Ogni volta che utilizzate un'API asincrona con coerenza ritardata, dovete ripetere periodicamente la vostra richiesta fino a quando l'azione non è completata e non si è propagata nel sistema. Sfortunatamente, AWS SDK non fornisce strumenti utili per questo, e il progetto Terraform ha sofferto in passato di numerosi bug come il 6813 (https://github.com/hashicorp/terraform/issues/6813):

    $ terraform apply
    aws_subnet.private-persistence.2: InvalidSubnetID.NotFound:
    L'ID della subnet 'subnet-xxxxxxx' non esiste

    In altre parole, stai creando una risorsa (ad esempio, una sottorete) e poi stai cercando di ottenere alcune informazioni su di essa (come l'ID della sottorete appena creata), ma Terraform non riesce a trovarle. La maggior parte di questi errori (incluso il 6813) è già stata corretta, ma di tanto in tanto si manifestano ancora, specialmente quando in Terraform viene aggiunto il supporto per un nuovo tipo di risorsa. È frustrante, ma nella maggior parte dei casi non causa danni. Quando ripeti il comando terraform apply, tutto dovrebbe funzionare, poiché a quel punto le informazioni saranno già propagate nel sistema.

    Questo passaggio è tratto dal libro di Evgeny Brikman «Terraform: infrastruttura a livello di codice».

Fonte: habr.com

Acquista hosting affidabile per siti web con protezione DDoS, VPS VDS server 🔥 Acquista hosting affidabile per siti web con protezione DDoS, VPS VDS server | ProHoster