Faldgruber i Terraform

Faldgruber i Terraform
Lad os fremhæve nogle faldgruber, herunder dem der er relateret til cykler, if-udtryk og implementeringsmetoder samt mere generelle problemer, der vedrører Terraform generelt:

  • parametrene count og for_each har begrænsninger;
  • behov for nedetid under udrulninger;
  • selv en god plan kan resultere i problemer;
  • refaktorisering kan have sine faldgruber;
  • forsinket konsistens stemmer overens… med udsættelse.

Parametrene count og for_each har begrænsninger

I eksemplerne i dette kapitel anvendes parameteren count og udtrykket for_each aktivt i cykler og betinget logik. De præsterer godt, men de har to vigtige begrænsninger, som det er vigtigt at være opmærksom på.

  • I count og for_each kan man ikke referere til nogen outputvariable fra ressourcen.
  • count og for_each kan ikke bruges i modulkonfigurationen.

I count og for_each kan man ikke referere til nogen outputvariable fra ressourcen

Forestil dig, at du skal udrulle flere EC2-servere, og af en eller anden grund ønsker du ikke at bruge ASG. Din kode kan se sådan ud:

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

Lad os se på dem hver for sig.

Da parameteren count er tildelt en statisk værdi, vil denne kode fungere uden problemer: når du udfører apply-kommandoen, opretter den tre EC2-servere. Men hvis du ønskede at udrulle én server i hver tilgængelighedszon (Availability Zone eller AZ) inden for den nuværende AWS-region? Du kan lade din kode indlæse listen over zoner fra datasourcen aws_availability_zones og så "cyklisk" gå igennem hver af dem og oprette en EC2-server i hver, ved at bruge parameteren count og adgang til arrayet via indekset:

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 kode vil også fungere fint, da parameteren count kan referere til datakilder uden problemer. Men hvad sker der, hvis antallet af servere, du har brug for at oprette, afhænger af output fra en eller anden ressource? For at demonstrere dette, er det nemmest at tage ressourcen random_integer, som, som navnet antyder, returnerer et tilfældigt heltal:

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

Denne kode genererer et tilfældigt tal fra 1 til 3. Lad os se, hvad der sker, hvis vi prøver at bruge output result fra denne ressource i parameteren count for ressourcen aws_instance:

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

Hvis vi kører terraform plan for denne kode, får vi følgende fejl:

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 kræver, at count og for_each beregnes under planlægningsfasen, før nogen ressourcer kan oprettes eller ændres. Det betyder, at count og for_each kan referere til litteraler, variabler, datakilder og endda ressource lister (forudsat at deres længde kan bestemmes under planlægningen), men ikke til beregnede output variabler fra ressourcer.

count og for_each kan ikke bruges i modulets konfiguration.

Engang kan du blive fristet til at tilføje parameteren count i modulkonfigurationer:

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 kode forsøger at bruge count inden for et modul for at oprette tre kopier af webserver-kluster ressourcen. Eller måske ønsker du at gøre modulforbindelsen valgfrie afhængigt af en boolesk betingelse ved at give dens parameter count værdien 0. Sådan kode vil se ganske fornuftigt ud, men som resultat af at køre terraform plan får du denne fejl:

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.

Desværre er brugen af count eller for_each i resource modulet ikke understøttet på tidspunktet for udgivelsen af Terraform 0.12.6. Ifølge udgivelsesnotaterne for Terraform 0.12 (http://bit.ly/3257bv4) planlægger HashiCorp at tilføje denne funktion i fremtiden, så afhængigt af hvornår du læser denne bog, kan det allerede være tilgængeligt. For at finde ud af det: check changelog for Terraform her.

Begrænsninger for implementeringer med nul nedetid

Brugen af create_before_destroy-blokken i kombination med ASG er en fremragende løsning til at organisere deployment med nul nedetid, hvis man ser bort fra én nuance: automatiseringsreglerne understøttes ikke. Eller for at være mere præcis, det nulstiller størrelsen af ASG tilbage til min_size ved hver deployment, hvilket kan blive et problem, hvis du har anvendt automatiseringsregler til at øge antallet af kørende servere.

For eksempel indeholder webserver-cluster-modulet et par ressourcer aws_autoscaling_schedule, der kl. 9 morgen øger antallet af servere i klyngen fra to til ti. Hvis deployment udføres, lad os sige kl. 11, vil den nye ASG-gruppe ikke starte med ti, men kun med to servere og vil forblive i denne tilstand indtil kl. 9 den næste dag.

Denne begrænsning kan omgås på flere måder.

  • Ændre parameteren recurrence i aws_autoscaling_schedule fra 0 9 * * * ("kør kl. 9 morgen") til noget som 0-59 9-17 * * * ("kør hvert minut fra kl. 9 morgen til kl. 17"). Hvis der allerede er ti servere i ASG, vil en rekursiv udførelse af denne automatiseringsregel ikke ændre noget, hvilket er præcis, hvad vi ønsker. Men hvis ASG-gruppen lige er blevet deployeret, garanterer denne regel, at maksimum efter et minut vil antallet af dens servere nå op på ti. Det er ikke en særlig elegant tilgang, og store spring fra ti til to servere og tilbage kan også skabe problemer for brugerne.
  • Opret et tilpasset script, der bruger AWS API til at bestemme antallet af aktive servere i ASG, påkald det ved hjælp af en ekstern datakilde (se "Ekstern datakilde" på s. 249) og tildel parameteren desired_capacity for ASG-gruppen den værdi, som dette script returnerer. På denne måde vil hver ny ASG-instans altid blive startet med den samme kapacitet som den tidligere kode i Terraform, hvilket komplicerer vedligeholdelsen.

Selvfølgelig, ideelt set burde Terraform have indbygget støtte til deployment med nul nedetid, men pr. maj 2019 planlagde HashiCorp-teamet ikke at tilføje denne funktionalitet (detaljer – her).

En korrekt plan kan blive mislykket implementeret

Nogle gange, når du kører planen, får du en helt korrekt implementeringsplan, men kommandoen apply returnerer en fejl. Prøv for eksempel at tilføje ressourcen aws_iam_user med det samme navn, som du brugte til IAM-brugeren, du oprettede i kapitel 2:

resource "aws_iam_user" "existing_user" {
   # Indsæt her navnet på den allerede eksisterende IAM-bruger,
   # så du kan øve dig i at bruge kommandoen terraform import
   name = "yevgeniy.brikman"
}

Nu, hvis du udfører kommandoen plan, vil Terraform give en tilsyneladende fornuftig implementeringsplan:

Terraform vil udføre følgende handlinger:

   # aws_iam_user.existing_user vil blive oprettet
   + resource "aws_iam_user" "existing_user" {
         + arn                  = (kendt efter apply)
         + force_destroy   = false
         + id                    = (kendt efter apply)
         + name               = "yevgeniy.brikman"
         + path                 = "\/"
         + unique_id         = (kendt efter apply)
      }

Plan: 1 tilføje, 0 ændre, 0 slette.

Hvis du udfører kommandoen apply, vil følgende fejl opstå:

Error: Fejl ved oprettelse af IAM-bruger yevgeniy.brikman: EntityAlreadyExists:
Bruger 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 naturligvis, at en IAM-bruger med dette navn allerede eksisterer. Og det kan ske ikke kun med IAM-brugere, men næsten med enhver ressource. Måske har nogen oprettet denne ressource manuelt eller via kommandolinjen, men uanset hvad, fører identifikationsoverensstemmelser til konflikter. Denne fejl har mange variationer, der ofte overrasker nybegyndere i Terraform.

Det vigtigste punkt er, at kommandoen terraform plan kun tager højde for de ressourcer, der er angivet i Terraform tilstandsfilen. Hvis ressourcerne er oprettet på en anden måde (f.eks. manuelt, ved at klikke i AWS-konsollen), vil de ikke komme ind i tilstandsfilen, og derfor vil Terraform ikke tage dem i betragtning ved udførelsen af kommandoen plan. Resultatet er, at en tilsyneladende korrekt plan viser sig at være mislykket.

Der kan drages to lektioner herfra.

  • Hvis du allerede er begyndt at arbejde med Terraform, så brug ikke noget andet. Hvis en del af din infrastruktur styres af Terraform, må du ikke længere ændre det manuelt. Ellers risikerer du ikke bare at møde mærkelige fejl i Terraform, men du underminerer også mange af fordelene ved IaC, da koden ikke længere vil være en nøjagtig repræsentation af din infrastruktur.
  • Hvis du allerede har en eksisterende infrastruktur, kan du bruge kommandoen import. Hvis du begynder at bruge Terraform med en eksisterende infrastruktur, kan du tilføje den til state-filen ved hjælp af kommandoen terraform import. På den måde vil Terraform vide, hvilken infrastruktur der skal styres. Kommandoen import accepterer to argumenter. Den første er adressen på ressourcen i dine konfigurationsfiler. Her er syntaksen den samme som i links til ressourcer: _. (for eksempel aws_iam_user.existing_user). Det andet argument er ID'et på den ressource, der skal importeres. For eksempel fungerer brugernavnet (som yevgeniy.brikman) som ID'et for ressourcen aws_iam_user, og ID'et for ressourcen aws_instance vil være serverens EC2-id (for eksempel i-190e22e5). Hvordan man importerer en ressource angives normalt i dokumentationen nederst på dens side.

    Nedenfor er kommandoen import, der gør det muligt at synkronisere ressourcen aws_iam_user, som du har tilføjet til din Terraform-konfiguration sammen med IAM-brugeren i kapitel 2 (selvfølgelig skal du erstatte yevgeniy.brikman med dit eget navn):

    $ terraform import aws_iam_user.existing_user yevgeniy.brikman

    Terraform kontakter AWS API'et for at finde din IAM-bruger og opretter en forbindelse i state-filen mellem denne og ressourcen aws_iam_user.existing_user i din Terraform-konfiguration. Fra dette tidspunkt vil Terraform vide, at IAM-brugeren allerede eksisterer, og vil ikke forsøge at oprette den igen, når plan-kommandoen udføres.

    Det skal bemærkes, at hvis du allerede har mange ressourcer, som du ønsker at importere til Terraform, kan det være en besværlig opgave at skrive kode manuelt og importere hver af dem en ad gangen. Derfor kan det være en god idé at tage et kig på et værktøj som Terraforming (http://terraforming.dtan4.net/), der automatisk kan importere kode og tilstand fra en AWS-konto.

    Refactoring kan have sine faldgruber

    Refactoring er en almindelig praksis inden for programmering, hvor du ændrer den interne struktur af koden, mens du lader den eksterne adfærd forblive uændret. Dette er nødvendigt for at gøre koden mere forståelig, ryddelig og nem at vedligeholde. Refactoring er en uundgåelig teknik, der regelmæssigt bør anvendes. Men når det kommer til Terraform eller ethvert andet værktøj til IaC, er det ekstremt vigtigt at være forsigtig med, hvad der menes med "eksterne adfærd" i en del af koden, ellers kan der opstå uforudsete problemer.

    For eksempel, en almindelig form for refactoring er at erstatte variable- eller funktionsnavne med mere forståelige. Mange IDE'er har indbygget understøttelse af refactoring og kan automatisk omdøbe variable og funktioner på tværs af hele projektet. I generelle programmeringssprog er dette en triviel proces, som man ikke behøver at tænke over, men i Terraform skal man være yderst forsigtig, ellers kan man opleve nedetid.

    For eksempel har modulet webserver-cluster en indgangsvariabel cluster_name:

    variable "cluster_name" {
       description = "Navnet, der skal bruges til alle cluster-ressourcer"
       type          = string
    }

    Forestil dig, at du er begyndt at bruge dette modul til at implementere en mikrotjeneste med navnet foo. Senere ønskede du at omdøbe din tjeneste til bar. Denne ændring kan virke triviel, men i virkeligheden kan den forårsage nedetid.

    Problemet er, at modulet webserver-cluster bruger variablen cluster_name i en række ressourcer, herunder parameteren name for to sikkerhedsgrupper 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 parameteren name ændres i en ressource, vil Terraform slette den gamle version af denne ressource og oprette en ny. Men hvis en sådan ressource er ALB, vil du ikke have en mekanisme til at omdirigere trafik til din webserver i perioden mellem sletningen og indlæsningen af den nye version. på samme måde, hvis en sikkerhedsgruppe slettes, vil dine servere begynde at afvise al netværkstrafik, indtil en ny gruppe oprettes.

    En anden form for refactoring, som du måske finder interessant, er ændring af Terraform-identifikatoren. Lad os tage eksemplet med ressourcen aws_security_group i modulet webserver-cluster:

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

    Identifikatoren for denne ressource kaldes instance. Forestil dig, at du under refactoreringen besluttede at ændre den til et mere forståeligt navn (efter din mening) cluster_instance:

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

    Hvad vil der ske? Korrekt: nedetid.

    Terraform knytter ID'en for hver ressource til identifikatoren fra cloududbyderen. For eksempel forbindes iam_user med IAM-brugerens ID i AWS, mens aws_instance knyttes til ID'en for en AWS EC2-server. Hvis du ændrer ressource-id'et (f.eks. fra instance til cluster_instance, som tilfældet er med aws_security_group), vil Terraform opfatte det som om, at du har slettet den gamle ressource og tilføjet en ny. Hvis du anvender disse ændringer, vil Terraform slette den gamle sikkerhedsgruppe og oprette en ny, og i mellemtiden vil dine servere begynde at afvise enhver netværkstrafik.

    Her er fire grundlæggende lektioner, som du bør tage med dig fra denne diskussion.

    • Brug altid plan-kommandoen. Den kan hjælpe med at identificere alle disse problemer. Gennemgå dens output grundigt, og vær opmærksom på situationer, hvor Terraform planlægger at slette ressourcer, som sandsynligvis ikke bør fjernes.
    • Opret, før du sletter. Hvis du ønsker at erstatte en ressource, skal du nøje overveje, om det er nødvendigt at oprette en erstatning, før den originale slettes. Hvis svaret er ja, kan create_before_destroy være til hjælp. Du kan også opnå det samme resultat manuelt ved at udføre to trin: først tilføje den nye ressource i konfigurationen og køre apply-kommandoen, og derefter fjerne den gamle ressource fra konfigurationen og bruge apply-kommandoen igen.
    • Ændring af identifikatorer kræver ændringer i tilstanden. Hvis du vil ændre id'et knyttet til en ressource (f.eks. omdøbe aws_security_group fra instance til cluster_instance) uden at slette ressourcen og oprette en ny version, skal du opdatere Terraform-tilstandsfilen korrekt. Gør aldrig dette manuelt — brug i stedet terraform state-kommandoen. Når du omdøber identifikatorer, skal du bruge terraform state mv-kommandoen, som har følgende syntaks:
      terraform state mv

      ORIGINAL_REFERENCE er et udtryk, der henviser til ressourcen i dens nuværende form, mens NEW_REFERENCE er den placering, hvor du ønsker at flytte den hen. For eksempel, når du omdøber aws_security_group fra instance til cluster_instance, skal du køre følgende kommando:

      $ terraform state mv 
         aws_security_group.instance 
         aws_security_group.cluster_instance

      Så vil du meddele Terraform, at den tilstand, der tidligere gjaldt for aws_security_group.instance, nu skal knyttes til 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. Parametre for mange ressourcer er uforanderlige. Hvis du forsøger at ændre dem, vil Terraform slette den gamle ressource og oprette en ny i stedet. Hver ressource har normalt en beskrivelse af, hvad der sker, når en given parameter ændres, så husk altid at tjekke dokumentationen. Brug altid kommandoen plan, og overvej hensigtsmæssigheden af at anvende strategien create_before_destroy.

    Forsinket konsistens harmonerer… med forsinkelse

    API'er fra nogle cloud-udbydere, såsom AWS, er asynkrone og har forsinket konsistens. Asynkronitet betyder, at API'et straks kan returnere et svar uden at vente på, at den anmodede handling er afsluttet. Forsinket konsistens betyder, at det kan tage tid at udbrede ændringerne i hele systemet; mens dette sker, kan dine svar være inkonsistente og afhænge af, hvilken datakilde der svarer på dine API-anmodninger.

    Forestil dig for eksempel, at du laver et API-opkald til AWS for at oprette en EC2-server. API'et vil næsten straks returnere et "succesfuldt" svar (201 Created) uden at vente på, at selve serveren er oprettet. Hvis du straks forsøger at oprette forbindelse til den, lykkes det næsten sikkert ikke, da AWS stadig initialiserer ressourcerne, eller serveren endnu ikke er startet op. Desuden, hvis du laver endnu et opkald for at få information om denne server, kan du få en fejl (404 Not Found). Faktisk kan oplysningerne om denne EC2-server stadig være under udbredelse gennem AWS, og det kan tage et par sekunder, før de er tilgængelige overalt.

    Hver gang du bruger et asynkront API med forsinket konsistens, skal du regelmæssigt gentage din anmodning, indtil handlingen er afsluttet og udbredt i systemet. Desværre giver AWS SDK ikke gode værktøjer til dette, og Terraform-projektet har tidligere lidt under mange fejl såsom 6813 (https://github.com/hashicorp/terraform/issues/6813):

    $ terraform apply
    aws_subnet.private-persistence.2: InvalidSubnetID.NotFound:
    Subnet ID 'subnet-xxxxxxx' findes ikke

    Med andre ord opretter du en ressource (f.eks. et subnet) og forsøger derefter at få nogle oplysninger om den (som ID'en på det nyligt oprettede subnet), men Terraform kan ikke finde dem. De fleste af sådanne fejl (inklusive 6813) er allerede blevet rettet, men de opstår stadig fra tid til anden, især når der tilføjes understøttelse for nye typer ressourcer i Terraform. Det er frustrerende, men i de fleste tilfælde skader det ikke. Ved at køre terraform apply igen, skulle det fungere, da oplysningerne nu er blevet spredt i systemet.

    Dette uddrag er fra bogen af Evgeny Brikman «Terraform: Infrastruktur som kode».

Kilde: habr.com

Køb pålidelig webhosting med DDoS-beskyttelse, VPS VDS-servere 🔥 Køb pålidelig webhosting med DDoS-beskyttelse, VPS VDS-servere | ProHoster