Să evidențiem câteva capcane, inclusiv cele legate de cicluri, expresii if și metode de desfășurare, precum și problemele mai generale care afectează Terraform în general:
- parametrii count și for_each au limitări;
- limitările desfășurărilor cu timp de nefuncționare zero;
- chiar și un plan bun poate eșua;
- refactorizarea poate avea propriile sale capcane;
- coerența amânată se aliniază… cu amânarea.
Parametrii count și for_each au limitări
În exemplele acestei capitole, parametrul count și expresia for_each sunt folosite activ în cicluri și logica condițională. Acestea funcționează bine, dar au două limitări importante de care trebuie să fiți conștienți.
- În count și for_each nu se poate face referire la variabilele de ieșire ale resurselor.
- count și for_each nu pot fi folosite în configurația modulului.
În count și for_each nu se poate face referire la variabilele de ieșire ale resurselor.
Imaginați-vă că trebuie să desfășurați mai multe servere EC2 și dintr-un anumit motiv nu doriți să folosiți ASG. Codul dumneavoastră poate arăta astfel:
resource "aws_instance" "example_1" {
count = 3
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t2.micro"
}
Să le discutăm pe rând.
Deoarece parametrului count i-a fost atribuită o valoare statică, acest cod va funcționa fără probleme: când veți executa comanda apply, va crea trei servere EC2. Dar dacă doriți să desfășurați câte un server în fiecare zonă de disponibilitate (Availability Zone sau AZ) în cadrul regiunii AWS actuale? Puteți face astfel încât codul dumneavoastră să încarce lista zonelor din sursa de date aws_availability_zones și apoi să parcurgă „cicluri” peste fiecare dintre ele și să creeze un server EC2 în fiecare, folosind parametrul count și accesând matricea prin indice:
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" {}Acest cod va funcționa de asemenea fără probleme, deoarece parametrul count poate face referire fără probleme la sursele de date. Dar ce se va întâmpla dacă numărul de servere pe care trebuie să le creați depinde de ieșirea unei resurse? Pentru a demonstra acest lucru, e cel mai simplu să folosim resursa random_integer, care, după cum sugerează și numele, returnează un număr întreg random:
resource "random_integer" "num_instances" {
min = 1
max = 3
}Acest cod generează un număr aleatoriu între 1 și 3. Să vedem ce se întâmplă dacă încercăm să folosim ieșirea result a acestui resursă în parametrul count al resursei aws_instance:
resource "aws_instance" "example_3" {
count = random_integer.num_instances.result
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t2.micro"
}Dacă executați acest cod terraform plan, veți obține următoarea eroare:
Error: Argument count invalid
pe linia 30 din main.tf, în resource "aws_instance" "example_3":
30: count = random_integer.num_instances.result
Valoarea "count" depinde de atributele resursei care nu pot fi determinate până la aplicare, așadar Terraform nu poate prezice câte instanțe vor fi create. Pentru a o evita, folosiți argumentul -target pentru a aplica mai întâi doar resursele de care depinde count.Terraform necesită ca count și for_each să fie calculate în etapa de planificare, înainte de a crea sau modifica vreo resursă. Aceasta înseamnă că count și for_each pot face referire la litere, variabile, surse de date și chiar liste de resurse (atâta timp cât lungimea lor poate fi determinată în timpul planificării), dar nu la variabilele de ieșire calculate ale resursei.
count și for_each nu pot fi utilizate în configurația modulului
Cândva, poate vei simți tentația să adaugi parametrul count în configurațiile modulului:
module "count_example" {
source = "..\/..\/..\/..\/modules\/services\/webserver-cluster"
count = 3
cluster_name = "terraform-up-and-running-example"
server_port = 8080
instance_type = "t2.micro"
}Acest cod încearcă să folosească count în interiorul modulului pentru a crea trei copii ale resursei webserver-cluster. Sau, poate doriți să faceți conexiunea modulului opțională în funcție de o anumită condiție booleană, atribuing parametrului count valoarea 0. Un astfel de cod ar părea destul de rezonabil, totuși, ca rezultat al execuției terraform plan veți obține următoarea eroare:
Error: Nume de argument rezervat în blocul modulului
pe linia 13 din main.tf, în module "count_example":
13: count = 3
Numele "count" este rezervat pentru utilizare într-o versiune viitoare a Terraform.Din păcate, la momentul lansării Terraform 0.12.6, utilizarea count sau for_each în resursa modul nu este suportată. Conform notelor de lansare Terraform 0.12 (http://bit.ly/3257bv4), HashiCorp plănuiește să adauge această capacitate în viitor, așadar, în funcție de când citiți această carte, aceasta poate fi deja disponibilă. Pentru a verifica cu certitudine, .
Limitări ale desfășurărilor cu timp de nefuncționare zero
Utilizarea blocului create_before_destroy împreună cu ASG este o soluție excelentă pentru organizarea desfășurărilor cu timp de nefuncționare zero, cu un mic nuanț: regulile de scalare automată nu sunt acceptate. Sau, pentru a fi mai precis, aceasta resetează dimensiunea ASG înapoi la min_size la fiecare desfășurare, ceea ce poate deveni o problemă dacă ai folosit reguli de scalare automată pentru a crește numărul de servere active.
De exemplu, modulul webserver-cluster conține câteva resurse aws_autoscaling_schedule care, la ora 9 dimineața, cresc numărul de servere din cluster de la două la zece. Dacă desfășurarea se efectuează, să zicem, la ora 11, noua grupă ASG se va lansa nu cu zece, ci cu doar două servere și va rămâne în această stare până la ora 9 dimineața a zilei următoare.
Această restricție poate fi ocolită în mai multe feluri.
- Schimbă parametrul recurrence în aws_autoscaling_schedule de la 0 9 * * * („rula la 9 dimineața”) la ceva de genul 0-59 9-17 * * * („rula în fiecare minut de la 9 dimineața la 5 după-amiaza”). Dacă în ASG sunt deja zece servere, reaplicarea acestei reguli de scalare automată nu va schimba nimic, ceea ce este ceea ce ne dorim. Dar dacă grupa ASG a fost desfășurată recent, această regulă garantează că, în maxim un minut, numărul serverelor sale va ajunge la zece. Aceasta nu este o abordare foarte elegantă, iar salturile mari de la zece la două servere și invers pot provoca de asemenea probleme utilizatorilor.
- Creează un script personalizat care utilizează API AWS pentru a determina numărul de servere active în ASG, apelându-l printr-o sursă externă de date (vezi secțiunea „Sursă externă de date” la pagina 249) și atribuind parametrului desired_capacity al grupului ASG valoarea returnată de acest script. Astfel, fiecare nou exemplu ASG va fi întotdeauna lansat cu aceeași capacitate, ceea ce complică întreținerea codului Terraform.
Desigur, în ideal, Terraform ar trebui să aibă suport încorporat pentru desfășurări cu timp de nefuncționare zero, dar, la starea din mai 2019, echipa HashiCorp nu avea în plan adăugarea acestei funcționalități ().
Un plan corect poate fi implementat eșuat
Uneori, când executați comanda plan, obțineți un plan de desfășurare corect, totuși comanda apply returnează o eroare. Încercați, de exemplu, să adăugați resursa aws_iam_user cu același nume pe care l-ați folosit pentru utilizatorul IAM pe care l-ați creat anterior în capitolul 2:
resource "aws_iam_user" "existing_user" {
# Introduceți aici numele utilizatorului IAM existent,
# pentru a practica utilizarea comenzii terraform import
name = "yevgeniy.brikman"
}Acum, dacă executați comanda plan, Terraform va afișa, la prima vedere, un plan de desfășurare rezonabil:
Terraform va efectua următoarele acțiuni:
# aws_iam_user.existing_user va fi creat
+ resource "aws_iam_user" "existing_user" {
+ arn = (cunoscut după aplicare)
+ force_destroy = false
+ id = (cunoscut după aplicare)
+ name = "yevgeniy.brikman"
+ path = "\/"
+ unique_id = (cunoscut după aplicare)
}
Plan: 1 de adăugat, 0 de modificat, 0 de distrus.Dacă executați comanda apply, veți obține următoarea eroare:
Error: Eroare la crearea utilizatorului IAM yevgeniy.brikman: EntityAlreadyExists:
Utilizatorul cu numele yevgeniy.brikman există deja.
pe linia 10 din main.tf, în resource "aws_iam_user" "existing_user":
10: resource "aws_iam_user" "existing_user" {Problema, bineînțeles, este că utilizatorul IAM cu acest nume există deja. Și acest lucru se poate întâmpla nu doar cu utilizatorii IAM, ci și cu aproape orice resursă. Poate că cineva a creat această resursă manual sau prin linia de comandă, dar, în orice caz, o coliziune de identificatori duce la conflicte. Această eroare are multe variante, care îi surprind adesea pe începători în Terraform.
Punctul cheie este că comanda terraform plan ia în considerare doar resursele specificate în fișierul de stare Terraform. Dacă resursele sunt create în alte moduri (de exemplu, manual, printr-un clic în consola AWS), ele nu vor apărea în fișierul de stare și, prin urmare, Terraform nu le va lua în calcul când executați comanda plan. Ca rezultat, un plan corect la prima vedere va eșua.
Din aceasta se pot trage două lecții.
- Dacă ați început deja să lucrați cu Terraform, nu folosiți nimic altceva. Dacă o parte a infrastructurii dvs. este gestionată cu Terraform, nu mai trebuie modificată manual. În caz contrar, nu numai că riscați să obțineți erori ciudate în Terraform, dar, de asemenea, anulați multe dintre avantajele IaC, deoarece codul nu va mai fi o reprezentare exactă a infrastructurii dvs.
- Dacă aveți deja o anumită infrastructură, utilizați comanda import. Dacă începeți să folosiți Terraform cu o infrastructură existentă, aceasta poate fi adăugată în fișierul de stări folosind comanda terraform import. Astfel, Terraform va ști ce infrastructură trebuie gestionată. Comanda import acceptă doi argumente. Primul este adresa resursei din fișierele dvs. de configurare. Aici se folosește aceeași sintaxă ca în referințele la resurse: _. (de exemplu, aws_iam_user.existing_user). Al doilea argument este identificatorul resursei care trebuie importată. De exemplu, pentru ID-ul resursei aws_iam_user, acesta este numele utilizatorului (de exemplu, yevgeniy.brikman), iar pentru ID-ul resursei aws_instance, va fi identificatorul serverului EC2 (de exemplu, i-190e22e5). Modul de importare a resursei este, de obicei, specificat în documentația de la baza paginii acesteia.
Mai jos este prezentată comanda import care permite sincronizarea resursei aws_iam_user, pe care ați adăugat-o în configurarea dvs. Terraform împreună cu utilizatorul IAM din capitolul 2 (bineînțeles, în loc de yevgeniy.brikman trebuie să introduceți numele dvs.):
$ terraform import aws_iam_user.existing_user yevgeniy.brikmanTerraform va apela API-ul AWS pentru a găsi utilizatorul dvs. IAM și a crea în fișierul de stări o legătură între acesta și resursa aws_iam_user.existing_user din configurația dvs. Terraform. De acum înainte, când veți executa comanda plan, Terraform va ști că utilizatorul IAM există deja și nu va încerca să-l creeze din nou.
Se cuvine să menționăm că, dacă aveți deja multe resurse pe care doriți să le importați în Terraform, scrierea manuală a codului și importul fiecăreia dintre ele pe rând poate fi o activitate costisitoare. De aceea, merită să luați în considerare un instrument precum Terraforming (http://terraforming.dtan4.net/), care poate importa automat codul și starea din contul AWS.
Refactorizarea poate avea capcanele sale
Refactorizare — o practică obișnuită în programare, când schimbați structura internă a codului, păstrând comportamentul extern neschimbat. Acest lucru este necesar pentru a face codul mai clar, ordonat și ușor de întreținut. Refactorizarea este o metodă esențială care ar trebui aplicată în mod regulat. Totuși, atunci când vine vorba de Terraform sau orice alt instrument IaC, trebuie să aveți mare grijă la ceea ce se înțelege prin „comportamentul extern” al unei secțiuni de cod, altfel pot apărea probleme neprevăzute.
De exemplu, un tip comun de refactorizare este înlocuirea numelui variabilelor sau funcțiilor cu unele mai clare. Multe IDE-uri au suport integrat pentru refactorizare și pot redenumi automat variabilele și funcțiile în întregul proiect. În limbajele de programare de uz general, aceasta este o procedură trivială, la care nu este necesar să te gândești, însă în Terraform, ar trebui să fii extrem de atent, altfel te poți confrunta cu întreruperi de funcționare.
De exemplu, modulul webserver-cluster are o variabilă de intrare numită cluster_name:
variable "cluster_name" { description = "Numele folosit pentru toate resursele clusterului" type = string }Imaginează-ți că ai început să folosești acest modul pentru a desfășura un microserviciu numit foo. Ulterior, ai dorit să îți schimbi numele serviciului în bar. Această schimbare poate părea trivială, dar în realitate poate cauza întreruperi de funcționare.
Problema este că modulul webserver-cluster utilizează variabila cluster_name în mai multe resurse, inclusiv parametrul name al celor două grupuri de securitate ș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] }Dacă schimbi parametrul name într-o resursă, Terraform va șterge versiunea veche a acestei resurse și va crea una nouă în locul ei. Dar dacă această resursă este ALB, în perioada dintre ștergerea sa și încărcarea unei noi versiuni, nu vei avea un mecanism pentru a redirecționa traficul către serverul tău web. La fel, dacă se șterge un grup de securitate, serverele tale vor începe să respingă orice trafic de rețea până când va fi creat un nou grup.
Un alt tip de refactorizare care ar putea să te intereseze este schimbarea identificatorului Terraform. Să luăm ca exemplu resursa aws_security_group din modulul webserver-cluster:
resource "aws_security_group" "instance" { # (...) }Identificatorul acestei resurse se numește instance. Imaginează-ți că în timpul refactorizării ai decis să-l schimbi cu un nume mai clar (în opinia ta) cluster_instance:
resource "aws_security_group" "cluster_instance" { # (...) }Ce se va întâmpla în final? Corect: întreruperi de funcționare.
Terraform leagă ID-ul fiecărui resursă cu identificatorul furnizorului de cloud. De exemplu, iam_user este legat de ID-ul utilizatorului IAM în AWS, iar aws_instance este asociat cu ID-ul serverului AWS EC2. Dacă schimbați identificatorul resursei (de exemplu, de la instance la cluster_instance, ca în cazul aws_security_group), pentru Terraform aceasta va părea ca și cum ați șters vechea resursă și ați adăugat una nouă. Dacă aplicați aceste modificări, Terraform va șterge vechea grupare de securitate și va crea una nouă, iar între timp serverele dumneavoastră vor începe să respingă orice trafic de rețea.
Iată patru lecții principale pe care ar trebui să le extrageți din această discuție.
- Folosiți întotdeauna comanda plan. Aceasta vă poate ajuta să identificați toate aceste probleme. Examinați cu atenție ieșirea sa și fiți atenți la situațiile în care Terraform planifică ștergerea resurselor care, cel mai probabil, nu ar trebui șterse.
- Creați înainte de a șterge. Dacă doriți să înlocuiți o resursă, gândiți-vă bine dacă trebuie să creați o înlocuire înainte de a șterge originalul. Dacă răspunsul este afirmativ, comanda create_before_destroy vă poate ajuta. Același rezultat poate fi obținut manual, efectuând doi pași: mai întâi adăugați noua resursă în configurare și rulați comanda apply, apoi eliminați vechea resursă din configurare și folosiți din nou comanda apply.
- Schimbarea identificatorilor necesită modificarea stării. Dacă doriți să schimbați identificatorul asociat resursei (de exemplu, să redenumiți aws_security_group de la instance la cluster_instance) fără a șterge resursa și a crea o nouă versiune a acesteia, trebuie să actualizați corespunzător fișierul de stare Terraform. Nu faceți acest lucru manual – folosiți în schimb comanda terraform state. Atunci când redenumiți identificatori, trebuie să executați comanda terraform state mv cu următoarea sintaxă:
terraform state mvORIGINAL_REFERENCE este o expresie care se referă la resursă în forma sa curentă, iar NEW_REFERENCE este locul în care doriți să o mutați. De exemplu, atunci când redenumiți grupul aws_security_group de la instance la cluster_instance, trebuie să executați următoarea comandă:
$ terraform state mv aws_security_group.instance aws_security_group.cluster_instanceAșadar, îi veți spune Terraform-ului că starea care se referea anterior la aws_security_group.instance trebuie acum să fie asociată cu aws_security_group.cluster_instance. Dacă, după redenumire și executarea acestei comenzi, terraform plan nu arată nicio modificare, înseamnă că ați făcut totul corect.
- Unele parametrări nu pot fi modificate. Parametrii multor resurse sunt immutabili. Dacă încercați să îi schimbați, Terraform va șterge resursa veche și va crea una nouă în locul ei. De obicei, pe pagina fiecărei resurse este specificat ce se întâmplă atunci când schimbați un anumit parametru, așa că nu uitați să consultați documentația. Folosiți întotdeauna comanda plan și analizați oportunitatea aplicării strategiei create_before_destroy.
Consistența întârziată se aliniază... cu întârzierea
API-urile unor furnizori de cloud, cum ar fi AWS, sunt asincrone și au o consistență întârziată. Asincronitatea înseamnă că interfața poate returna imediat un răspuns, fără a aștepta finalizarea acțiunii solicitate. Consistența întârziată înseamnă că poate dura timp pentru a răspândi modificările în întreaga sistem; în acest timp, răspunsurile dumneavoastră pot fi inconsistente și pot depinde de replica sursă a datelor care răspunde la cererile API.
Imaginează-ți, de exemplu, că faci un apel API la AWS pentru a crea un server EC2. API-ul va returna un răspuns "de succes" (201 Created) aproape instantaneu, fără a aștepta crearea efectivă a serverului. Dacă încerci imediat să te conectezi la el, aproape cu siguranță nu va reuși, deoarece în acel moment AWS încă inițializează resursele sau, ca variantă, serverul nu a pornit încă. Mai mult, dacă faci un alt apel pentru a obține informații despre acest server, este posibil să primești o eroare (404 Not Found). Ceea ce se întâmplă este că informațiile despre acest server EC2 încă pot fi răspândite prin AWS, iar pentru a fi disponibile în întreaga parte, va trebui să aștepți câteva secunde.
De fiecare dată când folosești un API asincron cu consistență întârziată, trebuie să repeți periodic cererea ta, până când acțiunea este finalizată și s-a răspândit în sistem. Din păcate, AWS SDK nu oferă instrumente bune pentru asta, iar proiectul Terraform a suferit anterior de multe erori precum 6813 (https://github.com/hashicorp/terraform/issues/6813):
$ terraform apply aws_subnet.private-persistence.2: InvalidSubnetID.NotFound: Subnet ID 'subnet-xxxxxxx' nu existăCu alte cuvinte, creați un resurs (de exemplu, un subrețea) și apoi încercați să obțineți informații despre acesta (precum ID-ul subrețelei tocmai create), iar Terraform nu poate să le găsească. Majoritatea acestor erori (inclusiv 6813) au fost deja corectate, dar din când în când ele mai apar, mai ales atunci când Terraform adaugă suport pentru un nou tip de resursă. Este frustrant, dar în majoritatea cazurilor nu aduce prejudicii. La repetarea comenzii terraform apply, totul ar trebui să funcționeze, deoarece între timp informațiile s-au răspândit în sistem.
Acest fragment este extras din cartea lui Evgheni Brikman .
Sursa: habr.com
