Zamke Terraform-a

Zamke Terraform-a
Istaknimo nekoliko zamki, uključujući one vezane uz petlje, if izraze i metode implementacije, kao i općenitije probleme koji se odnose na Terraform u cjelini:

  • parametri count i for_each imaju ograničenja;
  • ograničenja implementacija s vremenom zastoja nula;
  • čak i dobar plan može biti neuspješan;
  • refaktoring može imati svoje zamke;
  • odgođena konzistentnost se slaže... s odgađanjem.

Parametri count i for_each imaju ograničenja

U primjerima ovog poglavlja, parametar count i izraz for_each aktivno se koriste u petljama i uvodnoj logici. Dobar su alat, ali imaju dva važna ograničenja o kojima treba znati.

  • U count i for_each ne možete se referirati na varijable izlaza resursa.
  • count i for_each se ne mogu koristiti u konfiguraciji modula.

U count i for_each ne možete se referirati na varijable izlaza resursa

Zamislite da trebate implementirati nekoliko EC2 poslužitelja i iz nekog razloga ne želite koristiti ASG. Vaš kod može izgledati ovako:

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

Pogledajmo ih jedan po jedan.

Budući da je parametru count dodijeljena statična vrijednost, ovaj kod će raditi bez problema: kada pokrenete naredbu apply, stvorit će tri EC2 poslužitelja. No, ako želite implementirati jedan poslužitelj u svakoj dostupnoj zoni (Availability Zone ili AZ) unutar trenutnog AWS područja? Možete učiniti tako da vaš kod učita popis zona iz izvora podataka aws_availability_zones i zatim "petlja" prođe kroz svaku od njih i stvori EC2 poslužitelj u svakoj, koristeći parametar count i pristup nizu po indeksu:

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

Ovaj kod također će raditi savršeno, budući da se parametar count može bez problema referirati na izvore podataka. No, što će se dogoditi ako broj poslužitelja koje trebate stvoriti ovisi o izlazu nekog resursa? Da to demonstriramo, najlakše je uzeti resurs random_integer, koji, kako se može naslutiti iz imena, vraća slučajan cijeli broj:

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

Ovaj kod generira nasumični broj od 1 do 3. Pogledajmo što će se dogoditi ako pokušamo koristiti izlaz result ovog resursa u parametru count resursa aws_instance:

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

Ako pokrenete ovaj kod s terraform plan, dobit ćete sljedeću grešku:

Error: Invalid count argument

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

Vrijednost "count" ovisi o atributima resursa koji se ne mogu odrediti do primjene, pa Terraform ne može predvidjeti koliko će instanci biti stvoreno. Da biste to zaobišli, koristite argument -target da prvo primijenite samo resurse na koje "count" ovisi.

Terraform zahtijeva da se count i for_each izračunavaju tijekom faze planiranja, prije nego što se stvore ili promijene bilo kakvi resursi. To znači da count i for_each mogu referencirati literale, varijable, izvore podataka, pa čak i liste resursa (pod uvjetom da se njihova duljina može odrediti u fazi planiranja), ali ne i izračunate izlazne varijable resursa.

count i for_each ne mogu se koristiti u konfiguraciji modula

Jednom ćete možda imati iskušenje dodati parametar count u konfiguracije modula:

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

     count = 3

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

Ovaj kod pokušava koristiti count unutar modula za stvaranje tri kopije resursa webserver-cluster. Ili, možda, želite učiniti povezivanje modula opcionalnim ovisno o nekom booleovskom uvjetu, dodjeljujući njegovom parametru count vrijednost 0. Takav kod će izgledati sasvim razumno, međutim, prilikom izvođenja terraform plana dobit ćete sljedeću grešku:

Error: Reserved argument name in module block

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

Naziv "count" je rezerviran za upotrebu u budućoj verziji Terraforma.

Nažalost, u vrijeme izlaska Terraforma 0.12.6 korištenje count ili for_each u resursu modula nije podržano. Prema bilješkama o izdanju Terraforma 0.12 (http:\/\/bit.ly\/3257bv4), HashiCorp planira dodati ovu mogućnost u budućnosti, tako da, ovisno o tome kada čitate ovu knjigu, ona već može biti dostupna. Da biste bili sigurni, pročitajte changelog Terraforma ovdje.

Ograničenja implementacija bez vremena zastoja

Korištenje bloka create_before_destroy u kombinaciji s ASG predstavlja izvrsno rješenje za organizaciju implementacija bez prekida, osim jedne iznimke: pravila automatskog skaliranja nisu podržana. Ili, da budem precizniji, to postavlja veličinu ASG-a natrag na min_size pri svakoj implementaciji, što može postati problem ako ste koristili pravila automatskog skaliranja za povećanje broja pokrenutih poslužitelja.

Na primjer, modul webserver-cluster sadrži nekoliko resursa aws_autoscaling_schedule koji u 9 sati ujutro povećavaju broj poslužitelja u klasteru s dva na deset. Ako se implementacija izvrši, recimo, u 11 sati ujutro, nova grupa ASG-a neće se pokrenuti s deset, već samo s dva poslužitelja i ostat će u tom stanju do 9 sati sljedećeg dana.

Ovo ograničenje se može zaobići na nekoliko načina.

  • Promijeni parametar recurrence u aws_autoscaling_schedule s 0 9 * * * („izvršavati u 9 ujutro“) na nešto poput 0-59 9-17 * * * („izvršavati svake minute od 9 ujutro do 5 navečer“). Ako ASG već ima deset poslužitelja, ponovna primjena ovog pravila automatskog skaliranja neće ništa promijeniti, što je i potrebno. No, ako je grupa ASG-a nedavno implementirana, ovo pravilo jamči da će najkasnije za minutu broj njenih poslužitelja doseći deset. Ovo nije sasvim elegantan pristup, a veliki skokovi s deset na dva poslužitelja i obratno također mogu izazvati probleme korisnicima.
  • Izraditi prilagođeni skript koji koristi AWS API za određivanje broja aktivnih poslužitelja u ASG-u, pozvati ga putem vanjskog izvora podataka (vidi odjeljak „Vanjski izvor podataka“ na str. 249) i dodijeliti parametru desired_capacity grupe ASG-a vrijednost koju vraća taj skript. Tako će svaki novi primjerak ASG-a uvijek biti pokrenut s istim kapacitetom, što otežava održavanje kodova Terraform.

Naravno, idealno bi bilo da Terraform ima ugrađenu podršku za implementacije bez prekida, no od svibnja 2019. godine tim HashiCorpa nije planirao dodavati ovu funkcionalnost (više detalja - ovdje).

Ispravan plan može biti neuspješno implementiran

Ponekad, kada izvršavate komandu plan, dobijete potpuno ispravan plan implementacije, međutim, komanda apply vraća grešku. Pokušajte, na primer, dodati resurs aws_iam_user sa istim imenom koje ste koristili za IAM korisnika kojeg ste prethodno kreirali u poglavlju 2:

resource "aws_iam_user" "existing_user" {
   # Unesite ovde ime već postojećeg IAM korisnika,
   # kako biste vežbali korišćenje komande terraform import
   name = "yevgeniy.brikman"
}

Sada, ako izvršite komandu plan, Terraform će na prvi pogled prikazati sasvim razuman plan implementacije:

Terraform će izvesti sledeće radnje:

   # aws_iam_user.existing_user će biti kreiran
   + resource "aws_iam_user" "existing_user" {
         + arn                  = (poznato nakon primene)
         + force_destroy   = false
         + id                    = (poznato nakon primene)
         + name               = "yevgeniy.brikman"
         + path                 = "\/"
         + unique_id         = (poznato nakon primene)
      }

Plan: 1 za dodavanje, 0 za promenu, 0 za uništavanje.

Ako izvršite komandu apply, dobićete sledeću grešku:

Error: Error creating IAM User yevgeniy.brikman: EntityAlreadyExists:
Korisnik sa imenom yevgeniy.brikman već postoji.

   na main.tf liniji 10, u resursu "aws_iam_user" "existing_user":
   10: resource "aws_iam_user" "existing_user" {

Problem je, naravno, u tome što IAM korisnik sa tim imenom već postoji. To se može dogoditi ne samo sa IAM korisnicima, već i sa gotovo svakim resursom. Moguće je da je neko kreirao ovaj resurs ručno ili putem komandne linije, ali kako god bilo, preklapanje identifikatora vodi do sukoba. Ova greška ima mnogo varijacija koje često iznenade početnike u Terraform-u.

Ključna stvar je da komanda terraform plan uzima u obzir samo one resurse koji su navedeni u Terraform datoteci stanja. Ako su resursi kreirani na neki drugi način (na primer, ručno, klikom na dugme u AWS konzoli), oni neće biti uključeni u datoteku stanja i stoga ih Terraform neće uzeti u obzir pri izvršavanju komande plan. Kao rezultat, naizgled ispravan plan će se pokazati neuspešnim.

Iz ovoga se mogu izvući dva lekcije.

  • Ako ste već počeli raditi sa Terraform-om, nemojte koristiti ništa drugo. Ako deo vaše infrastrukture upravlja Terraform-om, više nemojte menjati ručno. U suprotnom, ne samo da rizikujete dobijanje čudnih grešaka u Terraform-u, već takođe anulirate mnoge prednosti IaC, jer kod više neće biti tačan prikaz vaše infrastrukture.
  • Ako već imate neku infrastrukturu, koristite komandu import. Ako počinjete koristiti Terraform s već postojećom infrastrukturom, možete je dodati u stanje datoteke pomoću komande terraform import. Tako će Terraform znati kojom infrastrukturom treba upravljati. Komanda import prihvaća dva argumenta. Prvi je adresa resursa u vašim konfiguracijskim datotekama. Ovdje vrijedi isti sintaksni oblik kao i u referencama na resurse: _. (poput aws_iam_user.existing_user). Drugi argument je identifikator resursa koji treba importirati. Recimo, kao ID resursa aws_iam_user koristi se ime korisnika (na primjer, yevgeniy.brikman), a ID resursa aws_instance će biti identifikator EC2 poslužitelja (poput i-190e22e5). Kako importirati resurs obično se navodi u dokumentaciji na dnu njegove stranice.

    U nastavku je prikazana komanda import koja omogućuje usklađivanje resursa aws_iam_user, koji ste dodali u svoju Terraform konfiguraciju zajedno s IAM korisnikom iz poglavlja 2 (naravno, umjesto yevgeniy.brikman stavite svoje ime):

    $ terraform import aws_iam_user.existing_user yevgeniy.brikman

    Terraform će se obratiti AWS API-ju kako bi pronašao vašeg IAM korisnika i stvorio u datoteci stanja vezu između njega i resursa aws_iam_user.existing_user u vašoj Terraform konfiguraciji. Od ovog trenutka, prilikom izvršenja komande plan, Terraform će znati da IAM korisnik već postoji i neće pokušavati ponovo ga stvoriti.

    Treba napomenuti da, ako već imate puno resursa koje želite importirati u Terraform, ručno pisanje koda i importiranje svakog od njih jedan po jedan može biti zamoran posao. Zbog toga je vrijedno obratiti pažnju na alat poput Terraforminga (http://terraforming.dtan4.net/), koji može automatski importirati kod i stanje iz AWS računa.

    Refaktoring može imati svoje zamke

    Refaktoring — je uobičajena praksa u programiranju kada mijenjate unutarnju strukturu koda, ostavljajući vanjsko ponašanje nepromijenjeno. To je potrebno da bi kod bio jasniji, uredniji i lakši za održavanje. Refaktoring je neizostavna metoda koju treba redovito primjenjivati. Međutim, kada je riječ o Terraformu ili bilo kojem drugom alatu IaC, treba vrlo pažljivo pristupiti tome što se podrazumijeva pod "vanjskim ponašanjem" dijela koda, inače će doći do neočekivanih problema.

    Na primjer, uobičajeni oblik refaktoriranja je zamjena imena varijabli ili funkcija s razumljivijima. Mnoge IDE-ovi imaju ugrađenu podršku za refaktoriranje i mogu automatski preimenovati varijable i funkcije unutar cijelog projekta. U jezicima opće namjene to je trivijalna procedura o kojoj ne treba previše razmišljati, no u Terraformu treba biti iznimno oprezan, jer se inače mogu pojaviti prekidi u radu.

    Primjerice, modul webserver-cluster ima ulaznu varijablu cluster_name:

    variable "cluster_name" {
       description = "Ime koje se koristi za sve resurse klastera"
       type          = string
    }

    Zamislite da ste počeli koristiti ovaj modul za postavljanje mikroservisa pod nazivom foo. Kasnije ste odlučili preimenovati svoj servis u bar. Ova promjena može izgledati trivijalno, no u stvarnosti može izazvati prekide u radu.

    Naime, modul webserver-cluster koristi varijablu cluster_name u čitavom nizu resursa, uključujući parametar name dviju sigurnosnih grupa i 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]
    }

    Ako promijenite parametar name u nekom resursu, Terraform će ukloniti staru verziju tog resursa i kreirati novu umjesto nje. No, ako je takav resurs ALB, tijekom razdoblja između njegovog uništavanja i učitavanja nove verzije nećete imati mehanizam za preusmjeravanje prometa na vaš web poslužitelj. Također, ako se briše sigurnosna grupa, vaši poslužitelji će početi odbacivati bilo koji mrežni promet dok se ne stvori nova grupa.

    Još jedan oblik refaktoriranja koji bi vas mogao zanimati je promjena identifikatora Terraform. Uzmite za primjer resurs aws_security_group u modulu webserver-cluster:

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

    Identifikator ovog resursa naziva se instance. Zamislite da ste tijekom refaktoriranja odlučili promijeniti ga u razumljiviji (po vašem mišljenju) naziv cluster_instance:

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

    Što će se na kraju dogoditi? Točno: prekid u radu.

    Terraform povezuje ID svake resursa s identifikatorom dobavljača oblaka. Na primjer, iam_user se povezuje s IAM korisničkim ID-om u AWS-u, dok se aws_instance povezuje s ID-om AWS EC2 poslužitelja. Ako promijenite identifikator resursa (na primjer, s instance na cluster_instance, kao u slučaju aws_security_group), za Terraform će izgledati kao da ste uklonili staru resurs i dodali novi. Kada primijenite te promjene, Terraform će ukloniti staru grupu sigurnosti i stvoriti novu, a u međuvremenu će vaši poslužitelji početi odbijati sav mrežni promet.

    Evo četiri ključne lekcije koje trebate izvući iz ove rasprave.

    • Uvijek koristite naredbu plan. Ona može otkriti sve te zavrzlame. Pažljivo pregledajte njezin izlaz i obratite pozornost na situacije kada Terraform planira ukloniti resurse koje vjerojatno ne biste trebali ukloniti.
    • Stvarajte prije nego što uklonite. Ako želite zamijeniti resurs, dobro razmislite trebate li stvoriti zamjenu prije nego što uklonite izvorni resurs. Ako je odgovor potvrdan, u tome vam može pomoći create_before_destroy. Isti rezultat se može postići ručno u dva koraka: prvo dodajte novi resurs u konfiguraciju i pokrenite naredbu apply, a zatim uklonite stari resurs iz konfiguracije i ponovno upotrijebite naredbu apply.
    • Promjena identifikatora zahtijeva promjenu stanja. Ako želite promijeniti identifikator povezan s resursom (na primjer, preimenovati aws_security_group s instance na cluster_instance) bez uklanjanja resursa i stvaranja nove verzije, potrebno je adekvatno ažurirati Terraform datoteku stanja. Nikada to ne radite ručno — umjesto toga koristite naredbu terraform state. Prilikom preimenovanja identifikatora trebate izvršiti naredbu terraform state mv, koja ima sljedeću sintaksu:
      terraform state mv

      ORIGINAL_REFERENCE je izraz koji se odnosi na resurs u njegovom trenutnom obliku, a NEW_REFERENCE je mjesto na koje ga želite premjestiti. Na primjer, prilikom preimenovanja grupe aws_security_group s instance na cluster_instance trebate izvršiti sljedeću naredbu:

      $ terraform state mv 
         aws_security_group.instance 
         aws_security_group.cluster_instance

      Tako ćete obavijestiti Terraform da je stanje koje se prethodno odnosilo na aws_security_group.instance sada povezano s aws_security_group.cluster_instance. Ako nakon preimenovanja i pokretanja ove naredbe terraform plan ne prikazuje nikakve promjene, onda ste sve učinili ispravno.

    • Neke parametre nije moguće mijenjati. Parametri mnogih resursa su nepromjenjivi. Ako pokušate izmijeniti te parametre, Terraform će obrisati stari resurs i stvoriti novi umjesto njega. Na stranici svakog resursa obično se navodi što se događa pri promjeni određenog parametra, stoga se uvijek posavjetujte s dokumentacijom. Uvijek koristite naredbu plan i razmotrite opravdanost primjene strategije create_before_destroy.

    Odgođena dosljednost dogovara se… s odgađanjem

    API nekih cloud providera, poput AWS-a, su asinkroni i imaju odgođenu dosljednost. Asinkronost znači da sučelje može odmah vratiti odgovor, ne čekajući završetak zatražene radnje. Odgođena dosljednost znači da može proći vrijeme da se promjene propagiraju kroz cijeli sustav; dok se to događa, vaši odgovori mogu biti nedosljedni i ovisiti o tome koja replika izvora podataka odgovara na vaše API pozive.

    Zamislite, na primjer, da šaljete API poziv AWS-u s molbom za kreiranje EC2 poslužitelja. API će gotovo odmah vratiti 'uspješan' odgovor (201 Created), ne čekajući završetak stvaranja samog poslužitelja. Ako odmah pokušate pristupiti tome, gotovo sigurno neće uspjeti, jer AWS još uvijek inicijalizira resurse ili, alternativno, poslužitelj se još nije pokrenuo. Štoviše, ako pošaljete još jedan poziv kako biste dobili informacije o tom poslužitelju, može doći do pogreške (404 Not Found). Naime, informacije o tom EC2 poslužitelju još uvijek se mogu širiti kroz AWS, i trebat će nekoliko sekundi da postanu dostupne svugdje.

    Svaki put kada koristite asinkroni API s odgođenom dosljednošću, morate redovito ponavljati svoj zahtjev dok radnja ne bude završena i ne bude proširena kroz sustav. Nažalost, AWS SDK ne nudi dobre alate za to, a projekt Terraform se ranije suočavao s brojnim pogreškama, poput 6813 (https://github.com/hashicorp/terraform/issues/6813):

    $ terraform apply
    aws_subnet.private-persistence.2: InvalidSubnetID.NotFound:
    Subnet ID 'subnet-xxxxxxx' ne postoji

    Drugim riječima, kreirate resurs (na primjer, podmrežu) i zatim pokušavate dobiti neke informacije o njemu (kao što je ID upravo kreirane podmreže), a Terraform ih ne može pronaći. Većina takvih pogrešaka (uključujući 6813) je već ispravljena, ali s vremena na vrijeme se još uvijek pojavljuju, posebno kada Terraform dodaje podršku za novu vrstu resursa. To je frustrirajuće, ali u većini slučajeva ne uzrokuje štetu. Pri ponovnom izvođenju terraform apply sve bi trebalo funkcionirati, jer će u tom trenutku informacije već biti distribuirane sustavom.

    Ovaj odlomak je iz knjige Evgenija Brikmana «Terraform: infrastruktura kao kod».

Izvor: habr.com

Kupite pouzdan hosting za web stranice s DDoS zaštitom, VPS VDS servere 🔥 Kupite pouzdan hosting za web stranice s DDoS zaštitom, VPS VDS servere | ProHoster