Terraform fallgruver

Terraform fallgruver
La oss fremheve noen fallgruver, inkludert de som er relatert til loops, if-setninger og distribusjonsteknikker, samt mer generelle problemer som påvirker Terraform generelt:

  • count og for_each parametere har begrensninger;
  • begrense null nedetidsdistribusjoner;
  • selv en god plan kan mislykkes;
  • refactoring kan ha sine fallgruver;
  • utsatt koherens er konsistent... med utsettelse.

Antall og for_each parametere har begrensninger

Eksemplene i dette kapittelet bruker utstrakt bruk av telleparameteren og for_each-uttrykket i løkker og betinget logikk. De presterer bra, men de har to viktige begrensninger som du må være klar over.

  • Count og for_each kan ikke referere til noen ressursutdatavariabler.
  • count og for_each kan ikke brukes i modulkonfigurasjon.

count og for_each kan ikke referere til noen ressursutdatavariabler

Tenk deg at du må distribuere flere EC2-servere og av en eller annen grunn ikke vil bruke ASG. Koden din kan være slik:

resource "aws_instance" "example_1" {
   count             = 3
   ami                = "ami-0c55b159cbfafe1f0"
   instance_type = "t2.micro"
}

La oss se på dem en etter en.

Siden telleparameteren er satt til en statisk verdi, vil denne koden fungere uten problemer: når du kjører påfør kommandoen, vil den opprette tre EC2-servere. Men hva om du ønsket å distribuere én server i hver tilgjengelighetssone (AZ) innenfor din nåværende AWS-region? Du kan få koden din til å laste inn en liste over soner fra aws_availability_zones-datakilden og deretter gå gjennom hver enkelt sone og lage en EC2-server i den ved å bruke telleparameteren og array-indekstilgang:

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 bra, siden telleparameteren kan referere til datakilder uten problemer. Men hva skjer hvis antallet servere du må opprette, avhenger av utdataene fra en ressurs? For å demonstrere dette er den enkleste måten å bruke random_integer-ressursen, som, som navnet antyder, returnerer et tilfeldig heltall:

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

Denne koden genererer et tilfeldig tall mellom 1 og 3. La oss se hva som skjer hvis vi prøver å bruke utdataene fra denne ressursen i telleparameteren til aws_instance-ressursen:

resource "aws_instance" "example_3" {
   count             = random_integer.num_instances.result
   ami                = "ami-0c55b159cbfafe1f0"
   instance_type = "t2.micro"
}

Hvis du kjører terraform plan på denne koden, får du følgende feilmelding:

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 krever at telling og for_each beregnes under planleggingsfasen, før noen ressurser opprettes eller endres. Dette betyr at count og for_each kan referere til bokstaver, variabler, datakilder og til og med ressurslister (så lenge lengden kan bestemmes ved planleggingstidspunktet), men ikke til beregnede ressursutdatavariabler.

count og for_each kan ikke brukes i modulkonfigurasjon

En dag kan du bli fristet til å legge til en telleparameter til modulkonfigurasjonen din:

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 forsøker å bruke telling inne i en modul for å lage tre kopier av webserver-cluster-ressursen. Eller du vil kanskje gjøre tilkobling av en modul valgfri basert på en boolsk tilstand ved å sette dens telleparameter til 0. Dette kan se ut som rimelig kode, men du får denne feilmeldingen når du kjører 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.

Fra og med Terraform 0.12.6 støttes dessverre ikke bruk av count eller for_each i en modulressurs. I følge Terraform 0.12-utgivelsesnotatene (http://bit.ly/3257bv4), planlegger HashiCorp å legge til denne funksjonen i fremtiden, så avhengig av når du leser denne boken, kan den allerede være tilgjengelig. For å finne ut sikkert, les Terraforms endringslogg her.

Begrensninger ved null nedetidsimplementeringer

Å bruke create_before_destroy-blokken i kombinasjon med ASG er en flott løsning for å lage distribusjoner med null nedetid, bortsett fra ett forbehold: autoskaleringsregler støttes ikke. Eller for å være mer presis, tilbakestiller dette ASG-størrelsen til min_size på hver distribusjon, noe som kan være et problem hvis du brukte autoskaleringsregler for å øke antall servere som kjører.

For eksempel inneholder webserver-cluster-modulen et par aws_autoscaling_schedule-ressurser, som klokken 9 øker antallet servere i klyngen fra to til ti. Hvis du distribuerer for eksempel klokken 11, vil den nye ASG starte opp med bare to servere i stedet for ti og forbli slik til klokken 9 neste dag.

Denne begrensningen kan omgås på flere måter.

  • Endre gjentaksparameteren i aws_autoscaling_schedule fra 0 9 * * * ("kjør kl. 9") til noe sånt som 0-59 9-17 * * * ("kjør hvert minutt fra kl. 9 til 5"). Hvis ASG allerede har ti servere, vil det å kjøre denne autoskaleringsregelen på nytt ikke endre noe, og det er det vi ønsker. Men hvis ASG bare nylig har blitt distribuert, vil denne regelen sikre at antallet servere i løpet av maksimalt et minutt når ti. Dette er ikke en helt elegant tilnærming, og store hopp fra ti til to servere og tilbake kan også skape problemer for brukerne.
  • Opprett et tilpasset skript som bruker AWS API for å bestemme antall aktive servere i ASG, kall det ved å bruke en ekstern datakilde (se "Ekstern datakilde" på side 249), og sett ASGs ønsket_kapasitet-parameter til verdien returnert av manuset. På denne måten vil hver nye ASG-forekomst alltid kjøre med samme kapasitet som den eksisterende Terraform-koden og gjør den vanskeligere å vedlikeholde.

Selvfølgelig ville Terraform ideelt sett ha innebygd støtte for distribusjoner uten nedetid, men fra mai 2019 hadde HashiCorp-teamet ingen planer om å legge til denne funksjonaliteten (detaljer - her).

Riktig plan kan bli implementert uten hell

Noen ganger produserer plankommandoen en helt korrekt distribusjonsplan, men bruk kommandoen returnerer en feil. Prøv for eksempel å legge til aws_iam_user-ressursen med samme navn som du brukte for IAM-brukeren du opprettet tidligere i kapittel 2:

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

Nå, hvis du kjører plan-kommandoen, vil Terraform gi ut en tilsynelatende rimelig distribusjonsplan:

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.

Hvis du kjører application-kommandoen vil du få følgende feilmelding:

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" {

Problemet er selvfølgelig at en IAM-bruker med det navnet allerede eksisterer. Og dette kan ikke bare skje med IAM-brukere, men med nesten hvilken som helst ressurs. Det er mulig at noen har opprettet denne ressursen manuelt eller ved å bruke kommandolinjen, men uansett fører samsvarende IDer til konflikter. Det er mange varianter av denne feilen som ofte overrasker nykommere til Terraform.

Nøkkelpunktet er at kommandoen terraform plan kun tar hensyn til de ressursene som er spesifisert i Terraform tilstandsfilen. Hvis ressurser opprettes på en annen måte (for eksempel manuelt ved å klikke i AWS-konsollen), vil de ikke havne i tilstandsfilen og derfor vil ikke Terraform ta hensyn til dem når plankommandoen utføres. Som et resultat vil en plan som virker riktig ved første øyekast vise seg å være mislykket.

Det er to lærdommer å lære av dette.

  • Hvis du allerede har begynt å jobbe med Terraform, ikke bruk noe annet. Hvis en del av infrastrukturen din administreres ved hjelp av Terraform, kan du ikke lenger endre den manuelt. Ellers risikerer du ikke bare rare Terraform-feil, men du nekter også mange av fordelene med IaC siden koden ikke lenger vil være en nøyaktig representasjon av infrastrukturen din.
  • Hvis du allerede har noe infrastruktur, bruk importkommandoen. Hvis du begynner å bruke Terraform med eksisterende infrastruktur, kan du legge den til tilstandsfilen ved å bruke kommandoen terraform import. På denne måten vil Terraform vite hvilken infrastruktur som må administreres. Importkommandoen tar to argumenter. Den første er ressursadressen i konfigurasjonsfilene dine. Syntaksen her er den samme som for ressurslenker: _. (som aws_iam_user.existing_user). Det andre argumentet er ID-en til ressursen som skal importeres. La oss si at ressurs-ID-en aws_iam_user er brukernavnet (for eksempel yevgeniy.brikman), og ressurs-ID-en aws_instance er EC2-server-IDen (som i-190e22e5). Hvordan du importerer en ressurs er vanligvis angitt i dokumentasjonen nederst på siden.

    Nedenfor er en importkommando som synkroniserer aws_iam_user-ressursen som du la til Terraform-konfigurasjonen sammen med IAM-brukeren i kapittel 2 (selvfølgelig erstatter navnet ditt med yevgeniy.brikman):

    $ terraform import aws_iam_user.existing_user yevgeniy.brikman

    Terraform vil kalle opp AWS API for å finne IAM-brukeren din og opprette en tilstandsfiltilknytning mellom den og aws_iam_user.existing_user-ressursen i Terraform-konfigurasjonen. Fra nå av, når du kjører plan-kommandoen, vil Terraform vite at IAM-brukeren allerede eksisterer og vil ikke prøve å opprette den igjen.

    Det er verdt å merke seg at hvis du allerede har mange ressurser som du vil importere til Terraform, kan det å skrive koden manuelt og importere hver enkelt om gangen være et problem. Så det er verdt å se nærmere på et verktøy som Terraforming (http://terraforming.dtan4.net/), som automatisk kan importere kode og tilstand fra AWS-kontoen din.

    Refaktorering kan ha sine fallgruver

    Refaktorering er en vanlig praksis innen programmering hvor du endrer den interne strukturen til koden mens du lar den eksterne atferden være uendret. Dette for å gjøre koden klarere, ryddigere og enklere å vedlikeholde. Refaktorering er en uunnværlig teknikk som bør brukes regelmessig. Men når det kommer til Terraform eller et annet IaC-verktøy, må du være ekstremt forsiktig med hva du mener med den "eksterne oppførselen" til et stykke kode, ellers vil det oppstå uventede problemer.

    For eksempel er en vanlig type refactoring å erstatte navnene på variabler eller funksjoner med mer forståelige. Mange IDE-er har innebygd støtte for refactoring og kan automatisk gi nytt navn til variabler og funksjoner gjennom hele prosjektet. I generelle programmeringsspråk er dette en triviell prosedyre som du kanskje ikke tenker på, men i Terraform må du være ekstremt forsiktig med dette, ellers kan du oppleve strømbrudd.

    For eksempel har webserver-cluster-modulen en inngangsvariabel cluster_name:

    variable "cluster_name" {
       description = "The name to use for all the cluster resources"
       type          = string
    }

    Tenk deg at du begynte å bruke denne modulen til å distribuere en mikrotjeneste kalt foo. Senere vil du endre navn på tjenesten til bar. Denne endringen kan virke triviell, men i virkeligheten kan den forårsake tjenesteforstyrrelser.

    Faktum er at webserver-cluster-modulen bruker cluster_name-variabelen i en rekke ressurser, inkludert navneparameteren til to sikkerhetsgrupper og 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]
    }

    Hvis du endrer navneparameteren på en ressurs, vil Terraform slette den gamle versjonen av den ressursen og opprette en ny i stedet. Men hvis den ressursen er en ALB, mellom å slette den og laste ned en ny versjon, vil du ikke ha en mekanisme for å omdirigere trafikk til webserveren din. På samme måte, hvis en sikkerhetsgruppe slettes, vil serverne dine begynne å avvise all nettverkstrafikk inntil en ny gruppe er opprettet.

    En annen type refactoring du kan være interessert i er å endre Terraform ID. La oss ta aws_security_group-ressursen i webserver-cluster-modulen som et eksempel:

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

    Identifikatoren til denne ressursen kalles forekomst. Tenk deg at du under refactoring bestemte deg for å endre det til et mer forståelig (etter din mening) navn cluster_instance:

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

    Hva vil skje til slutt? Det stemmer: en forstyrrelse.

    Terraform knytter hver ressurs-ID til skyleverandør-IDen. For eksempel er iam_user assosiert med AWS IAM-bruker-ID, og ​​aws_instance er assosiert med AWS EC2-server-ID. Hvis du endrer ressurs-ID (f.eks. fra instans til cluster_instance, slik tilfellet er med aws_security_group), til Terraform vil det se ut som om du har slettet den gamle ressursen og lagt til en ny. Hvis du bruker disse endringene, vil Terraform slette den gamle sikkerhetsgruppen og opprette en ny, mens serverne dine begynner å avvise all nettverkstrafikk.

    Her er fire viktige lærdommer du bør ta med deg fra denne diskusjonen.

    • Bruk alltid plankommandoen. Det kan avsløre alle disse ulempene. Gjennomgå resultatet nøye og vær oppmerksom på situasjoner der Terraform planlegger å slette ressurser som mest sannsynlig ikke bør slettes.
    • Opprett før du sletter. Hvis du ønsker å erstatte en ressurs, tenk nøye gjennom om du må opprette en erstatning før du sletter originalen. Hvis svaret er ja, kan create_before_destroy hjelpe. Det samme resultatet kan oppnås manuelt ved å utføre to trinn: legg først til en ny ressurs i konfigurasjonen og kjør appliceringskommandoen, og fjern deretter den gamle ressursen fra konfigurasjonen og bruk appliceringskommandoen på nytt.
    • Endring av identifikatorer krever endring av tilstand. Hvis du vil endre ID-en som er knyttet til en ressurs (for eksempel endre navn på aws_security_group fra instans til cluster_instance) uten å slette ressursen og opprette en ny versjon av den, må du oppdatere Terraform-tilstandsfilen tilsvarende. Gjør aldri dette manuelt - bruk terraform state-kommandoen i stedet. Når du gir nytt navn til identifikatorer, bør du kjøre kommandoen terraform state mv, som har følgende syntaks:
      terraform state mv <ORIGINAL_REFERENCE> <NEW_REFERENCE>

      ORIGINAL_REFERENCE er et uttrykk som refererer til ressursen i sin nåværende form, og NEW_REFERENCE er dit du vil flytte den. For eksempel, når du gir nytt navn til aws_security_group-gruppen fra instans til cluster_instance, må du kjøre følgende kommando:

      $ terraform state mv 
         aws_security_group.instance 
         aws_security_group.cluster_instance

      Dette forteller Terraform at tilstanden som tidligere var assosiert med aws_security_group.instance nå skal være assosiert med aws_security_group.cluster_instance. Hvis etter å ha endret navn og kjørt denne kommandoen terraform plan ikke viser noen endringer, så gjorde du alt riktig.

    • Noen innstillinger kan ikke endres. Parametrene til mange ressurser er uforanderlige. Hvis du prøver å endre dem, vil Terraform slette den gamle ressursen og opprette en ny i stedet. Hver ressursside vil vanligvis indikere hva som skjer når du endrer en bestemt innstilling, så sørg for å sjekke dokumentasjonen. Bruk alltid plankommandoen og vurder å bruke strategien create_before_destroy.

    Utsatt konsistens er konsistent... med utsettelse

    Noen skyleverandørers APIer, for eksempel AWS, er asynkrone og har forsinket konsistens. Asynkroni betyr at grensesnittet umiddelbart kan returnere et svar uten å vente på at den forespurte handlingen skal fullføres. Forsinket konsistens betyr at endringer kan ta tid å forplante seg i hele systemet; mens dette skjer, kan svarene dine være inkonsekvente og avhengig av hvilken datakildereplika som svarer på API-kallene dine.

    Tenk deg for eksempel at du foretar et API-kall til AWS og ber den om å opprette en EC2-server. API-en vil returnere et "vellykket" svar (201 Opprettet) nesten umiddelbart, uten å vente på at selve serveren skal opprettes. Hvis du prøver å koble til den med en gang, vil den nesten helt sikkert mislykkes fordi AWS på det tidspunktet fortsatt initialiserer ressurser, eller alternativt har serveren ikke startet opp ennå. Dessuten, hvis du foretar et nytt anrop for å få informasjon om denne serveren, kan du få en feilmelding (404 ikke funnet). Saken er at informasjonen om denne EC2-serveren fortsatt kan spres gjennom AWS før den blir tilgjengelig overalt, du må vente noen sekunder.

    Hver gang du bruker en asynkron API med lat konsistens, må du med jevne mellomrom prøve forespørselen din på nytt til handlingen fullføres og spres gjennom systemet. Dessverre gir ikke AWS SDK noen gode verktøy for dette, og Terraform-prosjektet pleide å lide av mange feil som 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

    Med andre ord, du oppretter en ressurs (som et subnett) og prøver deretter å få litt informasjon om den (som IDen til det nyopprettede subnettet), og Terraform finner den ikke. De fleste av disse feilene (inkludert 6813) er fikset, men de dukker fortsatt opp fra tid til annen, spesielt når Terraform legger til støtte for en ny ressurstype. Dette er irriterende, men forårsaker i de fleste tilfeller ingen skade. Når du kjører terraform applicer igjen, skal alt fungere, siden informasjonen på dette tidspunktet allerede vil ha spredt seg over hele systemet.

    Dette utdraget er presentert fra boken av Evgeniy Brikman "Terraform: infrastruktur på kodenivå".

Kilde: www.habr.com

Legg til en kommentar