Terraformske zamke

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

  • parametri count i for_each imaju ograničenja;
  • ograničiti nultu implementaciju zastoja;
  • čak i dobar plan može propasti;
  • refaktoring može imati svoje zamke;
  • odložena koherentnost je u skladu... sa odlaganjem.

Parametri count i for_each imaju ograničenja

Primeri u ovom poglavlju u velikoj meri koriste parametar brojanja i izraz for_each u petljama i uslovnoj logici. Oni rade dobro, 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 se ne mogu koristiti u konfiguraciji modula.

count i for_each ne mogu referencirati nijednu izlaznu varijablu resursa

Zamislite da trebate postaviti nekoliko EC2 servera 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 jedan po jedan.

Pošto je parametar brojanja postavljen na statičku vrijednost, ovaj kod će raditi bez problema: kada pokrenete naredbu primjene, kreirat će tri EC2 servera. Ali šta ako želite da primenite jedan server u svakoj zoni dostupnosti (AZ) u okviru vašeg trenutnog AWS regiona? Vaš kod možete učitati listu zona iz izvora podataka aws_availability_zones, a zatim proći kroz svaku od njih i kreirati EC2 server u njemu koristeći parametar brojanja i pristup indeksu niza:

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 će također dobro funkcionirati, budući da parametar count može bez problema referencirati izvore podataka. Ali šta se dešava ako broj servera koje treba da kreirate zavisi od izlaza nekog resursa? Da biste to demonstrirali, najlakši način je korištenje resursa random_integer, koji, kao što ime kaže, vraća nasumični cijeli broj:

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

Ovaj kod generiše nasumični broj između 1 i 3. Hajde da vidimo šta će se desiti ako pokušamo da koristimo 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 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 izračunaju tokom faze planiranja, prije nego što se bilo koji resurs kreira ili modificira. To znači da se count i for_each mogu odnositi na literale, varijable, izvore podataka, pa čak i liste resursa (sve dok se njihova dužina može odrediti u vrijeme planiranja), ali ne i na izračunate izlazne varijable resursa.

count i for_each se ne mogu koristiti u konfiguraciji modula

Jednog dana biste mogli biti u iskušenju da dodate parametar brojanja 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 kreiranje tri kopije resursa klastera web servera. Ili biste možda željeli učiniti povezivanje modula opcijskim na osnovu nekog Booleovog uvjeta tako što ćete postaviti njegov parametar brojanja na 0. Ovo bi moglo izgledati kao razuman kod, ali dobit ćete ovu grešku kada pokrenete terraform plan:

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, upotreba count ili for_each u resursu modula nije podržana. Prema napomenama o izdanju Terraform 0.12 (http://bit.ly/3257bv4), HashiCorp planira dodati ovu mogućnost u budućnosti, tako da ovisno o tome kada pročitate ovu knjigu, možda će već biti dostupna. Da saznate sigurno, pročitajte Terraform dnevnik promjena ovdje.

Ograničenja implementacije bez zastoja

Korištenje bloka create_before_destroy u kombinaciji sa ASG-om je odlično rješenje za kreiranje implementacija bez prekida rada, osim jednog upozorenja: pravila automatskog skaliranja nisu podržana. Ili da budemo precizniji, ovo vraća ASG veličinu na min_size pri svakoj implementaciji, što bi mogao biti problem ako koristite pravila automatskog skaliranja da povećate broj servera koji rade.

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

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

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

Naravno, Terraform bi idealno imao ugrađenu podršku za implementacije bez zastoja, ali od maja 2019., HashiCorp tim nije planirao dodati ovu funkcionalnost (detalji - ovdje).

Ispravan plan može biti neuspješno implementiran

Ponekad naredba plan proizvodi savršeno ispravan plan implementacije, ali naredba apply vraća grešku. Pokušajte, na primjer, dodati resurs aws_iam_user s istim imenom koje ste koristili za IAM korisnika kojeg ste kreirali ranije u 2. poglavlju:

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

Sada, ako pokrenete naredbu plan, Terraform će dati 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 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, što IAM korisnik sa tim imenom već postoji. A to se može dogoditi ne samo korisnicima IAM-a, već gotovo svakom resursu. Moguće je da je neko kreirao ovaj resurs ručno ili koristeći komandnu liniju, ali u svakom slučaju, podudaranje ID-ova dovodi do sukoba. Postoje mnoge varijacije ove greške koje često iznenade pridošlice u Terraformu.

Ključna stvar je da komanda terraform plana uzima u obzir samo one resurse koji su specificirani u datoteci stanja Terraform. Ako se resursi kreiraju na neki drugi način (na primjer, 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 naredbe plana. Kao rezultat toga, plan koji se na prvi pogled čini ispravnim će se pokazati neuspjelim.

Iz ovoga se mogu izvući dvije lekcije.

  • Ako ste već počeli raditi sa Terraformom, nemojte koristiti ništa drugo. Ako se dijelom vaše infrastrukture upravlja pomoću Terraforma, više ga ne možete mijenjati ručno. U suprotnom, ne samo da rizikujete čudne Terraform greške, već takođe negirate mnoge prednosti IaC-a jer kod više neće biti tačan prikaz vaše infrastrukture.
  • Ako već imate neku infrastrukturu, koristite naredbu import. Ako počinjete da koristite Terraform sa postojećom infrastrukturom, možete ga dodati u datoteku stanja pomoću komande terraform import. Na ovaj način Terraform će znati kojom infrastrukturom treba upravljati. Komanda import uzima dva argumenta. Prva je adresa resursa u vašim konfiguracijskim datotekama. Ovdje je sintaksa ista kao i za veze resursa: _. (kao 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 servera (kao i-190e22e5). Kako uvesti resurs obično je naznačeno u dokumentaciji na dnu njegove stranice.

    Ispod je naredba uvoza koja sinkronizira resurs aws_iam_user koji ste dodali u svoju Terraform konfiguraciju zajedno sa IAM korisnikom u poglavlju 2 (naravno, zamjenom vašeg imena za yevgeniy.brikman):

    $ terraform import aws_iam_user.existing_user yevgeniy.brikman

    Terraform će pozvati AWS API da pronađe vašeg IAM korisnika i kreira 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 pokušati ponovo da ga kreira.

    Vrijedi napomenuti da ako već imate puno resursa koje želite da uvezete u Terraform, ručno pisanje koda i uvoz svakog pojedinačno može biti problem. Stoga je vrijedno pogledati alat kao što je Terraforming (http://terraforming.dtan4.net/), koji može automatski uvesti kod i stanje sa vašeg AWS naloga.

    Refaktoring može imati svoje zamke

    Refaktoring je uobičajena praksa u programiranju gdje mijenjate unutrašnju strukturu koda dok vanjsko ponašanje ostavljate nepromijenjenim. Ovo je da bi kod bio jasniji, uredniji i lakši za održavanje. Refaktoring je nezaobilazna tehnika koju treba redovno koristiti. Ali kada je u pitanju Terraform ili bilo koji drugi IaC alat, morate biti izuzetno oprezni što podrazumijevate pod „spoljnim ponašanjem“ dijela koda, inače će se pojaviti neočekivani problemi.

    Na primjer, uobičajen tip refaktoriranja je zamjena imena varijabli ili funkcija razumljivijima. Mnogi IDE-ovi imaju ugrađenu podršku za refaktoring i mogu automatski preimenovati varijable i funkcije u cijelom projektu. U programskim jezicima opće namjene, ovo je trivijalna procedura o kojoj možda nećete razmišljati, ali u Terraformu morate biti izuzetno oprezni s ovim, inače možete doći do prekida 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 postavljanje mikroservisa pod nazivom foo. Kasnije želite da preimenujete svoju uslugu u bar. Ova promjena može izgledati trivijalno, ali u stvarnosti može uzrokovati poremećaje u usluzi.

    Činjenica je da modul webserver-cluster koristi varijablu cluster_name u brojnim resursima, uključujući parametar imena dvije sigurnosne grupe 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 na njenom mjestu stvoriti novu. Ali ako je taj resurs ALB, između brisanja i preuzimanja nove verzije, nećete imati mehanizam za preusmjeravanje prometa na vaš web server. Isto tako, ako se izbriše bezbednosna grupa, vaši serveri će početi da odbijaju svaki mrežni saobraćaj dok se ne kreira nova grupa.

    Druga vrsta refaktoriranja koja bi vas mogla zanimati je promjena Terraform ID-a. Uzmimo za primjer resurs aws_security_group u modulu webserver-cluster:

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

    Identifikator ovog resursa naziva se instanca. Zamislite da ste tokom refaktoriranja odlučili da ga promijenite u razumljivije (po vašem mišljenju) ime cluster_instance:

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

    Šta će se na kraju dogoditi? Tako je: poremećaj.

    Terraform povezuje svaki ID resursa s ID-om dobavljača u oblaku. Na primjer, iam_user je pridružen AWS IAM ID-u korisnika, a aws_instance je pridružen ID-u AWS EC2 servera. Ako promijenite ID resursa (recimo iz instance u cluster_instance, kao što je slučaj sa aws_security_group), u Terraform će izgledati kao da ste izbrisali stari resurs i dodali novi. Ako primijenite ove promjene, Terraform će izbrisati staru sigurnosnu grupu i kreirati novu, dok će vaši serveri 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 ove prepreke. Pažljivo pregledajte njegov izlaz i obratite pažnju na situacije u kojima Terraform planira izbrisati resurse koji najvjerovatnije ne bi trebali biti obrisani.
    • Kreirajte prije nego što obrišete. Ako želite zamijeniti resurs, dobro razmislite o tome trebate li kreirati zamjenu prije nego što izbrišete original. Ako je odgovor da, create_before_destroy može pomoći. Isti rezultat se 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 ponovo koristite naredbu primjene.
    • 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 kreiranja nove verzije istog, morate ažurirati datoteku stanja Terraform u skladu s tim. Nikada nemojte ovo raditi ručno - umjesto toga koristite naredbu stanja terraform. Kada preimenujete identifikatore, 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 resurs u njegovom trenutnom obliku, a NEW_REFERENCE je mjesto na koje želite da ga premjestite. Na primjer, kada preimenujete grupu aws_security_group iz instance u cluster_instance, trebate 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 sa aws_security_group.instance sada treba biti povezano sa aws_security_group.cluster_instance. Ako nakon preimenovanja i pokretanja ove komande terraform plan ne pokaže nikakve promjene, onda ste sve uradili ispravno.

    • Neke postavke se ne mogu promijeniti. Parametri mnogih resursa su nepromjenjivi. Ako ih pokušate promijeniti, Terraform će izbrisati stari resurs i na njegovom mjestu stvoriti novi. Svaka stranica resursa će obično naznačiti šta se dešava kada promijenite određenu postavku, pa svakako provjerite dokumentaciju. Uvijek koristite naredbu plan i razmislite o korištenju strategije create_before_destroy.

    Odložena konzistentnost je u skladu... sa odlaganjem

    API-ji nekih dobavljača oblaka, kao što je AWS, su asinhroni i imaju odloženu konzistentnost. Asinhroni znači da sučelje može odmah vratiti odgovor bez čekanja da se tražena radnja završi. Odgođena konzistentnost znači da promjenama može biti potrebno vrijeme da se prošire kroz sistem; dok se ovo dešava, vaši odgovori mogu biti nedosljedni i ovise 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 kreira EC2 server. API će vratiti "uspješan" odgovor (201 Created) gotovo trenutno, bez čekanja da se sam server kreira. Ako se pokušate odmah povezati s njim, gotovo sigurno neće uspjeti jer u tom trenutku AWS još uvijek inicijalizira resurse ili, alternativno, server se još nije pokrenuo. Štaviše, ako uputite još jedan poziv da biste dobili informacije o ovom serveru, možda ćete dobiti grešku (404 Nije pronađeno). Stvar je u tome što se informacije o ovom EC2 serveru još uvijek mogu širiti kroz AWS prije nego što postanu dostupne svuda, morat ćete pričekati nekoliko sekundi.

    Kad god koristite asinhroni API sa lijenom konzistentnošću, morate periodično ponavljati svoj zahtjev dok se radnja ne završi i proširi kroz sistem. Nažalost, AWS SDK ne pruža nikakve dobre alate za ovo, a projekat Terraform je patio od mnogo 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, kreirate resurs (poput podmreže) i zatim pokušavate dobiti neke informacije o njemu (kao što je ID novokreirane podmreže), a Terraform ga 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 dodaje podršku za novi tip resursa. Ovo je neugodno, ali u većini slučajeva ne uzrokuje nikakvu štetu. Kada ponovo pokrenete terraform apply, sve bi trebalo da radi, jer će se do tog vremena informacije već proširiti po sistemu.

    Ovaj odlomak je predstavljen iz knjige Evgenija Brikmana "Terraform: infrastruktura na nivou koda".

izvor: www.habr.com

Dodajte komentar