La oss se på noen fallgruver, inkludert de som er relatert til sykler, if-setninger og distribusjonsmetoder, samt mer generelle problemer som angår Terraform som helhet:
- parameterne count og for_each har begrensninger;
- begrensninger ved distribusjoner med null nedetid;
- selv en god plan kan vise seg å være mislykket;
- refaktorering kan ha sine skjulte fallgruver;
- utsettelse av konsistens er i samsvar… med forsinkelse.
Parameterne count og for_each har begrensninger
I eksemplene i dette kapitlet brukes parameteren count og uttrykket for_each aktivt i sykler og betinget logikk. De fungerer godt, men de har to viktige begrensninger som det er nødvendig å være klar over.
- Det er ikke mulig å referere til noen utgående variabler i ressursen i count og for_each.
- count og for_each kan ikke brukes i modulkonfigurasjonen.
Det er ikke mulig å referere til noen utgående variabler i ressursen i count og for_each.
Tenk deg at du trenger å distribuere flere EC2-servere, og av en eller annen grunn ønsker du ikke å bruke ASG. Koden din kan se slik ut:
resource "aws_instance" "example_1" {
count = 3
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t2.micro"
}
La oss se på dem én om gangen.
Siden parameteren count er tildelt en statisk verdi, vil denne koden fungere uten problemer: når du kjører apply-kommandoen, vil den opprette tre EC2-servere. Men hva om du ønsker å distribuere én server i hver tilgjengelighetssone (Availability Zone eller AZ) innenfor den nåværende AWS-regionen? Du kan gjøre det slik at koden din laster inn listen over soner fra datakilden aws_availability_zones, og deretter 'syklisk' går gjennom hver av dem og oppretter en EC2-server der ved å bruke parameteren count og tilgang til arrayet via indeksen:
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" {}Denne koden vil også fungere utmerket, siden parameteren count kan referere til datakilder uten problemer. Men hva skjer hvis antallet servere du trenger å opprette, avhenger av utdataene fra en viss ressurs? For å demonstrere dette, er det enklest å ta ressursen random_integer, som, som navnet antyder, returnerer et tilfeldig heltall:
resource "random_integer" "num_instances" {
min = 1
max = 3
}Denne koden genererer et tilfeldig tall fra 1 til 3. La oss se hva som skjer hvis vi prøver å bruke resultatet fra ressursen i parameteret count for aws_instance-ressursen:
resource "aws_instance" "example_3" {
count = random_integer.num_instances.result
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t2.micro"
}Hvis vi kjører terraform plan for denne koden, vil vi få følgende feil:
Feil: Ugyldig count-argument
på main.tf linje 30, i ressursen "aws_instance" "example_3":
30: count = random_integer.num_instances.result
"count"-verdien avhenger av ressursattributter som ikke kan bestemmes før apply, så Terraform kan ikke forutsi hvor mange instanser som vil bli opprettet. For å omgå dette, bruk -target-argumentet for først å bruke bare ressursene som count avhenger av.Terraform krever at count og for_each beregnes i planleggingsfasen, før noen ressurser opprettes eller endres. Dette betyr at count og for_each kan referere til litteraler, variabler, datakilder, og til og med ressurser, så lenge lengden kan bestemmes under planlegging, men ikke til beregnede utgangsvariabler for ressursen.
count og for_each kan ikke brukes i modulkonfigurasjon
En gang kan du bli fristet til å legge til count-parameteren i modulkonfigurasjonen:
module "count_example" {
source = "..\/..\/..\/..\/modules\/services\/webserver-cluster"
count = 3
cluster_name = "terraform-up-and-running-example"
server_port = 8080
instance_type = "t2.micro"
}Denne koden prøver å bruke count inne i modulen for å lage tre kopier av ressursen webserver-cluster. Eller kanskje ønsker du å gjøre modultilkoblingen valgfri avhengig av en boolsk betingelse, ved å gi count-parameteret verdien 0. Slik kode kan se fornuftig ut, men hvis du kjører terraform plan, vil du få følgende feil:
Feil: Reservert argumentnavn i modulblokken
på main.tf linje 13, i modul "count_example":
13: count = 3
Navnet "count" er reservert for bruk i en fremtidig versjon av Terraform.Dessverre, ved lanseringen av Terraform 0.12.6, støttes ikke bruken av count eller for_each i modulressursen. Ifølge utgivelsesnotatene for Terraform 0.12 (http://bit.ly/3257bv4) planlegger HashiCorp å legge til denne muligheten i fremtiden, så avhengig av når du leser denne boken, kan den allerede være tilgjengelig. For å finne ut sikkert, .
Begrensninger for distribusjoner med null nedetid
Bruken av create_before_destroy-blokken sammen med ASG er en glimrende løsning for organisering av distribusjoner med null nedetid, med ett unntak: automatiseringsreglene støttes ikke. Eller for å være mer presis, dette tilbakestiller ASG-størrelsen tilbake til min_størrelse ved hver distribusjon, noe som kan bli problematisk hvis du har brukt autoskalering regler for å øke antallet aktive servere.
For eksempel inneholder webserver-cluster-modulen et par aws_autoscaling_schedule-ressurser som ved kl. 9 øker antallet servere i klyngen fra to til ti. Hvis distribusjonen skjer, si, kl. 11, vil den nye ASG-gruppen ikke lastes med ti, men bare med to servere, og forbli slik til kl. 9 neste dag.
Denne begrensningen kan omgås på flere måter.
- Endre recurrence-parameteren i aws_autoscaling_schedule fra 0 9 * * * ('kjøre kl. 9') til noe som 0-59 9-17 * * * ('kjøre hvert minutt fra kl. 9 til kl. 17'). Hvis ASG allerede har ti servere, vil en ny kjøring av denne autoskalering regelen ikke endre noe, noe som er det vi ønsker. Men hvis ASG-gruppen nettopp er distribuert, vil denne regelen sikre at antallet servere når ti på maks ett minutt. Dette er ikke akkurat en elegant tilnærming, og store svingninger fra ti til to servere og tilbake kan også skape problemer for brukerne.
- Lag et egendefinert skript som bruker AWS API for å bestemme antallet aktive servere i ASG, kall det fra en ekstern datakilde (se 'Ekstern datakilde' på s. 249) og tilordne desired_capacity-parameteren til gruppe ASG verdien som skriptet returnerer. Dermed vil hver ny ASG-forekomst alltid starte med samme kapasitet, noe som kompliserer vedlikeholdet av vår Terraform-kode.
Selvfølgelig, ideelt sett burde Terraform ha innebygd støtte for distribusjoner med null nedetid, men pr. mai 2019 planla ikke HashiCorp-teamet å legge til denne funksjonaliteten ().
Den korrekte planen kan bli mislykket implementert.
Noen ganger når man kjører kommandoen plan, får man en fullstendig korrekt distribusjonsplan, men kommandoen apply returnerer en feil. Prøv for eksempel å legge til ressursen aws_iam_user med det samme navnet som du brukte for IAM-brukeren du opprettet tidligere i kapittel 2:
resource "aws_iam_user" "existing_user" {
# Sett inn navnet til den allerede eksisterende IAM-brukeren her,
# for å øve på bruken av kommandoen terraform import
name = "yevgeniy.brikman"
}Nå, hvis du kjører kommandoen plan, vil Terraform vise en tilsynelatende fornuftig distribusjonsplan:
Terraform vil utføre følgende handlinger:
# aws_iam_user.existing_user vil bli opprettet
+ resource "aws_iam_user" "existing_user" {
+ arn = (kjent etter apply)
+ force_destroy = false
+ id = (kjent etter apply)
+ name = "yevgeniy.brikman"
+ path = "\/"
+ unique_id = (kjent etter apply)
}
Plan: 1 å legge til, 0 å endre, 0 å slette.Hvis du kjører kommandoen apply, får du følgende feil:
Error: Feil oppretting av IAM-bruker yevgeniy.brikman: EntityAlreadyExists:
Bruker med navnet yevgeniy.brikman eksisterer allerede.
på main.tf linje 10, i resource "aws_iam_user" "existing_user":
10: resource "aws_iam_user" "existing_user" {Problemet er selvfølgelig at IAM-brukeren med dette navnet allerede eksisterer. Og dette kan skje ikke bare med IAM-brukere, men nesten med enhver ressurs. Det kan hende at noen har opprettet denne ressursen manuelt eller med hjelp av kommandolinjen, men uansett fører samsvar mellom identifikatorer til konflikter. Denne feilen har mange varianter som ofte overrasker nybegynnere i Terraform.
Det sentrale punktet er at kommandoen terraform plan kun tar hensyn til de ressursene som er angitt i Terraform-statusfilen. Hvis ressursene er opprettet på en annen måte (for eksempel manuelt, ved å klikke i AWS-konsollen), vil de ikke bli inkludert i statusfilen, og derfor vil ikke Terraform ta dem med i beregningen når kommandoen plan kjøres. Resultatet vil være at en tilsynelatende korrekt plan mislykkes.
Av dette kan det trekkes to lærdommer.
- Hvis du allerede har begynt å jobbe med Terraform, bruk ikke noe annet. Hvis en del av infrastrukturen din administreres med Terraform, kan den ikke lenger endres manuelt. Ellers risikerer du ikke bare å få merkelige feil fra Terraform, men du undergraver også mange av fordelene med IaC, siden koden ikke lenger vil være en nøyaktig representasjon av infrastrukturen din.
- Dersom du allerede har en infrastruktur, bruk import-kommandoen. Dersom du begynner å bruke Terraform med en eksisterende infrastruktur, kan den legges til i tilstandsfilen ved hjelp av terraform import-kommandoen. Da vil Terraform vite hvilken infrastruktur som skal administreres. Import-kommandoen tar to argumenter. Det første er adressen til ressursen i konfigurasjonsfilene dine. Her er syntaksen den samme som for henvisninger til ressurser: _. (som aws_iam_user.existing_user). Det andre argumentet er identifikatoren for ressursen som skal importeres. La oss si at som ID for ressursen aws_iam_user brukes brukernavnet (for eksempel yevgeniy.brikman), mens ID for ressursen aws_instance vil være identifikatoren for EC2-serveren (for eksempel i-190e22e5). Hvordan du importerer ressursen blir vanligvis spesifisert i dokumentasjonen nederst på siden.
Nedenfor vises import-kommandoen som lar deg synkronisere ressursen aws_iam_user, som du la til i Terraform-konfigurasjonen din sammen med IAM-brukeren i kapittel 2 (selvfølgelig må du erstatte yevgeniy.brikman med ditt eget navn):
$ terraform import aws_iam_user.existing_user yevgeniy.brikmanTerraform vil kontakte AWS API-et for å finne IAM-brukeren din og opprette en forbindelse i tilstandsfilen mellom den og ressursen aws_iam_user.existing_user i Terraform-konfigurasjonen din. Fra dette punktet av, når plan-kommandoen kjøres, vil Terraform vite at IAM-brukeren allerede eksisterer, og vil ikke forsøke å opprette den på nytt.
Det er verdt å merke seg at hvis du allerede har mange ressurser som du ønsker å importere til Terraform, kan det være tidkrevende å skrive koden for dem manuelt og importere dem én etter én. Derfor kan det være lurt å se på et verktøy som Terraforming (http://terraforming.dtan4.net/), som kan importere kode og tilstand automatisk fra AWS-kontoen din.
Refaktorisering kan ha sine fallgruver
Refaktorisering er en vanlig praksis innen programmering, der du endrer den interne strukturen i koden mens du lar atferden utad forbli uendret. Dette gjøres for å gjøre koden mer forståelig, ryddig og enklere å vedlikeholde. Refaktorisering er en uunngåelig teknikk som bør brukes jevnlig. Men når det gjelder Terraform eller noen annen IaC-løsning, er det svært viktig å være forsiktig med hva som menes med "utvending atferd" for et kodeområde, ellers kan det oppstå uforutsette problemer.
For example, a common type of refactoring is replacing variable or function names with more understandable ones. Many IDEs have built-in refactoring support and can automatically rename variables and functions throughout the entire project. In general-purpose programming languages, this is a trivial procedure that one can overlook; however, with Terraform, caution is essential, or you may encounter operational disruptions.
For instance, the webserver-cluster module has an input variable called cluster_name:
variable "cluster_name" { description = "The name to use for all the cluster resources" type = string }Imagine you started using this module to deploy a microservice named foo. Later, you wanted to rename your service to bar. This change may seem trivial, but in reality, it could lead to operational disruptions.
The point is that the webserver-cluster module uses the cluster_name variable in a number of resources, including the name parameter of two security groups and the 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] }If you change the name parameter in any resource, Terraform will delete the old version of that resource and create a new one in its place. But if that resource is an ALB, during the period between its deletion and the loading of the new version, there will be no mechanism for redirecting traffic to your web server. Similarly, if a security group is deleted, your servers will start rejecting any network traffic until a new group is created.
Another type of refactoring that may interest you is changing the Terraform identifier. Let's take the aws_security_group resource in the webserver-cluster module as an example:
resource "aws_security_group" "instance" { # (...) }The identifier of this resource is called instance. Imagine that during refactoring, you decided to change it to a more understandable name, in your opinion, cluster_instance:
resource "aws_security_group" "cluster_instance" { # (...) }What will happen in the end? Correct: an operational disruption.
Terraform knytter ID-en til hver ressurs med ID-en til skyleverandøren. For eksempel knyttes iam_user til IAM-brukerens ID i AWS, mens aws_instance knyttes til ID-en til AWS EC2-serveren. Hvis du endrer ressurs-ID-en (for eksempel fra instance til cluster_instance, som i tilfelle med aws_security_group), vil det for Terraform se ut som om du har slettet den gamle ressursen og lagt til en ny. Hvis du anvender disse endringene, vil Terraform slette den gamle sikkerhetsgruppen og opprette en annen, mens serverne dine begynner å avvise all nettverkstrafikk.
Her er fire viktige lærdommer du bør ta med deg fra denne diskusjonen.
- Bruk alltid plan-kommandoen. Den kan avdekke alle disse problemene. Gå grundig gjennom utdataene og vær oppmerksom på situasjoner der Terraform planlegger å fjerne ressurser som sannsynligvis ikke bør fjernes.
- Opprett før du sletter. Hvis du ønsker å erstatte en ressurs, vurder nøye om det er nødvendig å opprette en erstatning før den originale slettes. Hvis svaret er ja, kan create_before_destroy være til hjelp. Det samme resultatet kan oppnås manuelt ved å utføre to trinn: først legge til den nye ressursen i konfigurasjonen og kjøre apply-kommandoen, deretter fjerne den gamle ressursen fra konfigurasjonen og kjøre apply en gang til.
- Endring av ID-er krever endring av tilstanden. Hvis du ønsker å endre ID-en knyttet til en ressurs (for eksempel å omdøpe aws_security_group fra instance til cluster_instance) uten å slette ressursen og opprette en ny versjon, må du oppdatere Terraform-tilstandsfilen tilsvarende. Gjør aldri dette manuelt — bruk isteden terraform state-kommandoen. Når du omdøper ID-er, bør du bruke terraform state mv-kommandoen, som har følgende syntaks:
terraform state mvORIGINAL_REFERENCE er et uttrykk som refererer til ressursen i dens nåværende form, mens NEW_REFERENCE er stedet hvor du ønsker å flytte den. For eksempel, når du omdøper aws_security_group fra instance til cluster_instance, må du kjøre følgende kommando:
$ terraform state mv aws_security_group.instance aws_security_group.cluster_instanceDermed informerer du Terraform om, at den tilstand, der tidligere var knyttet til aws_security_group.instance, nu skal forbindes med aws_security_group.cluster_instance. Hvis terraform plan ikke viser nogen ændringer efter omdøbningen og kørsel af denne kommando, har du gjort det rigtigt.
- Nogle parametre kan ikke ændres. Parametrene for mange ressourcer er uforanderlige. Hvis du forsøger at ændre dem, sletter Terraform den gamle ressource og opretter en ny i stedet. På siden for hver ressource angives det normalt, hvad der sker ved ændring af en given parameter, så husk altid at tjekke dokumentationen. Brug altid kommandoen plan og overvej hensigtsmæssigheden af strategien create_before_destroy.
Forsinket konsistens stemmer overens... med forsinkelse
API'er fra nogle cloud-udbydere, som f.eks. AWS, er asynkrone og har forsinket konsistens. Asynkronitet betyder, at grænsefladen straks kan returnere et svar uden at vente på, at den anmodede handling er fuldført. Forsinket konsistens betyder, at det kan tage tid at sprede ændringerne over hele systemet; i mellemtiden kan dine svar være inkonsistente og afhænge af, hvilken replika af datakilden der svarer på dine API-opkald.
Forestil dig f.eks., at du laver et API-opkald til AWS med anmodning om at oprette en EC2-server. API'et vil næsten øjeblikkeligt returnere et 'success' svar (201 Created) uden at vente på, at selve serveren faktisk er oprettet. Hvis du straks forsøger at oprette forbindelse, vil det næppe lykkes, da AWS stadig initialiserer ressourcerne, eller serveren muligvis ikke er startet op endnu. Desuden, hvis du laver et andet opkald for at få information om denne server, kan der komme en fejl (404 Not Found). Problemet er, at oplysningerne om denne EC2-server stadig kan være i færd med at blive spredt over AWS, og det kan tage et par sekunder, før de er tilgængelige overalt.
Ved hver brug af et asynkront API med forsinket konsistens skal du periodisk gentage din anmodning, indtil handlingen er fuldført, og ændringerne er spredt over systemet. Desværre tilbyder AWS SDK ikke gode værktøjer til dette, og Terraform-projektet har tidligere lidt af mange fejl som 6813 (https://github.com/hashicorp/terraform/issues/6813):
$ terraform apply aws_subnet.private-persistence.2: InvalidSubnetID.NotFound: Subnet-ID'en 'subnet-xxxxxxx' eksisterer ikkeMed andre ord, oppretter du en ressurs (som et subnett) og prøver deretter å hente informasjon om den (som ID-en til det nyopprettede subnettet), men Terraform kan ikke finne det. De fleste av disse feilene (inkludert 6813) er allerede blitt fikset, men de dukker fortsatt opp fra tid til annen, spesielt når Terraform legger til støtte for en ny type ressurs. Det er irriterende, men i de fleste tilfeller er det ikke skadelig. Når du kjører terraform apply igjen, skal alt fungere, da denne informasjonen vil ha blitt distribuert i systemet.
Dette utdraget er hentet fra boken til Evgeny Brikman .
Kilde: habr.com
