Terraform pasti

Terraform pasti
Poudarimo nekaj pasti, vključno s tistimi, ki so povezane z zankami, stavki if in tehnikami uvajanja, pa tudi bolj splošnimi težavami, ki na splošno vplivajo na Terraform:

  • parametra count in for_each imata omejitve;
  • omejitev uvajanja brez izpadov;
  • tudi dober načrt lahko spodleti;
  • refactoring ima lahko svoje pasti;
  • odložena skladnost je skladna... z odlogom.

Parametra count in for_each imata omejitve

Primeri v tem poglavju v veliki meri uporabljajo parameter count in izraz for_each v zankah in pogojni logiki. Delujejo dobro, vendar imajo dve pomembni omejitvi, ki se ju morate zavedati.

  • Count in for_each se ne moreta sklicevati na nobeno izhodno spremenljivko vira.
  • count in for_each ni mogoče uporabiti v konfiguraciji modula.

count in for_each se ne moreta sklicevati na nobeno izhodno spremenljivko vira

Predstavljajte si, da morate namestiti več strežnikov EC2 in iz neznanega razloga ne želite uporabljati ASG. Vaša koda je lahko takšna:

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

Poglejmo jih enega za drugim.

Ker je parameter štetja nastavljen na statično vrednost, bo ta koda delovala brez težav: ko zaženete ukaz za uporabo, bo ustvaril tri strežnike EC2. Kaj pa, če bi želeli namestiti en strežnik v vsako območje razpoložljivosti (AZ) v vaši trenutni regiji AWS? Vaša koda lahko naloži seznam območij iz vira podatkov aws_availability_zones in nato preleti vsako od njih ter v njem ustvari strežnik EC2 z uporabo parametra štetja in dostopa do indeksa polja:

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

Tudi ta koda bo dobro delovala, saj se parameter štetja lahko brez težav sklicuje na vire podatkov. Toda kaj se zgodi, če je število strežnikov, ki jih morate ustvariti, odvisno od izhoda nekega vira? Za prikaz tega je najlažji način uporaba vira random_integer, ki, kot že ime pove, vrne naključno celo število:

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

Ta koda ustvari naključno število med 1 in 3. Poglejmo, kaj se zgodi, če poskusimo uporabiti izhod tega vira v parametru štetja vira aws_instance:

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

Če zaženete načrt terraform na tej kodi, boste prejeli naslednjo napako:

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 zahteva, da se count in for_each izračunata med fazo načrtovanja, preden se ustvarijo ali spremenijo kakršni koli viri. To pomeni, da se lahko count in for_each nanašata na literale, spremenljivke, vire podatkov in celo sezname virov (če je njihovo dolžino mogoče določiti v času načrtovanja), ne pa tudi na izračunane izhodne spremenljivke vira.

count in for_each ni mogoče uporabiti v konfiguraciji modula

Nekega dne vas bo morda zamikalo, da svoji konfiguraciji modula dodate parameter štetja:

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

     count = 3

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

Ta koda poskuša uporabiti count znotraj modula za ustvarjanje treh kopij vira gruče spletnega strežnika. Ali pa boste morda želeli narediti povezovanje modula izbirno, odvisno od nekega logičnega pogoja, tako da nastavite njegov parameter štetja na 0. To je morda videti kot razumna koda, vendar boste pri izvajanju načrta terraform prejeli to napako:

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.

Na žalost od Terraform 0.12.6 uporaba count ali for_each v viru modula ni podprta. Glede na opombe ob izdaji Terraform 0.12 (http://bit.ly/3257bv4) namerava HashiCorp to zmožnost dodati v prihodnosti, tako da bo morda že na voljo, odvisno od tega, kdaj boste prebrali to knjigo. Da bi zagotovo izvedeli, preberite dnevnik sprememb Terraform tukaj.

Omejitve uvajanja brez izpadov

Uporaba bloka create_before_destroy v kombinaciji z ASG je odlična rešitev za ustvarjanje uvedb brez izpadov, razen enega opozorila: pravila samodejnega skaliranja niso podprta. Ali če smo natančnejši, to ponastavi velikost ASG nazaj na min_size ob vsaki uvedbi, kar bi lahko bila težava, če bi uporabljali pravila samodejnega skaliranja za povečanje števila delujočih strežnikov.

Na primer, modul webserver-cluster vsebuje par virov aws_autoscaling_schedule, ki ob 9. uri poveča število strežnikov v gruči z dveh na deset. Če uvedete na primer ob 11. uri, se bo novi ASG zagnal samo z dvema strežnikoma namesto z desetimi in bo tako ostal do 9. ure zjutraj naslednjega dne.

To omejitev je mogoče zaobiti na več načinov.

  • Spremenite parameter ponavljanja v aws_autoscaling_schedule iz 0 9 * * * (»zagon ob 9«) na nekaj podobnega 0-59 9-17 * * * (»zagon vsako minuto od 9 do 5«). Če ima ASG že deset strežnikov, ponovno izvajanje tega pravila samodejnega skaliranja ne bo spremenilo ničesar, kar želimo. Če pa je bil ASG nameščen šele pred kratkim, bo to pravilo zagotovilo, da bo v največ eni minuti število njegovih strežnikov doseglo deset. To ni povsem eleganten pristop, uporabnikom pa lahko težave povzročajo tudi veliki skoki z desetih na dva strežnika in nazaj.
  • Ustvarite skript po meri, ki uporablja API AWS za določanje števila aktivnih strežnikov v ASG, ga pokličite z uporabo zunanjega vira podatkov (glejte »Zunanji vir podatkov« na strani 249) in nastavite parameter ASG desire_capacity na vrednost, ki jo vrne scenarij. Na ta način bo vsaka nova instanca ASG vedno delovala z enako zmogljivostjo kot obstoječa koda Terraform in jo bo težje vzdrževati.

Seveda bi bilo idealno, da bi imel Terraform vgrajeno podporo za uvedbe brez izpadov, vendar od maja 2019 ekipa HashiCorp ni nameravala dodati te funkcionalnosti (podrobnosti - tukaj).

Pravilni načrt se lahko neuspešno izvaja

Včasih ukaz načrt ustvari popolnoma pravilen načrt razmestitve, vendar ukaz za uporabo vrne napako. Poskusite na primer dodati vir aws_iam_user z istim imenom, kot ste ga uporabili za uporabnika IAM, ki ste ga ustvarili prej v 2. poglavju:

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

Zdaj, če zaženete ukaz načrta, bo Terraform prikazal na videz razumen načrt uvajanja:

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.

Če zaženete ukaz za uporabo, se prikaže naslednja napaka:

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

Težava je seveda v tem, da uporabnik IAM s tem imenom že obstaja. In to se lahko zgodi ne samo uporabnikom IAM, ampak skoraj vsakemu viru. Možno je, da je nekdo ustvaril ta vir ročno ali z uporabo ukazne vrstice, vendar v vsakem primeru ujemanje ID-jev vodi do sporov. Obstaja veliko različic te napake, ki novince v Terraformu pogosto presenetijo.

Ključna točka je, da ukaz načrta terraform upošteva le tiste vire, ki so navedeni v datoteki stanja Terraform. Če so viri ustvarjeni na kakšen drug način (na primer ročno s klikom v konzoli AWS), ne bodo končali v datoteki stanja in jih zato Terraform ne bo upošteval pri izvajanju ukaza plan. Posledično se bo načrt, ki se na prvi pogled zdi pravilen, izkazal za neuspešnega.

Iz tega se lahko naučimo dve lekciji.

  • Če ste že začeli delati s Terraformom, ne uporabljajte ničesar drugega. Če je del vaše infrastrukture upravljan s pomočjo Terraforma, ga ne morete več spreminjati ročno. V nasprotnem primeru ne tvegate samo čudnih napak Terraform, ampak tudi izničite številne prednosti IAC, saj koda ne bo več natančna predstavitev vaše infrastrukture.
  • Če že imate nekaj infrastrukture, uporabite ukaz za uvoz. Če začnete uporabljati Terraform z obstoječo infrastrukturo, jo lahko dodate v datoteko stanja z ukazom za uvoz terraform. Tako bo Terraform vedel, katero infrastrukturo je treba upravljati. Ukaz za uvoz sprejme dva argumenta. Prvi je naslov vira v vaših konfiguracijskih datotekah. Sintaksa tukaj je enaka kot za povezave virov: _. (kot je aws_iam_user.existing_user). Drugi argument je ID vira, ki ga želite uvoziti. Recimo, da je ID vira aws_iam_user uporabniško ime (na primer yevgeniy.brikman), ID vira aws_instance pa je ID strežnika EC2 (na primer i-190e22e5). Kako uvoziti vir je običajno navedeno v dokumentaciji na dnu njegove strani.

    Spodaj je ukaz za uvoz, ki sinhronizira vir aws_iam_user, ki ste ga dodali svoji konfiguraciji Terraform skupaj z uporabnikom IAM v 2. poglavju (seveda zamenjate svoje ime za yevgeniy.brikman):

    $ terraform import aws_iam_user.existing_user yevgeniy.brikman

    Terraform bo poklical API AWS, da poišče vašega uporabnika IAM in ustvari povezavo datoteke stanja med njim in virom aws_iam_user.existing_user v vaši konfiguraciji Terraform. Od zdaj naprej, ko zaženete ukaz načrta, bo Terraform vedel, da uporabnik IAM že obstaja in ga ne bo poskušal znova ustvariti.

    Omeniti velja, da če že imate veliko virov, ki jih želite uvoziti v Terraform, je lahko ročno pisanje kode in uvažanje vsakega posebej težavno. Zato je vredno pogledati orodje, kot je Terraforming (http://terraforming.dtan4.net/), ki lahko samodejno uvozi kodo in stanje iz vašega računa AWS.

    Refactoring ima lahko svoje pasti

    Refactoring je običajna praksa pri programiranju, kjer spremenite notranjo strukturo kode, medtem ko pustite zunanje vedenje nespremenjeno. To je zato, da bo koda jasnejša, urejenejša in lažja za vzdrževanje. Refactoring je nepogrešljiva tehnika, ki jo je treba redno uporabljati. Toda ko gre za Terraform ali katero koli drugo orodje IAC, morate biti zelo previdni, kaj mislite z "zunanjim obnašanjem" dela kode, sicer se bodo pojavile nepričakovane težave.

    Na primer, običajna vrsta refaktoriranja je zamenjava imen spremenljivk ali funkcij z bolj razumljivimi. Veliko IDE-jev ima vgrajeno podporo za preoblikovanje in lahko samodejno preimenuje spremenljivke in funkcije v celotnem projektu. V splošnih programskih jezikih je to trivialen postopek, na katerega morda ne pomislite, v Terraformu pa morate biti s tem zelo previdni, sicer lahko pride do izpadov.

    Na primer, modul spletnega strežnika-gruče ima vhodno spremenljivko cluster_name:

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

    Predstavljajte si, da ste začeli uporabljati ta modul za uvajanje mikrostoritve, imenovane foo. Pozneje želite svojo storitev preimenovati v bar. Ta sprememba se morda zdi nepomembna, v resnici pa lahko povzroči motnje storitev.

    Dejstvo je, da modul webserver-cluster uporablja spremenljivko cluster_name v številnih virih, vključno s parametrom imena dveh varnostnih skupin in 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]
    }

    Če spremenite parameter imena na viru, bo Terraform izbrisal staro različico tega vira in na njenem mestu ustvaril novo. Toda če je ta vir ALB, med brisanjem in prenosom nove različice ne boste imeli mehanizma za preusmerjanje prometa na vaš spletni strežnik. Podobno, če je varnostna skupina izbrisana, bodo vaši strežniki začeli zavračati kakršen koli omrežni promet, dokler ne ustvarite nove skupine.

    Druga vrsta refaktoriranja, ki bi vas morda zanimala, je spreminjanje ID-ja Terraform. Vzemimo za primer vir aws_security_group v modulu gruče spletnega strežnika:

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

    Identifikator tega vira se imenuje primerek. Predstavljajte si, da ste se med refaktoriranjem odločili, da ga spremenite v bolj razumljivo (po vašem mnenju) ime cluster_instance:

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

    Kaj bo na koncu? Tako je: motnja.

    Terraform poveže vsak ID vira z ID-jem ponudnika oblaka. Na primer, iam_user je povezan z ID-jem uporabnika AWS IAM, aws_instance pa je povezan z ID-jem strežnika AWS EC2. Če spremenite ID vira (recimo iz instance v cluster_instance, kot je to v primeru aws_security_group), bo za Terraform videti, kot da ste izbrisali stari vir in dodali novega. Če uveljavite te spremembe, bo Terraform izbrisal staro varnostno skupino in ustvaril novo, vaši strežniki pa bodo začeli zavračati kakršen koli omrežni promet.

    Tukaj so štiri ključne lekcije, ki bi jih morali odnesti iz te razprave.

    • Vedno uporabite ukaz plan. Lahko razkrije vse te zanke. Previdno preglejte njegove rezultate in bodite pozorni na situacije, v katerih namerava Terraform izbrisati vire, ki najverjetneje ne bi smeli biti izbrisani.
    • Ustvari, preden izbrišeš. Če želite nadomestiti vir, dobro premislite, ali morate ustvariti zamenjavo, preden izbrišete izvirnik. Če je odgovor pritrdilen, vam lahko pomaga create_before_destroy. Enak rezultat je mogoče doseči ročno z izvedbo dveh korakov: najprej dodajte nov vir v konfiguracijo in zaženite ukaz za uporabo, nato pa odstranite stari vir iz konfiguracije in znova uporabite ukaz za uporabo.
    • Spreminjanje identifikatorjev zahteva spremembo stanja. Če želite spremeniti ID, povezan z virom (na primer preimenovati aws_security_group iz instance v cluster_instance), ne da bi izbrisali vir in ustvarili njegovo novo različico, morate ustrezno posodobiti datoteko stanja Terraform. Tega nikoli ne počnite ročno – namesto tega uporabite ukaz terraform state. Ko preimenujete identifikatorje, morate zagnati ukaz terraform state mv, ki ima naslednjo sintakso:
      terraform state mv <ORIGINAL_REFERENCE> <NEW_REFERENCE>

      ORIGINAL_REFERENCE je izraz, ki se nanaša na vir v njegovi trenutni obliki, NEW_REFERENCE pa je, kamor ga želite premakniti. Na primer, ko preimenujete skupino aws_security_group iz instance v cluster_instance, morate zagnati naslednji ukaz:

      $ terraform state mv 
         aws_security_group.instance 
         aws_security_group.cluster_instance

      To pove Terraformu, da mora biti stanje, ki je bilo prej povezano z aws_security_group.instance, zdaj povezano z aws_security_group.cluster_instance. Če po preimenovanju in izvajanju tega ukaza teraformni načrt ne pokaže nobenih sprememb, potem ste naredili vse pravilno.

    • Nekaterih nastavitev ni mogoče spremeniti. Parametri mnogih virov so nespremenljivi. Če jih poskušate spremeniti, bo Terraform izbrisal stari vir in na njegovem mestu ustvaril novega. Vsaka stran z viri običajno prikazuje, kaj se zgodi, ko spremenite določeno nastavitev, zato preverite dokumentacijo. Vedno uporabite ukaz plan in razmislite o uporabi strategije create_before_destroy.

    Odložena doslednost je skladna... z odlogom

    Nekateri API-ji ponudnikov oblaka, kot je AWS, so asinhroni in imajo zakasnjeno doslednost. Asinhronost pomeni, da lahko vmesnik takoj vrne odgovor, ne da bi čakal na dokončanje zahtevanega dejanja. Zakasnjena skladnost pomeni, da lahko traja nekaj časa, da se spremembe razširijo po sistemu; medtem ko se to dogaja, so lahko vaši odgovori nedosledni in odvisni od tega, katera replika vira podatkov se odziva na vaše klice API-ja.

    Predstavljajte si na primer, da pokličete API za AWS in ga prosite, naj ustvari strežnik EC2. API bo vrnil "uspešen" odgovor (201 Created) skoraj v trenutku, ne da bi čakal, da bo sam strežnik ustvarjen. Če se poskušate takoj povezati z njim, skoraj zagotovo ne bo uspelo, ker na tej točki AWS še vedno inicializira vire ali pa se strežnik še ni zagnal. Poleg tega lahko prejmete napako (404 Not Found), če še enkrat pokličete, da bi dobili informacije o tem strežniku. Stvar je v tem, da se lahko informacije o tem strežniku EC2 še vedno širijo po celotnem AWS, preden postanejo na voljo povsod, zato boste morali počakati nekaj sekund.

    Kadarkoli uporabljate asinhroni API z leno konsistentnostjo, morate občasno znova poskusiti svojo zahtevo, dokler se dejanje ne dokonča in razširi po sistemu. Na žalost AWS SDK ne ponuja nobenih dobrih orodij za to in projekt Terraform je imel veliko napak, kot je 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

    Z drugimi besedami, ustvarite vir (na primer podomrežje) in nato poskušate pridobiti nekaj informacij o njem (na primer ID na novo ustvarjenega podomrežja), Terraform pa ga ne najde. Večina teh napak (vključno s 6813) je bila odpravljena, vendar se občasno še vedno pojavijo, zlasti ko Terraform doda podporo za novo vrsto vira. To je nadležno, vendar v večini primerov ne povzroča nobene škode. Ko znova zaženete terraform apply, bi moralo vse delovati, saj se bodo do takrat informacije že razširile po sistemu.

    Ta odlomek je predstavljen iz knjige Evgenija Brikmana "Terraform: infrastruktura na ravni kode".

Vir: www.habr.com

Dodaj komentar