Le të theksojmë disa nga kapriçot, duke përfshirë ato që lidhen me ciklet, shprehjet if dhe metodat e shpërndarjes, si dhe me probleme më të përgjithshme që i përkasin Terraform-it në përgjithësi:
- parametrat count dhe for_each kanë kufizime;
- kufizimet e shpërndarjeve pa pushime në kohë;
- edhe një plan i mirë mund të rezultojë i dështuar;
- refaktorizimi mund të ketë kapriço të tijat;
- konformiteti i vonuar përputhet… me vonesën.
Parametrat count dhe for_each kanë kufizime
Në shembujt e këtij kapitulli, parametri count dhe shprehja for_each përdoren aktivisht në cikle dhe logjikën kushtore. Ato janë efikase, por kanë dy kufizime të rëndësishme që duhet të njihni.
- Në count dhe for_each nuk mund të referoheni në asnjë variabël dalëse të burimit.
- count dhe for_each nuk mund të përdoren në konfigurimin e modulit.
Në count dhe for_each nuk mund të referoheni në asnjë variabël dalëse të burimit
Imagjinoni se duhet të shpërndani disa serverë EC2 dhe për një arsye nuk dëshironi të përdorni ASG. Kodi juaj mund të jetë kështu:
resource "aws_instance" "example_1" {
count = 3
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t2.micro"
}
Të shqyrtojmë ato një nga një.
Duke qenë se parametri count ka një vlerë statike, ky kod do të funksionojë pa probleme: kur të ekzekutoni komandën apply, ai do të krijojë tre servera EC2. Por nëse dëshironi të vendosni nga një server në çdo zonë disponueshmërie (Availability Zone ose AZ) brenda rajonit aktual të AWS? Mund ta bëni në mënyrë që kodi juaj të ngarkojë listën e zonave nga burimi i dhënash aws_availability_zones dhe pastaj të kalojë "ciklisht" për secilën nga to dhe të krijojë aty një server EC2, duke përdorur parametrin count dhe aksesin në array sipas indeksit:
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" {}Ky kod gjithashtu do të funksionojë shkëlqyeshëm, pasi parametri count mund të referohet pa probleme në burimet e dhënave. Por çfarë do të ndodhë nëse numri i serverëve që duhet të krijoni varet nga dalja e ndonjë burimi? Për ta demonstruar këtë, më së lehti është të marrim burimin random_integer, i cili, siç mund të kuptohet nga emri, kthen një numër të rastësishëm të plotë:
resource "random_integer" "num_instances" {
min = 1
max = 3
}Ky ky prezanton një numër të rastësishëm nga 1 deri në 3. Le të shohim se çfarë do të ndodhë nëse përpiqemi të përdorim daljen e rezultatit të këtij burimi në parametrin count të burimit aws_instance:
resource "aws_instance" "example_3" {
count = random_integer.num_instances.result
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t2.micro"
}Nëse ekzekutoni terraform plan për këtë kod, do të merrni gabimin e mëposhtëm:
Gabim: Argument i pavlefshëm count
në main.tf rreshti 30, në burimin "aws_instance" "example_3":
30: count = random_integer.num_instances.result
Vlera "count" varen nga atributet e burimeve që nuk mund të përcaktohen deri në aplikim, kështu që Terraform nuk mund të parashikojë se sa instance do të krijohen. Për të kaluar këtë, përdorni argumentin -target për të aplikuar fillimisht vetëm burimet që count varet prej tyre.Terraform kërkon që count dhe for_each të llogariten në fazën e planifikimit, para krijimit ose ndryshimit të ndonjë burimi. Kjo do të thotë se count dhe for_each mund të referohen në literale, variabla, burime të dhënash dhe madje lista burimesh (nëse gjatësia e tyre mund të përcaktohet gjatë planifikimit), por jo në variabla të llogaritura të daljes së burimit.
count dhe for_each nuk mund të përdoren në konfigurimin e modulit
Njëherë e një kohë, mund t'ju vijë në mendje të shtoni parametrin count në konfigurimet e modulit:
moduli "count_example" {
source = "../../../../modules/services/webserver-cluster"
count = 3
cluster_name = "terraform-up-and-running-example"
server_port = 8080
instance_type = "t2.micro"
}Ky kyç përpiqet të përdorë count brenda modulit për të krijuar tri kopje të burimit webserver-cluster. Ose ndoshta ju dëshironi të bëni lidhjen me modulin opsionale në varësi të ndonjë qëllimi boolean, duke i dhënë parametrin count vlerën 0. Ky kod do të dukej krejtësisht i arsyeshëm, megjithatë si rezultat i ekzekutimit të terraform plan do të merrni një gabim të tillë:
Gabim: Emri i argumentit të rezervuar në bllokun e modulit
në main.tf rreshti 13, në modulin "count_example":
13: count = 3
Emri "count" është i rezervuar për përdorim në një version të ardhshëm të Terraform.Më vjen keq, në momentin e lëshimit të Terraform 0.12.6 përdorimi i count ose for_each në burimin e modulit nuk mbështetet. Sipas shënimeve të lëshimit të Terraform 0.12 (http://bit.ly/3257bv4) kompania HashiCorp planifikon të shtojë këtë mundësi në të ardhmen, prandaj, në varësi të kohës kur e lexoni këtë libër, ajo mund të jetë tashmë e disponueshme. Për të mësuar me siguri, .
Kufizimet e shpërndarjeve me kohë zero mosfunksionimi
Përdorimi i bllokut create_before_destroy në kombinim me ASG është një zgjidhje e shkëlqyer për organizimin e desplejimeve pa kosto ndalesash, përveç një nuancë: rregullat e automatik-zhvillimit nuk mbështeten në këtë rast. Ose, nëse të jemi më të saktë, kjo e rikthen madhësinë e ASG në min_size me çdo desplojim, gjë që mund të bëhet një problem nëse keni përdorur rregullat e automatik-zhvillimit për të rritur numrin e serverëve të nisur.
Për shembull, moduli webserver-cluster përmban një çift burimesh aws_autoscaling_schedule, të cilat në orën 9:00 rrisin numrin e serverëve në klaster nga dy në dhjetë. Nëse realizoni një desplojim, të themi, në orën 11:00, grupi i ri ASG do të ngarkohet jo me dhjetë, por vetëm me dy serverë dhe do të mbetet në këtë gjendje deri në orën 9:00 të ditës tjetër.
Ky kufizim mund të anashkalohet në disa mënyra.
- Ndrysho parametrin recurrence në aws_autoscaling_schedule nga 0 9 * * * («të niset në 9 të mëngjesit») në diçka si 0-59 9-17 * * * («të niset çdo minutë nga ora 9 e mëngjesit deri në 5 të pasdites»). Nëse në ASG tashmë ka dhjetë servera, ri-ekzekutimi i këtij rregulli të automatikëmastrimit nuk do të ndryshojë asgjë, gjë që na nevojitet. Por nëse grupi ASG është vendosur së fundmi, ky rregull garanton që maksimumi brenda një minute numri i serverëve të tij të arrijë dhjetë. Kjo nuk është ndonjë qasje shumë elegante, dhe skenarët e mëdhenj nga dhjetë në dy servera dhe përsëri mund të shkaktojnë probleme tek përdoruesit.
- Krijo një skript të personalizuar, i cili aplikon API-në e AWS për të përcaktuar numrin e serverëve aktivë në ASG, thirri atë me një burim të jashtëm të dhënash (shih pikën «Burimi i jashtëm i të dhënave» në fq. 249) dhe cakto parametrin desired_capacity të grupit ASG vlerën e kthyer nga ky skript. Kështu, çdo instancë e re ASG gjithmonë do të nisë me të njëjtën kapacitet si kodi ynë Terraform dhe e komplikon mirëmbajtjen e tij.
Sigurisht, në mënyrë ideale, Terraform duhet të ketë mbështetje të integruar për implementime me kohë zero ndalese, por deri në maj 2019, ekipi i HashiCorp nuk kishte në plan të shtonte këtë funksionalitet ().
Plani i saktë mund të mos zbatohet me sukses
Ndonjëherë, kur ekzekutoni komandën plan, merrni një plan implementimi mjaft të saktë, megjithatë, ekipi apply kthen një gabim. Provoni, për shembull, të shtoni burimin aws_iam_user me të njëjtin emër që keni përdorur për përdoruesin IAM që keni krijuar më parë në kapitullin 2:
resource "aws_iam_user" "existing_user" {
# Vendosni këtu emrin e përdoruesit ekzistues IAM,
# për të praktikuar përdorimin e komandës terraform import
name = "yevgeniy.brikman"
}Tani, nëse ekzekutoni komandën plan, Terraform do të japë një plan implementimi që duket krejtësisht i arsyeshëm:
Terraform do të realizojë veprime të mëposhtme:
# aws_iam_user.existing_user do të krijohet
+ resource "aws_iam_user" "existing_user" {
+ arn = (i njohur pas aplikimit)
+ force_destroy = false
+ id = (i njohur pas aplikimit)
+ name = "yevgeniy.brikman"
+ path = "/"
+ unique_id = (i njohur pas aplikimit)
}
Plani: 1 për të shtuar, 0 për të ndryshuar, 0 për të shkatërruar.Nëse ekzekutoni komandën apply, do të rezultojë me këtë gabim:
Gabim: Gabim gjatë krijimit të Përdoruesit IAM yevgeniy.brikman: EntityAlreadyExists:
Përdoruesi me emrin yevgeniy.brikman tashmë ekziston.
në main.tf rreshti 10, në burimin "aws_iam_user" "existing_user":
10: burimi "aws_iam_user" "existing_user" {Problemi, sigurisht, është se një përdorues IAM me këtë emër tashmë ekziston. Dhe kjo mund të ndodhë jo vetëm me përdoruesit IAM, por edhe me pothuajse çdo burim tjetër. Ndoshta dikush e ka krijuar këtë burim manualisht ose me anë të komandës, por çfarëdo qoftë, përputhja e identifikuesve çon në konflikte. Ky gabim ka shumë variacione që shpesh i përfundojnë papritur fillestarët në Terraform.
Pika kyçe është se komandat terraform plan marrin parasysh vetëm burimet që janë të specifikuara në skedarin e gjendjes së Terraform. Nëse burimet janë krijuar në ndonjë mënyrë tjetër (për shembull, manualisht, duke klikuar në konsolën AWS), ato nuk do të jenë në skedarin e gjendjes dhe, si rrjedhojë, Terraform nuk do t'i marrë parasysh ato gjatë ekzekutimit të komandës plan. Si rezultat, një plan që duket korrekt në pamje të parë do të dështojë.
Nga kjo mund të nxirren dy mësime.
- Nëse keni filluar të punoni me Terraform, mos përdorni asgjë tjetër. Nëse një pjesë e infrastrukturës tuaj menaxhohet me Terraform, nuk mund ta ndryshoni më manualisht. Në të kundërt, jo vetëm që rrezikoni të merrni gabime të çuditshme nga Terraform, por gjithashtu anashkaloni shumë përfitime të IaC, pasi kodi nuk do të jetë më një përfaqësim i saktë i infrastrukturës suaj.
- Nëse tashmë keni një infrastrukturë, përdorni komandën import. Nëse po filloni të përdorni Terraform me një infrastrukturë ekzistuese, mund ta shtoni atë në skedarin e gjendjes me komandën terraform import. Kështu, Terraform do të di se cila infrastrukturë duhet të menaxhohet. Komanda import merr dy argumente. I pari është adresa e burimit në skedarët tuaj të konfigurimit. Këtu është i njëjti sintaksë si në lidhjet me burimet: _. (si p.sh., aws_iam_user.existing_user). Argumenti i dytë është identifikuesi i burimit që duhet të importohet. Për shembull, si ID burimi, aws_iam_user është emri i përdoruesit (p.sh., yevgeniy.brikman), ndërsa ID burimi aws_instance do të jetë identifikuesi i serverit EC2 (si p.sh., i-190e22e5). Si të importoni një burim, zakonisht tregohet në dokumentacionin në fund të faqes së tij.
Më poshtë është paraqitur komanda import, e cila lejon sinkronizimin e burimit aws_iam_user, të cilin e keni shtuar në konfigurimin tuaj Terraform së bashku me përdoruesin IAM në kapitullin 2 (sigurisht, në vend të yevgeniy.brikman duhet të vendosni emrin tuaj):
$ terraform import aws_iam_user.existing_user yevgeniy.brikmanTerraform do tregon se duhet të lidhet me API-në e AWS për të gjetur përdoruesin tuaj IAM dhe për të krijuar një lidhje në skedarin e gjendjes ndërmjet tij dhe burimit aws_iam_user.existing_user në konfigurimin tuaj Terraform. Nga ky moment, kur të ekzekutoni komandën plan, Terraform do ta dijë se përdoruesi IAM ekziston tashmë dhe nuk do të përpiqet ta krijojë atë përsëri.
Duhet të theksohet se, nëse tashmë keni shumë burime që dëshironi të importoni në Terraform, shkruarja manuale e kodit dhe importimi i çdo prej tyre një nga një mund të jetë një detyrë e mundimshme. Prandaj, ia vlen të shqyrtoni një mjet si Terraforming (http://terraforming.dtan4.net/), i cili mund të importe automatikisht kod dhe gjendje nga llogaria juaj AWS.
Ristrukturimi mund të ketë sfidat e veta
Ristrukturimi — një praktikë e zakonshme në programim, ku ndryshoni strukturën interne të kodit, duke lënë pa ndryshim sjelljen e jashtme. Kjo është e nevojshme për ta bërë kodin më të qartë, të rregullt dhe të lehtë për t'u mbajtur. Refaktorizimi është një metodë e pazëvendësueshme që duhet aplikuar rregullisht. Por, kur flasim për Terraform ose çdo mjet tjetër IaC, duhet të jeni shumë të kujdesshëm me atë që nënkuptohet nga "sjellja e jashtme" e një pjesë të kodit, përndryshe mund të shkaktojë probleme të paparashikuara.
Për shembull, një formë e zakonshme e refaktorizimit është zëvendësimi i emrave të variablave ose funksioneve me emra më të qartë. Shumë IDE kanë mbështetje të integruar për refaktorizimin dhe mund të rrinë automatikisht emrat e variablave dhe funksioneve në të gjithë projektin. Në gjuhët e programimit të cilësisë së përgjithshme, kjo është një procedurë triviale për të cilën nuk mund të shqetësoheni, megjithatë në Terraform duhet të jeni shumë të kujdesshëm, përndryshe mund të hasni probleme gjatë funksionimit.
Për shembull, moduli webserver-cluster ka një variabël hyrjeje cluster_name:
variable "cluster_name" { description = "Emri që do të përdoret për të gjitha burimet e klasterit" type = string }Imagjinoni se keni filluar të përdorni këtë modul për të implementuar një mikroshërbim me emrin foo. Më vonë, ju keni dëshirë të ndryshoni emrin e shërbimit tuaj në bar. Ky ndryshim mund të duket trivialisht, por në realitet ai mund të shkaktojë ndërprerje në funksionim.
Ajo që ndodh është se moduli webserver-cluster përdor variablën cluster_name në një sërë burimesh, duke përfshirë parametrin name të dy grupeve të sigurisë dhe 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] }Nëse ndryshoni parametrin name në ndonjë burim, Terraform do të fshijë versionin e vjetër të atij burimi dhe do të krijojë një të ri në vend. Por nëse ky burim është ALB, gjatë periudhës ndërmjet fshirjes së tij dhe ngarkimit të versionit të ri, ju nuk do të keni një mekanizëm për të drejtuar trafikun në serverin tuaj web. Po ashtu, nëse një grup sigurie fshihet, serverët tuaj do të fillojnë të refuzojnë çdo trafik rrjeti derisa të krijohet një grup i ri.
Një tjetër lloj refaktorizimi që mund t'ju interesojë është ndryshimi i identifikuesit Terraform. Le të marrim si shembull burimin aws_security_group në modulin webserver-cluster:
resource "aws_security_group" "instance" { # (...) }Identifikuesi i këtij burimi quhet instance. Imagjinoni se gjatë refaktorizimit keni vendosur ta ndryshoni në një emër më të kuptueshëm (në mendimin tuaj) cluster_instance:
resource "aws_security_group" "cluster_instance" { # (...) }Çfarë do të ndodhte në fund? E drejtë: do të kishte një ndërprerje në shërbim.
Terraform lidh ID-në e çdo burimi me identifikuesin e ofruesit të cloud-it. Për shembull, iam_user lidhet me identifikuesin e përdoruesit IAM në AWS, ndërsa aws_instance—me ID-në e serverit AWS EC2. Nëse e ndryshoni identifikuesin e burimit (të themi, nga instance në cluster_instance, siç është rasti me aws_security_group), për Terraform do të duket sikur keni fshirë burimin e vjetër dhe keni shtuar një të ri. Nëse zbatoni këto ndryshime, Terraform do të fshijë grupin e vjetër të sigurisë dhe do të krijojë një tjetër, ndërkohë që serverët tuaj do të fillojnë të refuzojnë çdo trafik rrjeti.
Ja katër mësime kryesore që duhet të Nxirrni nga kjo bisedë.
- Gjithmonë përdorni komandën plan. Ajo mund të nxjerrë në pah të gjitha këto pengesa. Rishikoni me kujdes daljen e saj dhe kushtojeni vëmendje situatave kur Terraform planifikon të fshijë burime që, me siguri, nuk duhet fshirë.
- Krijoni para se të fshini. Nëse dëshironi të zëvendësoni një burim, mendojini mirë nëse duhet të krijoni një zëvendësim para se të fshini origjinalin. Nëse përgjigjja është pozitive, create_before_destroy mund të ndihmojë. E njëjta gjë mund të arrihet në mënyrë manuale duke kryer dy hapa: së pari, shtoni një burim të ri në konfigurim dhe ekzekutoni komandën apply, pastaj fshini burimin e vjetër nga konfigurimi dhe përdorni komandën apply përsëri.
- Ndryshimi i identifikuesve kërkon ndryshimin e gjendjes. Nëse dëshiron të ndryshosh identifikuesin e lidhur me burimin (p.sh., të ribesh emri i aws_security_group nga instance në cluster_instance), duke shmangur kështu fshirjen e burimit dhe krijimin e një versioni të ri, është e nevojshme të azhurnohet në mënyrë përkatëse skedari i gjendjes së Terraform. Kurrë mos e bëni këtë manualisht — përdorni në vend të kësaj komandën terraform state. Kur ribëni emrat e identifikuesve, duhet të ekzekutoni komandën terraform state mv, e cila ka këtë sintaksë:
terraform state mvORIGINAL_REFERENCE është një shprehje që referon burimin në formatin e tij aktual, ndërsa NEW_REFERENCE është vendi ku dëshiron ta transferosh. Për shembull, kur ribëni emrin e grupit aws_security_group nga instance në cluster_instance, duhet të ekzekutoni komandën e mëposhtme:
$ terraform state mv aws_security_group.instance aws_security_group.cluster_instanceKështu do t'i tregoni Terraform-it se gjendja që dikur i përkiste aws_security_group.instance tani duhet të lidhet me aws_security_group.cluster_instance. Nëse pas ribërjes dhe ekzekutimit të kësaj komande terraform plan nuk tregon ndonjë ndryshim, do të thotë se e keni bërë gjithçka siç duhet.
- Disa parametra nuk mund të ndryshohen. Parametrat e shumë burimeve janë të pandryshueshëm. Nëse përpiqeni të ndryshoni ato, Terraform do të fshijë burimin e vjetër dhe do të krijojë një të ri në vend të tij. Në faqen e çdo burimi zakonisht tregohet se çfarë ndodh kur ndryshohet një parametr i caktuar, prandaj mos harroni të konsultoheni me dokumentacionin. Gjithmonë përdorni komandën plan dhe shqyrtoni arsyeshmërinë e aplikimit të strategjisë create_before_destroy.
Konsistenca e vonuar konsiston... me vonesën
API-të e disa ofruesve të cloud, si AWS, janë asinkrone dhe kanë konsistencë të vonuar. Asinkronia do të thotë që interfata mund të kthejë një përgjigje menjëherë, pa pritur përfundimin e veprimit të kërkuar. Konsistenca e vonuar do të thotë se mund të nevojitet kohë për të shpërndarë ndryshimet në të gjithë sistemin; derisa kjo të ndodhë, përgjigjet tuaja mund të jenë të paqëndrueshme dhe të varen nga cila kopje e burimit të të dhënave përgjigjet në thirrjet tuaja API.
Imagjinoni, për shembull, se po bëni një thirrje API në AWS për të krijuar një server EC2. API do të kthejë një përgjigje "të suksesshme" (201 Created) pothuajse menjëherë, pa pritur krijimin e vetë serverit. Nëse përpiqeni të lidheni menjëherë pas kësaj, pothuajse me siguri nuk do të funksionojë, pasi në atë moment AWS ende po inicializon burimet ose, si opsion, serveri ende nuk është ngarkuar. Për më tepër, nëse bëni një thirrje tjetër për të marrë informacion mbi këtë server, mund të merrni një gabim (404 Not Found). Problemi është se informacionet për këtë server EC2 ende mund të shpërndahen në AWS dhe do të duhet të prisni disa sekonda që të bëhen të disponueshme kudo.
Me çdo përdorim të një API asinkrone me koherencë të vonuar, duhet të përsërisni kërkesën tuaj herë pas here derisa veprimi të përfundojë dhe të shpërndahet në sistem. Fatkeqësisht, AWS SDK nuk ofron asnjë mjet të mirë për këtë, dhe projekti Terraform më parë ka vuajtur nga shumë gabime si ajo 6813 (https://github.com/hashicorp/terraform/issues/6813):
$ terraform apply aws_subnet.private-persistence.2: InvalidSubnetID.NotFound: ID e subnet-it 'subnet-xxxxxxx' nuk ekzistonMe të tjera fjalë, ju krijoni një burim (p.sh., një subnet) dhe më pas përpiqeni të merrni disa të dhëna për të (p.sh., ID-në e subnet-it të sapokrijuar), por Terraform nuk mund t'i gjejë ato. Shumica e kësaj lloji të gabimeve (përfshirë 6813) janë tashmë rregulluar, por nganjëherë ato ende shfaqen, veçanërisht kur në Terraform shtohet mbështetje për një lloj të ri burimi. Kjo është irrituese, por në shumicën e rasteve nuk shkakton asnjë dëm. Kur të përshtatet përsëri terraform apply, gjithçka duhet të funksionojë, pasi deri në atë kohë informacioni do të jetë përhapur në sistem.
Ky fragment është marrë nga libri i Evgeny Brickman .
Burimi: habr.com
