Teraformos spąstai

Teraformos spąstai
Pabrėžkime keletą spąstų, įskaitant susijusius su kilpomis, if pareiškimais ir diegimo metodais, taip pat bendresnes problemas, turinčias įtakos Terraform apskritai:

  • count ir for_each parametrai turi apribojimų;
  • apriboti nulinį prastovų diegimą;
  • net geras planas gali žlugti;
  • pertvarkymas gali turėti spąstų;
  • atidėtas darna atitinka... su atidėjimu.

Count ir for_each parametrai turi apribojimų

Šio skyriaus pavyzdžiuose plačiai naudojamas skaičiavimo parametras ir for_each išraiška kilpose ir sąlyginėje logikoje. Jie veikia gerai, tačiau turi du svarbius apribojimus, kuriuos reikia žinoti.

  • Count ir for_each negali nurodyti jokių išvesties išteklių kintamųjų.
  • count ir for_each negali būti naudojami modulio konfigūracijoje.

count ir for_each negali nurodyti jokių išteklių išvesties kintamųjų

Įsivaizduokite, kad jums reikia įdiegti kelis EC2 serverius ir dėl kokių nors priežasčių nenorite naudoti ASG. Jūsų kodas gali būti toks:

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

Pažvelkime į juos po vieną.

Kadangi skaičiavimo parametras nustatytas į statinę reikšmę, šis kodas veiks be problemų: paleidus komandą taikyti bus sukurti trys EC2 serveriai. Bet ką daryti, jei norėtumėte įdiegti vieną serverį kiekvienoje prieinamumo zonoje (AZ) dabartiniame AWS regione? Galite nustatyti, kad kodas įkeltų zonų sąrašą iš aws_availability_zones duomenų šaltinio, tada peržiūrėkite kiekvieną iš jų ir sukurkite joje EC2 serverį naudodami skaičiavimo parametrą ir masyvo indekso prieigą:

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

Šis kodas taip pat veiks gerai, nes skaičiavimo parametras gali be jokių problemų nurodyti duomenų šaltinius. Bet kas atsitiks, jei serverių, kuriuos reikia sukurti, skaičius priklauso nuo kai kurių išteklių išvesties? Norėdami tai parodyti, paprasčiausias būdas yra naudoti random_integer išteklių, kuris, kaip rodo pavadinimas, grąžina atsitiktinį sveikąjį skaičių:

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

Šis kodas generuoja atsitiktinį skaičių nuo 1 iki 3. Pažiūrėkime, kas atsitiks, jei bandysime panaudoti šio šaltinio išvestį aws_instance šaltinio count parametre:

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

Jei paleisite terraforminį planą naudodami šį kodą, gausite šią klaidą:

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“ reikalauja, kad skaičius ir už_kiekvienas būtų apskaičiuoti planavimo etape, prieš sukuriant ar keičiant išteklius. Tai reiškia, kad skaičius ir už_kiekvienas gali reikšti pažodinius žodžius, kintamuosius, duomenų šaltinius ir net išteklių sąrašus (jeigu jų ilgį galima nustatyti planuojant), bet ne į apskaičiuotus išteklių išvesties kintamuosius.

count ir for_each negali būti naudojami modulio konfigūracijoje

Kažkada jums gali kilti pagunda prie modulio konfigūracijos pridėti skaičiavimo parametrą:

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

     count = 3

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

Šis kodas bando naudoti skaičių modulyje, kad sukurtų tris žiniatinklio serverio klasterio išteklių kopijas. Arba galbūt norėsite, kad modulio prijungimas būtų neprivalomas, atsižvelgiant į kai kurias Būlio sąlygas, nustatydami jo skaičiavimo parametrą į 0. Tai gali atrodyti kaip pagrįstas kodas, tačiau vykdydami terraforminį planą gausite šią klaidą:

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.

Deja, nuo Terraform 0.12.6 modulio šaltinyje nepalaikomas skaičius arba for_each. Remiantis „Terraform 0.12“ išleidimo pastabomis (http://bit.ly/3257bv4), „HashiCorp“ planuoja pridėti šią galimybę ateityje, todėl priklausomai nuo to, kada skaitysite šią knygą, ji jau gali būti prieinama. Norėdami įsitikinti, skaitykite Terraform pakeitimų žurnalą čia.

Nulinio prastovos diegimo apribojimai

Bloko create_before_destroy naudojimas kartu su ASG yra puikus sprendimas kuriant nulinės prastovos diegimus, išskyrus vieną įspėjimą: automatinio mastelio keitimo taisyklės nepalaikomos. Arba, tiksliau, ASG dydis iš naujo nustatomas į min_size kiekvieno diegimo metu, o tai gali būti problema, jei naudojate automatinio mastelio keitimo taisykles, kad padidintumėte veikiančių serverių skaičių.

Pavyzdžiui, žiniatinklio serverio klasterio modulyje yra aws_autoscaling_schedule išteklių pora, kuri 9 val. ryto padidina serverių skaičių klasteryje nuo dviejų iki dešimties. Jei įdiegsite, tarkime, 11 val., naujasis ASG bus paleistas tik dviem serveriais, o ne dešimt, ir toks bus iki kitos dienos 9 val.

Šį apribojimą galima apeiti keliais būdais.

  • Pakeiskite pasikartojimo parametrą aws_autoscaling_schedule iš 0 9 * * * („paleisti 9 val.“) į maždaug 0–59 9–17 * * * („paleisti kas minutę nuo 9 iki 5 val.“). Jei ASG jau turi dešimt serverių, tai dar kartą paleidus šią automatinio mastelio keitimo taisyklę niekas nepasikeis, ko mes ir norime. Bet jei ASG buvo įdiegtas neseniai, ši taisyklė užtikrins, kad per minutę jos serverių skaičius pasieks dešimt. Tai nėra visiškai elegantiškas požiūris, o dideli šuoliai nuo dešimties iki dviejų serverių ir atgal taip pat gali sukelti problemų vartotojams.
  • Sukurkite tinkintą scenarijų, kuris naudoja AWS API aktyvių serverių skaičiui ASG nustatyti, iškvieskite jį naudodami išorinį duomenų šaltinį (žr. „Išorinis duomenų šaltinis“ p. 249) ir nustatykite ASG parametro pageidaujamas_pajėgumas į vertę, kurią grąžina scenarijus. Tokiu būdu kiekvienas naujas ASG egzempliorius visada veiks tokiu pat pajėgumu kaip ir esamas Terraform kodas, todėl jį bus sunkiau prižiūrėti.

Žinoma, Terraform idealiu atveju turėtų integruotą palaikymą, skirtą diegti be prastovų, tačiau 2019 m. gegužės mėn. HashiCorp komanda neplanavo pridėti šios funkcijos (detalės – čia).

Teisingas planas gali būti nesėkmingai įgyvendintas

Kartais plano komanda sukuria visiškai teisingą diegimo planą, tačiau programa taikyti grąžina klaidą. Pavyzdžiui, pabandykite pridėti aws_iam_user išteklių tuo pačiu pavadinimu, kurį naudojote IAM naudotojui, kurį sukūrėte anksčiau 2 skyriuje:

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

Dabar, jei paleisite plano komandą, „Terraform“ išves iš pažiūros pagrįstą diegimo planą:

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.

Jei paleisite taikymo komandą, gausite šią klaidą:

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

Žinoma, problema ta, kad IAM vartotojas tokiu vardu jau egzistuoja. Ir tai gali atsitikti ne tik IAM vartotojams, bet ir beveik bet kokiam ištekliui. Gali būti, kad kas nors sukūrė šį šaltinį rankiniu būdu arba naudodamas komandinę eilutę, bet bet kuriuo atveju dėl ID atitikimo kyla konfliktų. Yra daug šios klaidos variantų, kurie dažnai nustebina Terraform naujokus.

Svarbiausia, kad Terraform plano komanda atsižvelgia tik į tuos išteklius, kurie nurodyti Terraform būsenos faile. Jei ištekliai kuriami kitu būdu (pavyzdžiui, rankiniu būdu spustelėjus AWS konsolėje), jie nepateks į būsenos failą, todėl Terraform į juos neatsižvelgs vykdydama plano komandą. Dėl to planas, kuris iš pirmo žvilgsnio atrodo teisingas, pasirodys nesėkmingas.

Iš to galima pasimokyti dvi pamokas.

  • Jei jau pradėjote dirbti su Terraform, nenaudokite nieko kito. Jei dalis jūsų infrastruktūros valdoma naudojant „Terraform“, nebegalite jos keisti rankiniu būdu. Priešingu atveju jūs ne tik rizikuojate keistomis Terraform klaidomis, bet ir paneigiate daugelį IaC pranašumų, nes kodas nebebus tikslus jūsų infrastruktūros vaizdas.
  • Jei jau turite tam tikrą infrastruktūrą, naudokite komandą importuoti. Jei pradedate naudoti Terraform su esama infrastruktūra, galite įtraukti jį į būsenos failą naudodami terraform importavimo komandą. Tokiu būdu „Terraform“ žinos, kokią infrastruktūrą reikia valdyti. Importavimo komanda turi du argumentus. Pirmasis yra išteklių adresas jūsų konfigūracijos failuose. Sintaksė čia yra tokia pati kaip išteklių nuorodų: _. (pvz., aws_iam_user.existing_user). Antrasis argumentas yra importuojamo ištekliaus ID. Tarkime, kad išteklių ID aws_iam_user yra vartotojo vardas (pavyzdžiui, yevgeniy.brikman), o ištekliaus ID aws_instance yra EC2 serverio ID (pvz., i-190e22e5). Kaip importuoti išteklius, paprastai nurodoma dokumentacijoje, esančioje jo puslapio apačioje.

    Žemiau yra importavimo komanda, kuri sinchronizuoja aws_iam_user išteklius, kuriuos pridėjote prie Terraform konfigūracijos kartu su IAM vartotoju 2 skyriuje (žinoma, pakeičiant jūsų vardą yevgeniy.brikman):

    $ terraform import aws_iam_user.existing_user yevgeniy.brikman

    „Terraform“ iškvies AWS API, kad surastų jūsų IAM vartotoją ir sukurtų būsenos failo susiejimą tarp jo ir aws_iam_user.existing_user išteklių jūsų „Terraform“ konfigūracijoje. Nuo šiol, kai paleisite plano komandą, „Terraform“ žinos, kad IAM vartotojas jau yra, ir nebebandys jo sukurti.

    Verta paminėti, kad jei jau turite daug išteklių, kuriuos norite importuoti į „Terraform“, rankiniu būdu rašyti kodą ir importuoti kiekvieną po vieną gali kilti problemų. Taigi verta ieškoti tokio įrankio kaip „Terraforming“ (http://terraforming.dtan4.net/), kuris gali automatiškai importuoti kodą ir būseną iš jūsų AWS paskyros.

    Refaktorizavimas gali turėti savo spąstų

    Refaktoringas yra įprasta programavimo praktika, kai keičiate vidinę kodo struktūrą, o išorinis elgesys nepakeičiamas. Taip siekiama, kad kodas būtų aiškesnis, tvarkingesnis ir lengviau prižiūrimas. Refaktorizavimas yra nepakeičiama technika, kurią reikia naudoti reguliariai. Tačiau kalbant apie „Terraform“ ar bet kurį kitą IaC įrankį, turite būti labai atsargūs, ką reiškia „išorinis kodo dalies elgesys“, kitaip atsiras netikėtų problemų.

    Pavyzdžiui, įprastas pertvarkymo būdas yra kintamųjų ar funkcijų pavadinimų pakeitimas suprantamesniais. Daugelis IDE turi integruotą refaktorizavimo palaikymą ir gali automatiškai pervardyti kintamuosius ir funkcijas viso projekto metu. Bendrosios paskirties programavimo kalbose tai yra triviali procedūra, apie kurią galbūt nepagalvotumėte, tačiau „Terraform“ turite būti ypač atsargūs, kitaip galite patirti gedimų.

    Pavyzdžiui, žiniatinklio serverio klasterio modulis turi įvesties kintamąjį cluster_name:

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

    Įsivaizduokite, kad pradėjote naudoti šį modulį mikropaslaugai, vadinamai foo, diegti. Vėliau norėsite pervadinti paslaugą į barą. Šis pakeitimas gali atrodyti nereikšmingas, tačiau iš tikrųjų jis gali sukelti paslaugų sutrikimus.

    Faktas yra tas, kad žiniatinklio serverio klasterio modulis naudoja kintamąjį cluster_name daugelyje išteklių, įskaitant dviejų saugos grupių pavadinimo parametrą ir 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]
    }

    Jei pakeisite ištekliaus pavadinimo parametrą, „Terraform“ ištrins senąją to šaltinio versiją ir vietoje jos sukurs naują. Bet jei tas išteklius yra ALB, nuo jo ištrynimo iki naujos versijos atsisiuntimo neturėsite mechanizmo srautui nukreipti į savo žiniatinklio serverį. Taip pat, jei saugos grupė bus ištrinta, jūsų serveriai pradės atmesti bet kokį tinklo srautą, kol bus sukurta nauja grupė.

    Kitas pertvarkymo tipas, kuris jus gali sudominti, yra Terraform ID keitimas. Kaip pavyzdį paimkime aws_security_group išteklius žiniatinklio serverio klasterio modulyje:

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

    Šio šaltinio identifikatorius vadinamas egzemplioriumi. Įsivaizduokite, kad pertvarkydami nusprendėte jį pakeisti į suprantamesnį (jūsų nuomone) pavadinimą cluster_instance:

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

    Kas bus galų gale? Teisingai: sutrikimas.

    „Terraform“ susieja kiekvieną išteklių ID su debesies teikėjo ID. Pavyzdžiui, iam_user yra susietas su AWS IAM vartotojo ID, o aws_instance yra susietas su AWS EC2 serverio ID. Jei pakeisite ištekliaus ID (pvz., iš egzemplioriaus į cluster_instance, kaip yra aws_security_group atveju), į Terraform, jis atrodys taip, tarsi ištrynėte seną šaltinį ir pridėjote naują. Jei pritaikysite šiuos pakeitimus, „Terraform“ ištrins seną saugos grupę ir sukurs naują, o jūsų serveriai pradės atmesti bet kokį tinklo srautą.

    Štai keturios pagrindinės pamokos, kurias turėtumėte išmokti iš šios diskusijos.

    • Visada naudokite plano komandą. Jis gali atskleisti visas šias kliūtis. Atidžiai peržiūrėkite jos rezultatus ir atkreipkite dėmesį į situacijas, kai Terraform planuoja ištrinti išteklius, kurių greičiausiai nereikėtų ištrinti.
    • Sukurkite prieš ištrindami. Jei norite pakeisti šaltinį, prieš ištrindami originalą gerai pagalvokite, ar reikia sukurti pakaitalą. Jei atsakymas yra teigiamas, Create_before_destroy gali padėti. Tą patį rezultatą galima pasiekti rankiniu būdu, atlikus du veiksmus: pirmiausia į konfigūraciją įtraukite naują šaltinį ir paleiskite komandą taikyti, o tada pašalinkite seną išteklius iš konfigūracijos ir vėl naudokite taikymo komandą.
    • Norint pakeisti identifikatorius, reikia pakeisti būseną. Jei norite pakeisti su ištekliu susietą ID (pvz., pervardyti aws_security_group iš egzemplioriaus į cluster_instance) neištrindami šaltinio ir nesukūrę naujos jo versijos, turite atitinkamai atnaujinti Terraform būsenos failą. Niekada nedarykite to rankiniu būdu – vietoj to naudokite komandą terraform state. Pervardydami identifikatorius, turėtumėte paleisti komandą terraform state mv, kurios sintaksė yra tokia:
      terraform state mv <ORIGINAL_REFERENCE> <NEW_REFERENCE>

      ORIGINAL_REFERENCE yra išraiška, nurodanti išteklių dabartine forma, o NEW_REFERENCE yra vieta, kur norite jį perkelti. Pavyzdžiui, pervardydami aws_security_group grupę iš egzemplioriaus į cluster_instance, turite paleisti šią komandą:

      $ terraform state mv 
         aws_security_group.instance 
         aws_security_group.cluster_instance

      Tai nurodo „Terraform“, kad būsena, kuri anksčiau buvo susieta su aws_security_group.instance, dabar turėtų būti susieta su aws_security_group.cluster_instance. Jei pervadinę ir paleidę šią komandą terraform planas nerodo jokių pakeitimų, vadinasi, viską padarėte teisingai.

    • Kai kurių nustatymų pakeisti negalima. Daugelio išteklių parametrai nekeičiami. Jei bandysite juos pakeisti, „Terraform“ ištrins seną šaltinį ir vietoje jo sukurs naują. Kiekviename ištekliaus puslapyje paprastai nurodoma, kas nutinka pakeitus konkretų nustatymą, todėl būtinai peržiūrėkite dokumentaciją. Visada naudokite plano komandą ir apsvarstykite galimybę naudoti strategiją Create_before_destroy.

    Atidėtas nuoseklumas atitinka... su atidėjimu

    Kai kurios debesies paslaugų teikėjų API, pvz., AWS, yra asinchroninės ir jų nuoseklumas yra uždelstas. Asinchroniškumas reiškia, kad sąsaja gali iš karto grąžinti atsakymą nelaukdama, kol bus atliktas prašomas veiksmas. Uždelstas nuoseklumas reiškia, kad pakeitimams gali prireikti daug laiko, kol jie pasklis visoje sistemoje; kol tai vyksta, jūsų atsakymai gali būti nenuoseklūs ir priklausyti nuo to, kurio duomenų šaltinio replika reaguoja į jūsų API iškvietimus.

    Pavyzdžiui, įsivaizduokite, kad skambinate API į AWS, prašydami sukurti EC2 serverį. API pateiks „sėkmingą“ atsakymą (201 sukurta) beveik akimirksniu, nelaukdama, kol bus sukurtas pats serveris. Jei bandysite prisijungti prie jo iš karto, tai beveik neabejotinai nepavyks, nes tuo metu AWS vis dar inicijuoja išteklius arba serveris dar nebuvo paleistas. Be to, jei dar kartą skambinate norėdami gauti informacijos apie šį serverį, galite gauti klaidą (404 Nerasta). Reikalas tas, kad informacija apie šį EC2 serverį vis tiek gali būti platinama visame AWS, kol ji taps prieinama visur, turėsite palaukti kelias sekundes.

    Kiekvieną kartą, kai naudojate asinchroninę API, kurios nuoseklumas yra lėtas, turite periodiškai pakartoti užklausą, kol veiksmas bus baigtas ir perkeliamas sistemoje. Deja, AWS SDK tam nepateikia jokių gerų įrankių, o „Terraform“ projektas anksčiau kentė nuo daugybės klaidų, tokių kaip 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

    Kitaip tariant, sukuriate šaltinį (pvz., potinklį) ir bandote gauti informacijos apie jį (pvz., naujai sukurto potinklio ID), o „Terraform“ negali jo rasti. Dauguma šių klaidų (įskaitant 6813) buvo ištaisytos, tačiau jos vis tiek retkarčiais atsiranda, ypač kai Terraform prideda palaikymą naujam išteklių tipui. Tai erzina, bet daugeliu atvejų nedaro jokios žalos. Kai vėl paleisite terraform aplikaciją, viskas turėtų veikti, nes iki to laiko informacija jau bus pasklidusi visoje sistemoje.

    Ši ištrauka pateikta iš Jevgenijaus Brikmano knygos „Terraforma: infrastruktūra kodo lygiu“.

Šaltinis: www.habr.com

Добавить комментарий