Terraform zamke

Terraform zamke
Istaknimo nekoliko zamki, uključujući one vezane uz petlje, if naredbe i tehnike implementacije, kao i općenitije probleme koji utječu na Terraform općenito:

  • Parametri count i for_each imaju ograničenja;
  • Ograničenja implementacije bez zastoja;
  • čak i dobar plan može propasti;
  • refaktoriranje može imati svoje zamke;
  • Odgođena konzistentnost je konzistentna s... odgađanjem.

Parametri count i for_each imaju ograničenja

Primjeri u ovom poglavlju opsežno koriste parametar count i izraz for_each u petljama i uvjetnoj logici. Iako ove naredbe dobro funkcioniraju, imaju dva važna ograničenja kojih treba biti svjestan.

  • Nijedna varijabla izlaznog resursa ne može se referencirati u count ili for_each.
  • Postavke count i for_each ne mogu se koristiti u konfiguraciji modula.

Nijedna varijabla izlaznog resursa ne može se referencirati u count ili for_each

Zamislite da trebate implementirati nekoliko EC2 servera i iz nekog razloga ne želite koristiti ASG. Vaš kod bi mogao izgledati ovako:

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 će kod raditi bez problema: kada pokrenete naredbu apply, stvorit će se tri EC2 poslužitelja. Ali što ako želite implementirati jedan poslužitelj u svakoj zoni dostupnosti (AZ) unutar trenutne AWS regije? Vaš kod može učitati popis zona iz izvora podataka aws_availability_zones, a zatim proći kroz svaku od njih, stvarajući EC2 poslužitelj u njoj pomoću parametra count i pristupajući 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 će kod također dobro raditi, budući da parametar count može bez problema referencirati izvore podataka. 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 možete pretpostaviti iz njegovog imena, vraća slučajni cijeli broj:

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

Ovaj kod generira slučajni broj između 1 i 3. Pogledajmo što se događa ako pokušamo koristiti izlazni rezultat 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 terraform plan na ovom kodu, 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

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 procijene u vrijeme raspoređivanja, prije nego što se bilo koji resursi kreiraju ili modificiraju. To znači da count i for_each mogu referencirati literale, varijable, izvore podataka, pa čak i popise resursa (pod uvjetom da se njihova duljina može odrediti u vrijeme raspoređivanja), ali ne i izračunate varijable izlaza resursa.

count i for_each ne mogu se koristiti u konfiguraciji modula

U nekom trenutku biste mogli biti u iskušenju da dodate parametar count u konfiguraciju 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 biste možda željeli učiniti uključivanje modula opcionalnim na temelju nekog Booleovog uvjeta postavljanjem njegovog parametra count na 0. Ovaj kod može izgledati razumno, ali kada pokrenete Terraform Plan, 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

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

Nažalost, od 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 značajku u budućnosti, tako da, ovisno o tome kada ovo čitate, možda je već dostupna. Da biste bili sigurni, Pročitajte popis promjena Terraforma ovdje.

Ograničenja implementacija bez zastoja

Korištenje bloka create_before_destroy zajedno s ASG-om izvrsno je rješenje za implementacije bez zastoja, osim jedne napomene: ne podržava pravila automatskog skaliranja. Točnije, resetira veličinu ASG-a natrag na min_size pri svakoj implementaciji, što može biti problem ako ste koristili pravila automatskog skaliranja za povećanje broja pokrenutih poslužitelja.

Na primjer, modul webserver-cluster sadrži par resursa aws_autoscaling_schedule koji povećavaju broj poslužitelja u klasteru s dva na deset u 9:00 sati. Ako ga implementirate, recimo, u 11:00 sati, novi ASG će se pokrenuti sa samo dva poslužitelja umjesto deset i ostat će u tom stanju do 9:00 sati sljedećeg dana.

Ovo ograničenje se 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 * * * ("pokreni svake minute od 9 ujutro do 5 sati"). Ako ASG već ima deset poslužitelja, ponovno pokretanje ovog pravila automatskog skaliranja neće ništa promijeniti, što je ono što želimo. Međutim, ako je ASG novo implementiran, ovo pravilo jamči da će dosegnuti deset poslužitelja u maksimalno jednoj minuti. Ovo nije baš elegantan pristup, a veliki skokovi s deset na dva poslužitelja i natrag također mogu uzrokovati probleme korisnicima.
  • Izradite 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 desired_capacity ASG-a na vrijednost koju vraća ova skripta. To osigurava da svaka nova instanca ASG-a uvijek počinje s istim kapacitetom kao i stari Terraform kod, što otežava održavanje.

Naravno, idealno bi bilo da Terraform ima ugrađenu podršku za implementacije bez zastoja, ali od svibnja 2019. HashiCorp tim nije imao planove dodati ovu funkcionalnost (detalji su ovdje).

Ispravan plan može biti neuspješno proveden

Ponekad pokretanje naredbe plan daje savršeno valjan plan implementacije, ali naredba apply vraća grešku. Na primjer, pokušajte dodati resurs aws_iam_user s istim imenom koje ste koristili za IAM korisnika kojeg ste ranije stvorili u 2. poglavlju:

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

Sada, ako pokrenete naredbu plan, Terraform će ispisati plan implementacije koji na prvi pogled izgleda razumno:

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 apply, dobit ćete sljedeću greš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, u tome što IAM korisnik s tim imenom već postoji. To se može dogoditi ne samo IAM korisnicima, već gotovo svakom resursu. Netko je možda ručno ili putem naredbenog retka stvorio resurs, ali u svakom slučaju, sukobi ID-ova dovode do sukoba. Ova greška ima mnogo varijacija, često iznenađujući nove korisnike Terraforma.

Ključna stvar je da naredba terraform plan uzima u obzir samo resurse navedene u Terraform datoteci stanja. Ako se resursi kreiraju na bilo koji drugi način (na primjer, ručno, klikom miša u AWS konzoli), neće biti uključeni u datoteku stanja i stoga ih Terraform neće uzeti u obzir prilikom izvršavanja naredbe plan. Kao rezultat toga, naizgled ispravan plan neće uspjeti.

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ć i poništavate mnoge prednosti IaC-a, jer kod više neće točno predstavljati vašu infrastrukturu.
  • Ako već imate neku infrastrukturu, upotrijebite naredbu import. Ako počinjete koristiti Terraform s postojećom infrastrukturom, možete je dodati u svoju datoteku stanja pomoću naredbe terraform import. Ovo govori Terraformu kojom infrastrukturom treba upravljati. Naredba import prima dva argumenta. Prvi je adresa resursa u vašim konfiguracijskim datotekama. Koristi istu sintaksu kao i reference resursa: _. (kao aws_iam_user.existing_user). Drugi argument je ID resursa za uvoz. Na primjer, ID resursa aws_iam_user je korisničko ime (npr. yevgeniy.brikman), a ID resursa aws_instance je ID EC2 poslužitelja (kao i-190e22e5). Kako uvesti resurs obično je navedeno u dokumentaciji na dnu stranice.

    Evo naredbe za uvoz koja sinkronizira resurs aws_iam_user koji ste dodali svojoj Terraform konfiguraciji zajedno s IAM korisnikom u 2. poglavlju (naravno, zamijenite svoje ime s yevgeniy.brikman):

    $ terraform import aws_iam_user.existing_user yevgeniy.brikman

    Terraform će pristupiti AWS API-ju kako bi pronašao vašeg IAM korisnika i stvorio povezanost datoteke stanja između njega i resursa aws_iam_user.existing_user u vašoj Terraform konfiguraciji. Od ovog trenutka nadalje, kada pokrenete naredbu plan, Terraform će znati da IAM korisnik već postoji i neće ga pokušavati ponovno stvoriti.

    Vrijedi napomenuti da ako već imate mnogo resursa koje želite uvesti u Terraform, ručno pisanje koda i uvoz svakog pojedinačno može biti nezgrapno. Stoga je vrijedno razmotriti alat poput Terraforminga (http://terraforming.dtan4.net/), koji može automatski uvesti kod i stanje s vašeg AWS računa.

    Refaktoriranje može imati svoje zamke

    Refaktoriranje Refaktoriranje je uobičajena programerska praksa u kojoj mijenjate unutarnju strukturu koda, a vanjsko ponašanje ostavljate nepromijenjenim. To se radi kako bi kod bio razumljiviji, uredniji i lakši za održavanje. Refaktoriranje je neizostavna tehnika koju treba redovito koristiti. Ali kada je riječ o Terraformu ili bilo kojem drugom IaC alatu, trebali biste biti izuzetno oprezni oko toga što podrazumijevate pod "vanjskim ponašanjem" dijela koda, inače će se pojaviti neočekivani problemi.

    Na primjer, uobičajeno refaktoriranje je promjena naziva varijabli ili funkcija u razumljivije. Mnogi IDE-ovi imaju ugrađenu podršku za refaktoriranje i mogu automatski preimenovati varijable i funkcije tijekom projekta. U programskim jezicima opće namjene ovo je trivijalan postupak koji se može previdjeti, ali u Terraformu je potreban izuzetan oprez kako bi se izbjegli prekidi rada.

    Na primjer, modul webserver-cluster 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 preimenovati svoj servis u bar. Ova promjena može se činiti trivijalnom, ali u stvarnosti bi mogla uzrokovati prekide rada.

    Poanta je u tome da modul webserver-cluster koristi varijablu cluster_name u brojnim resursima, 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 naziva resursa, Terraform će izbrisati staru verziju tog resursa i na njegovom mjestu stvoriti novu. Međutim, ako je resurs ALB, nećete imati mehanizam za preusmjeravanje prometa na svoj web poslužitelj između njegovog brisanja i učitavanja nove verzije. Slično tome, ako se sigurnosna grupa izbriše, vaši će poslužitelji početi odbijati sav mrežni promet dok se ne stvori nova grupa.

    Još jedno refaktoriranje koje bi vas moglo zanimati je promjena Terraform identifikatora. Uzmimo za primjer resurs aws_security_group u modulu webserver-cluster:

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

    Identifikator za ovaj resurs naziva se instanca. Zamislite da tijekom refaktoriranja odlučite promijeniti ga u opisniji (po vašem mišljenju) naziv cluster_instance:

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

    Što će se na kraju dogoditi? Tako je: prekid usluge.

    Terraform povezuje svaki ID resursa s ID-om pružatelja usluga u oblaku. Na primjer, iam_user je povezan s ID-om korisnika AWS IAM-a, a aws_instance je povezan s ID-om AWS EC2 poslužitelja. Ako promijenite ID resursa (recimo, s instance na cluster_instance, kao kod aws_security_group), Terraform će to vidjeti kao da ste izbrisali stari resurs i dodali novi. Primjena ovih promjena uzrokovat će da Terraform izbriše staru sigurnosnu grupu i stvori novu, dok će vaši poslužitelji početi odbijati sav mrežni promet.

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

    • Uvijek koristite naredbu plan. Ona može identificirati sve ove probleme. Pažljivo pregledajte njezin izlaz i obratite pozornost na situacije u kojima Terraform planira izbrisati resurse koji vjerojatno ne bi trebali biti izbrisani.
    • Stvori prije uništavanja. Ako želite zamijeniti resurs, pažljivo razmotrite trebate li stvoriti zamjenu prije brisanja originala. Ako je tako, create_before_destroy može pomoći. Isti rezultat može se postići ručno u dva koraka: prvo, dodajte novi resurs u konfiguraciju i pokrenite naredbu apply, zatim uklonite stari resurs iz konfiguracije i ponovno pokrenite naredbu apply.
    • Promjena identifikatora zahtijeva promjenu stanja. Ako želite promijeniti identifikator povezan s resursom (na primjer, preimenovanje aws_security_group iz instance u cluster_instance) bez brisanja resursa i stvaranja nove verzije, morate ažurirati datoteku stanja Terraforma u skladu s tim. 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 referencira resurs u njegovom trenutnom stanju, a NEW_REFERENCE je lokacija na koju ga želite premjestiti. Na primjer, da biste preimenovali aws_security_group iz instance u cluster_instance, pokrenuli biste 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 Terraform Plan ne pokazuje promjene nakon preimenovanja i pokretanja ove naredbe, sve ste ispravno napravili.

    • Neki se parametri ne mogu mijenjati. Mnogi parametri resursa su nepromjenjivi. Ako ih pokušate promijeniti, Terraform će izbrisati stari resurs i na njegovom mjestu stvoriti novi. Svaka stranica resursa obično navodi što se događa kada se parametar promijeni, stoga svakako konzultirajte dokumentaciju. Uvijek koristite naredbu plan i razmislite o korištenju strategije create_before_destroy.

    Odgođena konzistentnost je u skladu s… kašnjenjem

    API-ji nekih pružatelja usluga u oblaku, kao što je AWS, su asinkroni i imaju odgođenu konzistentnost. Asinkronost znači da sučelje može odmah vratiti odgovor, bez čekanja da se tražena radnja dovrši. Odgođena konzistentnost znači da je potrebno vrijeme da se promjene prošire kroz sustav; dok se to događa, vaši odgovori mogu biti nekonzistentni i ovisiti o tome koja replika izvora podataka odgovara na vaše API pozive.

    Na primjer, zamislite da uputite API poziv AWS-u tražeći stvaranje EC2 poslužitelja. API će gotovo odmah vratiti odgovor "uspjeh" (201 Created) bez čekanja da se sam poslužitelj stvori. Ako pokušate odmah uspostaviti vezu, gotovo sigurno neće uspjeti jer AWS još uvijek inicijalizira resurse ili, alternativno, poslužitelj se još nije pokrenuo. Nadalje, ako uputite još jedan poziv za dobivanje informacija o ovom poslužitelju, mogli biste dobiti grešku (404 Not Found). To je zato što se informacije o ovom EC2 poslužitelju možda još uvijek šire kroz AWS, pa će trebati nekoliko sekundi da budu dostupne negdje drugdje.

    Kad god koristite asinkroni API s lijenom konzistentnošću, morate periodički ponovno pokušavati poslati zahtjev dok se radnja ne dovrši i proširi po cijelom sustavu. Nažalost, AWS SDK ne pruža dobre alate za to, a projekt Terraform je prethodno patio od brojnih 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 (na primjer, podmrežu), a zatim pokušate dobiti neke informacije o njemu (poput ID-a novostvorene podmreže), a Terraform ga ne može pronaći. Većina tih pogrešaka (uključujući 6813) je ispravljena, ali se i dalje povremeno pojavljuju, posebno kada Terraform doda podršku za novu vrstu resursa. To je dosadno, ali u većini slučajeva je bezopasno. Ponovno pokretanje naredbe terraform apply trebalo bi funkcionirati, jer će se informacije do tada proširiti po cijelom sustavu.

    Ovaj odlomak je iz knjige Evgenyja Brickmana Terraform: Infrastruktura na razini koda.

Izvor: www.habr.com