Insidie ​​di Terraform

Insidie ​​di Terraform
Scupritemu uni pochi di trappule, cumprese quelle relative à i loops, se dichjarazioni è tecniche di implementazione, è ancu prublemi più generali chì affettanu Terraform in generale:

  • i paràmetri count è for_each anu limitazioni;
  • limità i dispiegamenti zero downtime;
  • ancu un bonu pianu pò fallu;
  • a refactoring pò avè i so trappule;
  • a coerenza differita hè coherente ... cù u differimentu.

I paràmetri count è for_each anu limitazioni

L'esempii in stu capitulu facenu un usu estensivu di u paràmetru count è l'espressione for_each in loops è logica cundizionale. Eseguinu bè, ma anu duie limitazioni impurtanti chì avete bisognu di sapè.

  • Count è for_each ùn ponu micca riferite à alcuna variabile di output di risorse.
  • count è for_each ùn ponu micca esse usatu in a cunfigurazione di u modulu.

count è for_each ùn ponu micca riferite à alcuna variabile di output di risorse

Immaginate chì avete bisognu di implementà parechji servitori EC2 è per una certa ragione ùn vulete micca aduprà ASG. U vostru codice puderia esse cusì:

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

Fighjemu à elli unu à unu.

Siccomu u paràmetru di count hè stabilitu à un valore staticu, stu codice hà da travaglià senza prublemi: quandu eseguite u cumandamentu di l'applicazione, creà trè servitori EC2. Ma chì se vulete implementà un servitore in ogni Zona di Disponibilità (AZ) in a vostra regione AWS attuale? Pudete avè u vostru còdice carricà una lista di zoni da a fonte di dati aws_availability_zones è poi passa per ognuna è crea un servitore EC2 in questu utilizendu u paràmetru di cuntu è l'accessu à l'indexu di array:

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" {}

Stu codice hà da travaglià ancu bè, postu chì u paràmetru di count pò riferite fonti di dati senza prublemi. Ma chì succede se u numeru di servitori chì avete bisognu di creà dipende da a pruduzzioni di qualchì risorsa? Per dimustrà questu, u modu più faciule hè di utilizà a risorsa random_integer, chì, cum'è u nome suggerisce, torna un integer aleatoriu:

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

Stu codice genera un numeru aleatoriu trà 1 è 3. Videmu ciò chì succedi s'ellu ci prova d'utilizà l'output di sta risorsa in u paràmetru di count di a risorsa aws_instance:

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

Sè vo eseguite u pianu terraform nantu à stu codice, uttene u seguente errore:

Error: Invalid count argument

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

The "count" value depends on resource attributes that cannot be determined until apply, so Terraform cannot predict how many instances will be created. To work around this, use the -target argument to first apply only the resources that the count depends on.

Terraform richiede chì count è for_each sia calculatu durante a fase di pianificazione, prima chì qualsiasi risorse sò create o mudificate. Questu significa chì count è for_each ponu riferite à litterali, variàbili, fonti di dati, è ancu listi di risorse (sempre chì a so durata pò esse determinata à u tempu di pianificazione), ma micca à e variabili di output di risorse calculate.

count è for_each ùn ponu micca esse usatu in a cunfigurazione di u modulu

Qualchì ghjornu pudete esse tentatu di aghjunghje un paràmetru di count à a cunfigurazione di u vostru modulu:

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

     count = 3

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

Stu codice prova di utilizà u conte in un modulu per creà trè copie di a risorsa webserver-cluster. O pudete vulete fà a cunnessione di un modulu facultativu basatu annantu à una certa cundizione Booleana, mettendu u so paràmetru di count à 0. Questu pò esse cum'è codice raghjone, ma avete da ottene stu errore quandu eseguite u pianu terraform:

Error: Reserved argument name in module block

   on main.tf line 13, in module "count_example":
   13: count = 3

The name "count" is reserved for use in a future version of Terraform.

Sfortunatamente, da Terraform 0.12.6, l'usu di count o for_each in una risorsa di modulu ùn hè micca supportatu. Sicondu i note di liberazione di Terraform 0.12 (http://bit.ly/3257bv4), HashiCorp pensa à aghjunghje sta capacità in u futuru, cusì secondu quandu avete lettu stu libru, pò esse digià dispunibule. Per sapè di sicuru, leghjite u changelog di Terraform quì.

Limitazioni di implementazioni Zero Downtime

Utilizà u bloccu create_before_destroy in cumbinazione cù ASG hè una grande suluzione per creà implementazioni zero-downtime, eccettu per una caveat: e regule di autoscaling ùn sò micca supportate. O per esse più precisu, questu resetta a dimensione ASG torna à min_size nantu à ogni implementazione, chì puderia esse un prublema s'è vo avete aduprà reguli di autoscaling per aumentà u numeru di servitori in esecuzione.

Per esempiu, u modulu webserver-cluster cuntene un paru di risorse aws_autoscaling_schedule, chì à 9 am aumenta u numeru di servitori in u cluster da dui à deci. Se implementate à, per esempiu, 11 a.m., u novu ASG s'avviarà cù solu dui servitori invece di dece è ferma cusì finu à 9 a.m. u ghjornu dopu.

Questa limitazione pò esse aggirata in parechje manere.

  • Cambia u paràmetru di recurrenza in aws_autoscaling_schedule da 0 9 * * * ("corre à 9 am") à qualcosa cum'è 0-59 9-17 * * * ("corre ogni minutu da 9 am à 5 pm"). Se ASG hà digià dece servitori, eseguisce sta regula autoscaling di novu ùn cambierà nunda, chì hè ciò chì vulemu. Ma se l'ASG hè statu implementatu solu di pocu tempu, sta regula assicurarà chì in un massimu di un minutu u numeru di i so servitori righjunghji dece. Questu ùn hè micca un accostu sanu sanu eleganti, è grandi salti da deci à dui servitori è torna pò ancu causà prublemi per l'utilizatori.
  • Crea un script persunalizatu chì utilizeghja l'AWS API per determinà u numeru di servitori attivi in ​​l'ASG, chjamate cù una fonte di dati esterna (vede "Source di dati esterna" à a pagina 249), è stabilisce u paràmetru di capacità_desirata di l'ASG à u valore restituitu da u script. In questu modu, ogni nova istanza ASG sarà sempre eseguita à a listessa capacità cum'è u codice Terraform esistente è rende più difficiuli di mantene.

Di sicuru, Terraform avissi idealmente un supportu integratu per implementazioni zero-downtime, ma da maghju 2019, a squadra HashiCorp ùn avia micca prughjetti di aghjunghje sta funziunalità (dettagli - quì).

U pianu currettu pò esse implementatu senza successu

A volte, u cumandamentu di u pianu pruduce un pianu di implementazione perfettamente currettu, ma u cumandamentu di l'applicazione torna un errore. Pruvate, per esempiu, aghjunghjendu a risorsa aws_iam_user cù u listessu nome chì avete utilizatu per l'utilizatore IAM chì avete creatu prima in Capitulu 2:

resource "aws_iam_user" "existing_user" {
   # Подставьте сюда имя уже существующего пользователя IAM,
   # чтобы попрактиковаться в использовании команды terraform import
   name = "yevgeniy.brikman"
}

Avà, se eseguite u cumandamentu di u pianu, Terraform hà da pruduce un pianu di implementazione apparentemente ragionevule:

Terraform will perform the following actions:

   # aws_iam_user.existing_user will be created
   + resource "aws_iam_user" "existing_user" {
         + arn                  = (known after apply)
         + force_destroy   = false
         + id                    = (known after apply)
         + name               = "yevgeniy.brikman"
         + path                 = "/"
         + unique_id         = (known after apply)
      }

Plan: 1 to add, 0 to change, 0 to destroy.

Se eseguite u cumandamentu di l'applicazione, uttene u seguente errore:

Error: Error creating IAM User yevgeniy.brikman: EntityAlreadyExists:
User with name yevgeniy.brikman already exists.

   on main.tf line 10, in resource "aws_iam_user" "existing_user":
   10: resource "aws_iam_user" "existing_user" {

U prublema, sicuru, hè chì un utilizatore IAM cù quellu nome esiste digià. È questu pò accade micca solu à l'utilizatori IAM, ma à quasi ogni risorsa. Hè pussibule chì qualchissia hà creatu stu risorsu manualmente o utilizendu a linea di cummanda, ma in ogni modu, l'ID currispondenti porta à cunflitti. Ci hè parechje variazioni di stu errore chì spessu chjappà i novi à Terraform per sorpresa.

U puntu chjave hè chì u cumandamentu di u pianu di terraform piglia solu in cunsiderà quelli risorse chì sò specificati in u schedariu statale di Terraform. Se i risorse sò creati in qualchì altru modu (per esempiu, manualmente clicchendu in a cunsola AWS), ùn finiscinu micca in u schedariu di u statu è per quessa Terraform ùn li pigliarà micca in contu quandu eseguisce u cumandamentu di u pianu. In u risultatu, un pianu chì pareva currettu à u primu sguardu serà un successu.

Ci hè duie lezioni per esse amparatu da questu.

  • Sè avete digià principiatu à travaglià cù Terraform, ùn aduprate micca altru. Se una parte di a vostra infrastruttura hè gestita cù Terraform, ùn pudete più mudificà manualmente. Altrimenti, ùn solu risicheghjanu l'errore Terraform stranu, ma ancu negate parechji di i benefici di IaC postu chì u codice ùn serà più una rapprisintazioni precisa di a vostra infrastruttura.
  • Sè avete digià qualchì infrastruttura, utilizate u cumandamentu d'importazione. Sè avete principiatu à aduprà Terraform cù l'infrastruttura esistente, pudete aghjunghje à u schedariu statale cù u cumandamentu d'importazione di terraform. In questu modu Terraform saperà quale infrastruttura deve esse gestita. U cumandamentu di impurtazione piglia dui argumenti. U primu hè l'indirizzu di risorsa in i vostri schedarii di cunfigurazione. A sintassi quì hè listessa per i ligami di risorse: _. (cum'è aws_iam_user.existing_user). U sicondu argumentu hè l'ID di a risorsa da esse impurtata. Diciamu chì l'ID di risorsa aws_iam_user hè u nome d'utilizatore (per esempiu, yevgeniy.brikman), è l'ID di risorsa aws_instance hè l'ID di u servitore EC2 (cum'è i-190e22e5). Cumu impurtà una risorsa hè di solitu indicata in a documentazione in u fondu di a so pagina.

    Quì sottu hè un cumandamentu d'importazione chì sincronizza a risorsa aws_iam_user chì avete aghjuntu à a vostra cunfigurazione Terraform cù l'utilizatori IAM in u Capitulu 2 (sustituendu u vostru nome per yevgeniy.brikman, sicuru):

    $ terraform import aws_iam_user.existing_user yevgeniy.brikman

    Terraform chjamà l'API AWS per truvà u vostru utilizatore IAM è creà una associazione di file statali trà questu è a risorsa aws_iam_user.existing_user in a vostra cunfigurazione Terraform. Da avà, quandu eseguite u cumandamentu di u pianu, Terraform hà da sapè chì l'utente IAM esiste digià è ùn pruvarà micca di creà novu.

    Hè da nutà chì s'è vo avete digià assai risorsi chì vo vulete impurtà in Terraform, scriviri manually u codice è impurtà ognunu unu à un tempu pò esse un fastidiu. Allora vale a pena cercà un strumentu cum'è Terraforming (http://terraforming.dtan4.net/), chì pò impurtà automaticamente codice è statu da u vostru contu AWS.

    A refactoring pò avè i so trappule

    Refactoring hè una pratica cumuna in a prugrammazione induve cambiate a struttura interna di u codice mentre lassendu u cumpurtamentu esternu senza cambià. Questu hè per fà u codice più chjaru, più pulitu, è più faciule da mantene. A refactoring hè una tecnica indispensabile chì deve esse usata regularmente. Ma quandu si tratta di Terraform o di qualsiasi altru strumentu IaC, avete da esse assai attenti à ciò chì vulete dì da u "cumportamentu esternu" di un pezzu di codice, altrimente i prublemi inespettati.

    Per esempiu, un tipu cumuni di refactoring hè di rimpiazzà i nomi di variàbili o funzioni cù quelli più comprensibili. Parechji IDE anu un supportu integratu per a refactoring è ponu automaticamente rinominà variabili è funzioni in tuttu u prugettu. In i linguaggi di prugrammazione generale, questu hè una prucedura triviale chì ùn puderebbe micca pensà, ma in Terraform avete da esse assai attenti cù questu, altrimenti pudete sperienze outages.

    Per esempiu, u modulu webserver-cluster hà una variabile di input cluster_name:

    variable "cluster_name" {
       description = "The name to use for all the cluster resources"
       type          = string
    }

    Imagine chì avete cuminciatu à aduprà stu modulu per implementà un microserviziu chjamatu foo. In seguitu, vulete rinominà u vostru serviziu à bar. Stu cambiamentu pò parè triviale, ma in realtà pò causà disrupzioni di serviziu.

    U fattu hè chì u modulu webserver-cluster usa a variàbile cluster_name in una quantità di risorse, cumpresu u paràmetru di nome di dui gruppi di sicurità è 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 cambiate u paràmetru di nome nantu à una risorsa, Terraform sguasserà a versione antica di quella risorsa è creà una nova in u so locu. Ma se quella risorsa hè un ALB, trà l'eliminazione è a scaricazione di una nova versione, ùn avete micca un mecanismu per redirige u trafficu à u vostru servitore web. In listessu modu, se un gruppu di sicurità hè sguassatu, i vostri servitori cumincianu à ricusà ogni trafficu di rete finu à chì un novu gruppu hè creatu.

    Un altru tipu di refactoring chì pudete esse interessatu hè cambià l'ID Terraform. Pigliemu a risorsa aws_security_group in u modulu webserver-cluster cum'è un esempiu:

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

    L'identificatore di sta risorsa hè chjamatu istanza. Imagine chì durante a refactoring avete decisu di cambià in un nome più comprensibile (in u vostru parè) cluster_instance:

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

    Chì succede à a fine ? Hè ghjustu: una disrupzione.

    Terraform associa ogni ID di risorsa cù l'ID di u fornitore di nuvola. Per esempiu, iam_user hè assuciatu cù l'ID d'utilizatore AWS IAM, è aws_instance hè assuciatu cù l'ID di u servitore AWS EC2. Se cambiate l'ID di risorsa (per dì da l'istanza à cluster_instance, cum'è u casu cù aws_security_group), à Terraform apparirà cum'è s'è vo sguassate u vechju risorsu è aghjustatu un novu. Se applicate sti cambiamenti, Terraform sguasserà u vechju gruppu di sicurità è creà un novu, mentre chì i vostri servitori cumincianu à ricusà ogni trafficu di rete.

    Eccu quattru lezioni chjave chì duvete piglià da sta discussione.

    • Aduprate sempre u cumandamentu di u pianu. Puderà revelà tutti questi snags. Revisate a so pruduzzione cù cura è fate attenzione à e situazioni induve Terraform pensa à sguassà risorse chì più prubabilmente ùn deve micca esse sguassate.
    • Crea prima di sguassà. Se vulete rimpiazzà una risorsa, pensate bè s'ellu ci vole à creà un sustitutu prima di sguassà l'uriginale. Se a risposta hè sì, create_before_destroy pò aiutà. U listessu risultatu pò esse ottenutu manualmente eseguendu dui passi: prima aghjunghje una nova risorsa à a cunfigurazione è eseguite u cumandamentu di l'applicazione, è poi sguassate u vechju risorsu da a cunfigurazione è utilizate u cumandamentu di appiecà di novu.
    • U cambiamentu di l'identificatori richiede un cambiamentu di statu. Se vulete cambià l'ID assuciatu cù una risorsa (per esempiu, rinominate aws_security_group da l'istanza à cluster_instance) senza sguassà a risorsa è creà una nova versione di questu, duvete aghjurnà u schedariu di statu Terraform in cunseguenza. Ùn fate mai questu manualmente - utilizate u cumandamentu di u statu terraform invece. Quandu rinominà l'identificatori, duvete eseguisce u cumandimu terraform state mv, chì hà a sintassi seguente:
      terraform state mv <ORIGINAL_REFERENCE> <NEW_REFERENCE>

      ORIGINAL_REFERENCE hè una espressione chì si riferisce à a risorsa in a so forma attuale, è NEW_REFERENCE hè induve vulete spustà. Per esempiu, quandu si rinomina u gruppu aws_security_group da l'istanza à cluster_instance, avete bisognu di eseguisce u cumandimu seguente:

      $ terraform state mv 
         aws_security_group.instance 
         aws_security_group.cluster_instance

      Questu dici à Terraform chì u statu chì era prima assuciatu cù aws_security_group.instance deve avà esse assuciatu cù aws_security_group.cluster_instance. Sè dopu à rinomina è eseguisce stu pianu di terraform di cumanda ùn mostra micca cambiamenti, allora avete fattu tuttu bè.

    • Certi paràmetri ùn ponu esse cambiati. I paràmetri di parechje risorse sò immubiliabili. Se pruvate à cambià, Terraform sguasserà u vechju risorsu è creà un novu in u so locu. Ogni pagina di risorsa di solitu indicà ciò chì succede quandu cambiate un paràmetru particulare, cusì assicuratevi di verificà a documentazione. Aduprate sempre u cumandamentu di u pianu è cunsiderà aduprà a strategia create_before_destroy.

    A coerenza differita hè coherente ... cù u differimentu

    Certi API di i fornitori di nuvola, cum'è AWS, sò asincroni è anu ritardatu a coerenza. L'asincronia significa chì l'interfaccia pò rinvià immediatamente una risposta senza aspittà chì l'azzione dumandata finisci. A cunsistenza ritardata significa chì i cambiamenti ponu piglià tempu per propagate in tuttu u sistema; mentre chì questu succede, e vostre risposte ponu esse inconsistenti è dipendenu da quale a replica di a fonte di dati risponde à e vostre chjama API.

    Imagine, per esempiu, chì fate una chjama API à AWS per dumandà à creà un servitore EC2. L'API restituverà una risposta "successu" (201 Created) quasi istantaneamente, senza aspittà chì u servitore stessu sia creatu. Se pruvate à cunnetta subitu, quasi certamenti fallerà perchè à quellu puntu AWS hè sempre inizializzando risorse o, in alternativa, u servitore ùn hà micca ancu avviatu. Inoltre, se fate una altra chjama per uttene infurmazioni nantu à stu servitore, pudete riceve un errore (404 Not Found). A cosa hè chì l'infurmazioni nantu à stu servitore EC2 pò ancu esse propagatu in tutta AWS prima ch'ella sia dispunibule in ogni locu, avete da aspittà uni pochi seconde.

    Ogni volta chì aduprate una API asincrona cù una coerenza pigra, duvete ripruvà periodicamente a vostra dumanda finu à chì l'azzione finisce è si propaga à traversu u sistema. Sfurtunatamente, l'AWS SDK ùn furnisce micca boni strumenti per questu, è u prughjettu Terraform soffre assai di bug cum'è 6813 (https://github.com/hashicorp/terraform/issues/6813):

    $ terraform apply
    aws_subnet.private-persistence.2: InvalidSubnetID.NotFound:
    The subnet ID 'subnet-xxxxxxx' does not exist

    In altri palori, create una risorsa (cum'è una subnet) è poi pruvate d'avè qualchì infurmazione nantu à questu (cum'è l'ID di a subnet appena creata), è Terraform ùn pò micca truvà. A maiò parte di sti bugs (cumpresu 6813) sò stati riparati, ma sò sempre cresce da u tempu à u tempu, soprattuttu quandu Terraform aghjunghje supportu per un novu tipu di risorsa. Questu hè fastidiosu, ma in a maiò parte di i casi ùn causa micca dannu. Quandu si eseguite terraform appiicazione di novu, tuttu duverebbe travaglià, postu chì da questu tempu l'infurmazioni seranu digià sparghje in tuttu u sistema.

    Stu strattu hè prisentatu da u libru di Evgeniy Brikman "Terraform: infrastruttura à u livellu di codice".

Source: www.habr.com

Add a comment