Terraform zamke

Terraform zamke
Istaknimo nekoliko zamki, uključujući one povezane s petljama, if izjavama i tehnikama implementacije, kao i općenitija pitanja koja utječu na Terraform općenito:

  • parametri count i for_each imaju ograničenja;
  • ograničiti implementacije bez prekida rada;
  • čak i dobar plan može propasti;
  • refactoring može imati svoje zamke;
  • odgođena koherencija je dosljedna... s odgodom.

Parametri count i for_each imaju ograničenja

Primjeri u ovom poglavlju opsežno koriste parametar brojanja i izraz for_each u petljama i uvjetnoj logici. Imaju dobre rezultate, ali imaju dva važna ograničenja kojih morate biti svjesni.

  • Count i for_each ne mogu referencirati nijednu izlaznu varijablu resursa.
  • count i for_each ne mogu se koristiti u konfiguraciji modula.

count i for_each ne mogu referencirati nijednu izlaznu varijablu resursa

Zamislite da trebate implementirati nekoliko EC2 poslužitelja i iz nekog razloga ne želite koristiti ASG. Vaš kod bi mogao biti ovakav:

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

Pogledajmo ih jednu po jednu.

Budući da je parametar count postavljen na statičku vrijednost, ovaj kod će raditi bez problema: kada pokrenete naredbu apply, stvorit će se tri EC2 poslužitelja. Ali što ako želite postaviti jedan poslužitelj u svakoj zoni dostupnosti (AZ) unutar svoje trenutne AWS regije? Možete postaviti da vaš kod učita popis zona iz izvora podataka aws_availability_zones, a zatim proći kroz svaku i stvoriti EC2 poslužitelj u njoj koristeći parametar brojanja i pristup indeksu 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" {}

Ovaj će kod također dobro funkcionirati, budući da parametar brojanja može referencirati izvore podataka bez ikakvih problema. Ali što se događa ako broj poslužitelja koje trebate stvoriti ovisi o izlazu nekog resursa? Da biste to demonstrirali, najlakši način je korištenje resursa random_integer, koji, kao što ime sugerira, vraća slučajni cijeli broj:

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

Ovaj kod generira nasumični broj između 1 i 3. Pogledajmo što će se dogoditi ako pokušamo upotrijebiti izlaz ovog resursa u parametru brojanja resursa aws_instance:

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

Ako pokrenete terraform plan na ovom kodu, dobit ćete sljedeću pogrešku:

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 zahtijeva da se count i for_each izračunaju tijekom faze planiranja, prije nego što se bilo koji resursi kreiraju ili modificiraju. To znači da se count i for_each mogu odnositi na literale, varijable, izvore podataka, pa čak i popise resursa (sve dok se njihova duljina može odrediti u vrijeme raspoređivanja), ali ne i na izračunate izlazne varijable resursa.

count i for_each ne mogu se koristiti u konfiguraciji modula

Jednog dana biste mogli doći u iskušenje da svojoj konfiguraciji modula dodate parametar brojanja:

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 upotrijebiti count unutar modula za stvaranje tri kopije resursa klastera web poslužitelja. Ili biste možda željeli učiniti povezivanje modula opcionalnim na temelju nekog Booleovog uvjeta postavljanjem njegovog parametra brojanja na 0. Ovo bi moglo izgledati kao razuman kod, ali dobit ćete ovu pogrešku kada izvodite plan terraforme:

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, korištenje count ili for_each u resursu modula nije podržano. Prema bilješkama o izdanju Terraform 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 bi mogla biti već dostupna. Da saznam sigurno, ovdje pročitajte dnevnik promjena Terraforma.

Ograničenja implementacija bez prekida rada

Korištenje bloka create_before_destroy u kombinaciji s ASG-om izvrsno je rješenje za stvaranje implementacija bez prekida rada, osim jednog upozorenja: pravila automatskog skaliranja nisu podržana. Ili da budemo precizniji, ovo vraća veličinu ASG-a na min_size pri svakoj implementaciji, što bi mogao biti problem ako ste koristili pravila automatskog skaliranja za povećanje broja poslužitelja koji rade.

Na primjer, modul web server-cluster sadrži par resursa aws_autoscaling_schedule, koji u 9 ujutro povećava broj poslužitelja u klasteru s dva na deset. Ako implementirate u, recimo, 11 sati, novi ASG će se pokrenuti sa samo dva poslužitelja umjesto deset i tako će ostati do 9 ujutro sljedećeg dana.

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

  • Promijenite parametar ponavljanja u aws_autoscaling_schedule s 0 9 * * * (“pokreni u 9 ujutro”) na nešto poput 0-59 9-17 * * * (“pokretanje svake minute od 9 do 5 sati”). Ako ASG već ima deset poslužitelja, ponovno pokretanje ovog pravila automatskog skaliranja neće promijeniti ništa, što je ono što želimo. Ali ako je ASG tek nedavno implementiran, ovo će pravilo osigurati da za maksimalno jednu minutu broj njegovih poslužitelja dosegne deset. Ovo nije sasvim elegantan pristup, a velika skakanja s deset na dva poslužitelja i natrag također mogu stvarati probleme korisnicima.
  • Napravite prilagođenu skriptu koja koristi AWS API za određivanje broja aktivnih poslužitelja u ASG-u, pozovite je pomoću vanjskog izvora podataka (pogledajte "Vanjski izvor podataka" na stranici 249) i postavite parametar ASG-a desire_capacity na vrijednost koju vraća skripta. Na taj će način svaka nova instanca ASG-a uvijek raditi s istim kapacitetom kao i postojeći Terraform kod i otežava održavanje.

Naravno, idealno bi bilo da Terraform ima ugrađenu podršku za implementacije bez prekida rada, ali od svibnja 2019. tim HashiCorpa nije planirao dodati ovu funkcionalnost (detalji - ovdje).

Ispravan plan može biti neuspješno proveden

Ponekad naredba plan proizvede savršeno točan plan postavljanja, ali naredba primijeni vraća pogrešku. Pokušajte, na primjer, dodati resurs aws_iam_user s istim imenom koje ste koristili za IAM korisnika kojeg ste stvorili ranije u poglavlju 2:

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

Sada, ako pokrenete naredbu plan, Terraform će ispisati naizgled razuman plan implementacije:

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.

Ako pokrenete naredbu primijeniti dobit ćete sljedeću pogrešku:

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

Problem je, naravno, što IAM korisnik s tim imenom već postoji. A to se može dogoditi ne samo korisnicima IAM-a, već i gotovo svakom resursu. Moguće je da je netko kreirao ovaj resurs ručno ili pomoću naredbenog retka, ali u svakom slučaju, podudaranje ID-ova dovodi do sukoba. Postoje mnoge varijacije ove pogreške koje često iznenade novopridošlice u Terraformu.

Ključna točka je da naredba terraform plana uzima u obzir samo one resurse koji su navedeni u datoteci stanja Terraform. Ako su resursi kreirani na neki drugi način (npr. ručno klikom na AWS konzoli), oni neće završiti u datoteci stanja i stoga ih Terraform neće uzeti u obzir prilikom izvršavanja plan naredbe. Kao rezultat toga, plan koji se na prvi pogled čini točnim će se pokazati neuspješnim.

Iz ovoga se mogu izvući dvije lekcije.

  • Ako ste već počeli raditi s Terraformom, nemojte koristiti ništa drugo. Ako se dijelom vaše infrastrukture upravlja pomoću Terraforma, više ga ne možete ručno mijenjati. U suprotnom, ne samo da riskirate čudne Terraform pogreške, već također negirate mnoge prednosti IAC-a jer kod više neće biti točan prikaz vaše infrastrukture.
  • Ako već imate neku infrastrukturu, koristite naredbu import. Ako počinjete koristiti Terraform s postojećom infrastrukturom, možete ga dodati u državnu datoteku koristeći terraform import naredbu. Na ovaj način Terraform će znati kojom infrastrukturom treba upravljati. Naredba uvoza uzima dva argumenta. Prva je adresa resursa u vašim konfiguracijskim datotekama. Sintaksa je ovdje ista kao i za veze resursa: _. (poput aws_iam_user.existing_user). Drugi argument je ID resursa koji se uvozi. Recimo da je ID resursa aws_iam_user korisničko ime (na primjer, yevgeniy.brikman), a ID resursa aws_instance je ID EC2 poslužitelja (kao i-190e22e5). Kako uvesti izvor obično je naznačeno u dokumentaciji na dnu njegove stranice.

    Ispod je naredba za uvoz koja sinkronizira resurs aws_iam_user koji ste dodali svojoj Terraform konfiguraciji zajedno s IAM korisnikom u poglavlju 2 (naravno, zamjenjujući svoje ime za yevgeniy.brikman):

    $ terraform import aws_iam_user.existing_user yevgeniy.brikman

    Terraform će pozvati AWS API kako bi pronašao vašeg IAM korisnika i stvorio asocijaciju datoteke stanja između njega i resursa aws_iam_user.existing_user u vašoj Terraform konfiguraciji. Od sada, kada pokrenete naredbu plana, Terraform će znati da IAM korisnik već postoji i neće ga pokušati ponovno stvoriti.

    Vrijedno je napomenuti da ako već imate puno resursa koje želite uvesti u Terraform, ručno pisanje koda i uvoz jednog po jednog može biti gnjavaža. Stoga vrijedi pogledati alat kao što je Terraforming (http://terraforming.dtan4.net/), koji može automatski uvesti kod i stanje s vašeg AWS računa.

    Refactoring može imati svoje zamke

    Refactoring je uobičajena praksa u programiranju gdje mijenjate unutarnju strukturu koda dok vanjsko ponašanje ostavljate nepromijenjenim. Ovo je kako bi kôd bio jasniji, uredniji i lakši za održavanje. Refactoring je nezamjenjiva tehnika koju treba redovito koristiti. Ali kada je riječ o Terraformu ili bilo kojem drugom IaC alatu, morate biti izuzetno oprezni o tome što mislite pod "vanjskim ponašanjem" dijela koda, inače će se pojaviti neočekivani problemi.

    Na primjer, uobičajena vrsta refaktoriranja je zamjena naziva varijabli ili funkcija razumljivijim. Mnogi IDE-ovi imaju ugrađenu podršku za refactoring i mogu automatski preimenovati varijable i funkcije kroz projekt. U programskim jezicima opće namjene, ovo je trivijalan postupak o kojem možda ne razmišljate, ali u Terraformu morate biti izuzetno oprezni s tim, inače bi mogli doći do prekida rada.

    Na primjer, modul klastera web poslužitelja ima ulaznu varijablu cluster_name:

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

    Zamislite da ste počeli koristiti ovaj modul za implementaciju mikroservisa pod nazivom foo. Kasnije želite svoju uslugu preimenovati u bar. Ova se promjena može činiti trivijalnom, ali u stvarnosti može uzrokovati prekid usluge.

    Činjenica je da modul klastera web-poslužitelja koristi varijablu cluster_name u brojnim resursima, uključujući parametar imena 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 imena na resursu, Terraform će izbrisati staru verziju tog resursa i stvoriti novu umjesto nje. Ali ako je taj resurs ALB, između brisanja i preuzimanja nove verzije, nećete imati mehanizam za preusmjeravanje prometa na vaš web poslužitelj. Isto tako, ako se sigurnosna grupa izbriše, vaši će poslužitelji početi odbijati svaki mrežni promet dok se ne stvori nova grupa.

    Druga vrsta refaktoriranja koja bi vas mogla zanimati je promjena Terraform ID-a. Uzmimo kao primjer resurs aws_security_group u modulu klastera web poslužitelja:

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

    Identifikator ovog resursa naziva se instanca. 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 biti na kraju? Tako je: poremećaj.

    Terraform pridružuje svaki ID resursa ID-u pružatelja usluge oblaka. Na primjer, iam_user pridružen je ID-u korisnika AWS IAM, a aws_instance pridružen je ID-u poslužitelja AWS EC2. Ako promijenite ID resursa (recimo iz instance u cluster_instance, kao što je slučaj s aws_security_group), Terraformu će izgledati kao da ste izbrisali stari resurs i dodali novi. Ako primijenite ove promjene, Terraform će izbrisati staru sigurnosnu grupu i stvoriti novu, dok će vaši poslužitelji početi odbijati svaki mrežni promet.

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

    • Uvijek koristite naredbu plan. Može otkriti sve te nedostatke. Pažljivo pregledajte njegov rezultat i obratite pozornost na situacije u kojima Terraform planira izbrisati resurse koji najvjerojatnije ne bi trebali biti izbrisani.
    • Stvorite prije brisanja. Ako želite zamijeniti resurs, dobro razmislite trebate li izraditi zamjenu prije brisanja izvornika. Ako je odgovor potvrdan, create_before_destroy može pomoći. Isti se rezultat može postići ručno izvođenjem dva koraka: prvo dodajte novi resurs u konfiguraciju i pokrenite naredbu primjene, a zatim uklonite stari resurs iz konfiguracije i ponovno upotrijebite naredbu primijeni.
    • Promjena identifikatora zahtijeva promjenu stanja. Ako želite promijeniti ID povezan s resursom (na primjer, preimenovati aws_security_group iz instance u cluster_instance) bez brisanja resursa i stvaranja njegove nove verzije, morate u skladu s tim ažurirati datoteku stanja Terraform. Nikada to ne radite ručno - umjesto toga koristite naredbu terraform state. Prilikom preimenovanja identifikatora, trebali biste pokrenuti naredbu terraform state mv koja ima sljedeću sintaksu:
      terraform state mv <ORIGINAL_REFERENCE> <NEW_REFERENCE>

      ORIGINAL_REFERENCE je izraz koji se odnosi na izvor u njegovom trenutnom obliku, a NEW_REFERENCE je mjesto gdje ga želite premjestiti. Na primjer, kada preimenujete grupu aws_security_group iz instance u cluster_instance, morate pokrenuti sljedeću naredbu:

      $ terraform state mv 
         aws_security_group.instance 
         aws_security_group.cluster_instance

      Ovo govori Terraformu da stanje koje je prethodno bilo povezano s aws_security_group.instance sada treba biti povezano s aws_security_group.cluster_instance. Ako nakon preimenovanja i pokretanja ove naredbe terraform plan ne pokaže nikakve promjene, onda ste sve učinili ispravno.

    • Neke postavke nije moguće promijeniti. Parametri mnogih resursa su nepromjenjivi. Ako ih pokušate promijeniti, Terraform će izbrisati stari resurs i stvoriti novi umjesto njega. Svaka stranica s resursima obično će naznačiti što se događa kada promijenite određenu postavku, stoga svakako provjerite dokumentaciju. Uvijek koristite naredbu plan i razmislite o korištenju strategije create_before_destroy.

    Odgođena dosljednost je dosljedna... s odgodom

    Neki API-ji pružatelja usluga oblaka, kao što je AWS, asinkroni su i imaju odgođenu dosljednost. Asinkronija znači da sučelje može odmah vratiti odgovor bez čekanja da se zahtijevana radnja završi. Odgođena konzistentnost znači da promjenama može trebati neko vrijeme da se prošire cijelim sustavom; dok se to događa, vaši odgovori mogu biti nedosljedni i ovisni o tome koja replika izvora podataka odgovara na vaše API pozive.

    Zamislite, na primjer, da uputite API poziv AWS-u tražeći od njega da stvori EC2 poslužitelj. API će vratiti "uspješan" odgovor (201 Created) gotovo trenutno, bez čekanja da se sam poslužitelj kreira. Ako se odmah pokušate spojiti na njega, gotovo sigurno neće uspjeti jer u tom trenutku AWS još uvijek inicijalizira resurse ili, alternativno, poslužitelj se još nije pokrenuo. Štoviše, ako uputite još jedan poziv da dobijete informacije o ovom poslužitelju, možda ćete primiti pogrešku (404 nije pronađeno). Stvar je u tome što se informacije o ovom EC2 poslužitelju još uvijek mogu širiti kroz AWS prije nego što postanu svugdje dostupne, morat ćete pričekati nekoliko sekundi.

    Kad god koristite asinkroni API s lijenom dosljednošću, morate povremeno ponavljati svoj zahtjev dok se radnja ne dovrši i ne proširi kroz sustav. Nažalost, AWS SDK ne nudi nikakve dobre alate za to, a projekt Terraform prije je patio od puno grešaka poput 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

    Drugim riječima, stvorite resurs (poput podmreže) i zatim pokušate dobiti neke informacije o njemu (poput ID-a novostvorene podmreže), a Terraform je ne može pronaći. Većina ovih grešaka (uključujući 6813) je ispravljena, ali se i dalje pojavljuju s vremena na vrijeme, posebno kada Terraform doda podršku za novu vrstu resursa. To je neugodno, ali u većini slučajeva ne uzrokuje nikakvu štetu. Kada ponovno pokrenete terraform apply, sve bi trebalo raditi, jer će se do tog trenutka informacije već proširiti cijelim sustavom.

    Ovaj odlomak je prikazan iz knjige Evgeniya Brikmana "Terraform: infrastruktura na razini koda".

Izvor: www.habr.com

Dodajte komentar