Terraform faldgruber

Terraform faldgruber
Lad os fremhæve et par faldgruber, herunder dem, der er relateret til loops, if-sætninger og implementeringsteknikker, såvel som mere generelle problemer, der påvirker Terraform generelt:

  • antallet og for_each parametre har begrænsninger;
  • begrænse nul nedetidsimplementeringer;
  • selv en god plan kan mislykkes;
  • refactoring kan have sine faldgruber;
  • udskudt sammenhæng er i overensstemmelse... med udsættelse.

Antallet og for_each-parametrene har begrænsninger

Eksemplerne i dette kapitel gør udstrakt brug af tælleparameteren og for_each-udtrykket i sløjfer og betinget logik. De klarer sig godt, men de har to vigtige begrænsninger, som du skal være opmærksom på.

  • Count og for_each kan ikke referere til nogen ressourceoutputvariable.
  • count og for_each kan ikke bruges i modulkonfiguration.

count og for_each kan ikke referere til nogen ressourceoutputvariable

Forestil dig, at du skal installere flere EC2-servere og af en eller anden grund ikke vil bruge ASG. Din kode kunne være sådan her:

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

Lad os se på dem én efter én.

Da count-parameteren er sat til en statisk værdi, vil denne kode fungere uden problemer: Når du kører appliceringskommandoen, vil den oprette tre EC2-servere. Men hvad nu hvis du ville installere én server i hver tilgængelighedszone (AZ) inden for din nuværende AWS-region? Du kan få din kode til at indlæse en liste over zoner fra datakilden aws_availability_zones og derefter gå gennem hver enkelt og oprette en EC2-server i den ved hjælp af tælleparameteren og array-indeksadgang:

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 count parameteren kan referere til datakilder uden problemer. Men hvad sker der, hvis antallet af servere, du skal oprette, afhænger af outputtet fra en ressource? For at demonstrere dette er den nemmeste måde at bruge 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 mellem 1 og 3. Lad os se, hvad der sker, hvis vi forsøger at bruge outputtet fra denne ressource i count-parameteren for aws_instance-ressourcen:

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

Hvis du kører terraform plan på denne kode, får du 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 oprettes eller ændres. Dette betyder, at count and for_each kan henvise til bogstaver, variabler, datakilder og endda ressourcelister (så længe deres længde kan bestemmes på tidspunktet for planlægning), men ikke til beregnede ressourceoutputvariabler.

count og for_each kan ikke bruges i modulkonfiguration

En dag kan du blive fristet til at tilføje en tælleparameter til din modulkonfiguration:

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 inde i et modul til at skabe tre kopier af webserver-cluster-ressourcen. Eller du vil måske gøre tilslutning af et modul valgfrit baseret på en boolsk tilstand ved at indstille dets tælleparameter til 0. Dette kan ligne en rimelig kode, men du får denne fejl, når du kø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 Terraform 0.12.6 er det desværre ikke understøttet at bruge count eller for_each i en modulressource. Ifølge Terraform 0.12 release notes (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, er den muligvis allerede tilgængelig. For at finde ud af det med sikkerhed, læs Terraform changelog her.

Begrænsninger af nul nedetidsimplementeringer

Brug af create_before_destroy-blokken i kombination med ASG er en fantastisk løsning til at skabe nul-downtime-implementeringer, bortset fra en advarsel: autoskaleringsregler understøttes ikke. Eller for at være mere præcis, nulstiller dette ASG-størrelsen tilbage til min_size ved hver implementering, hvilket kunne være et problem, hvis du brugte autoskaleringsregler til at øge antallet af servere, der kører.

For eksempel indeholder webserver-cluster-modulet et par aws_autoscaling_schedule-ressourcer, som kl. 9 øger antallet af servere i klyngen fra to til ti. Hvis du implementerer for eksempel kl. 11, starter den nye ASG op med kun to servere i stedet for ti og forbliver sådan indtil kl. 9 næste dag.

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

  • Skift gentagelsesparameteren i aws_autoscaling_schedule fra 0 9 * * * ("løb kl. 9 om morgenen") til noget i stil med 0-59 9-17 * * * ("løb hvert minut fra kl. 9 til 5"). Hvis ASG allerede har ti servere, vil det at køre denne autoskaleringsregel igen ikke ændre noget, hvilket er det, vi ønsker. Men hvis ASG'en først er blevet installeret for nylig, vil denne regel sikre, at antallet af dens servere på maksimalt et minut når ti. Dette er ikke en helt elegant tilgang, og store spring fra ti til to servere og tilbage kan også give problemer for brugerne.
  • Opret et brugerdefineret script, der bruger AWS API til at bestemme antallet af aktive servere i ASG'en, kald det ved hjælp af en ekstern datakilde (se "Ekstern datakilde" på side 249), og indstil ASG's ønsket_kapacitet parameter til værdien returneret af manuskriptet. På denne måde vil hver ny ASG-instans altid køre med samme kapacitet som den eksisterende Terraform-kode og gør den sværere at vedligeholde.

Naturligvis ville Terraform ideelt set have indbygget support til nul-nedetidsimplementeringer, men fra maj 2019 havde HashiCorp-teamet ingen planer om at tilføje denne funktionalitet (detaljer - her).

Den korrekte plan kan blive implementeret uden held

Nogle gange producerer plankommandoen en helt korrekt implementeringsplan, men anvend kommandoen returnerer en fejl. Prøv for eksempel at tilføje aws_iam_user-ressourcen med det samme navn, som du brugte til den IAM-bruger, du oprettede tidligere i kapitel 2:

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

Nu, hvis du kører plankommandoen, vil Terraform udsende en tilsyneladende rimelig implementeringsplan:

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 kører appliceringskommandoen, får du følgende fejl:

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 der allerede eksisterer en IAM-bruger med det navn. Og dette kan ikke kun ske for IAM-brugere, men for næsten enhver ressource. Det er muligt, at nogen har oprettet denne ressource manuelt eller ved hjælp af kommandolinjen, men i begge tilfælde fører matchende id'er til konflikter. Der er mange variationer af denne fejl, der ofte overrasker nybegyndere til Terraform.

Nøglepunktet er, at kommandoen terraform plan kun tager højde for de ressourcer, der er specificeret i Terraform tilstandsfilen. Hvis ressourcer oprettes på en anden måde (f.eks. manuelt ved at klikke i AWS-konsollen), ender de ikke i tilstandsfilen, og derfor vil Terraform ikke tage hensyn til dem, når plankommandoen udføres. Som følge heraf vil en plan, der ved første øjekast virker korrekt, vise sig at være mislykket.

Der er to erfaringer at lære af dette.

  • Hvis du allerede er begyndt at arbejde med Terraform, skal du ikke bruge andet. Hvis en del af din infrastruktur administreres ved hjælp af Terraform, kan du ikke længere ændre den manuelt. Ellers risikerer du ikke kun mærkelige Terraform-fejl, men du ophæver 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 noget infrastruktur, skal du bruge importkommandoen. Hvis du begynder at bruge Terraform med eksisterende infrastruktur, kan du tilføje den til tilstandsfilen ved at bruge kommandoen terraform import. På denne måde vil Terraform vide, hvilken infrastruktur der skal administreres. Importkommandoen tager to argumenter. Den første er ressourceadressen i dine konfigurationsfiler. Syntaksen her er den samme som for ressourcelinks: _. (som aws_iam_user.existing_user). Det andet argument er ID'et for den ressource, der skal importeres. Lad os sige, at ressource-id'et aws_iam_user er brugernavnet (for eksempel yevgeniy.brikman), og ressource-id'et aws_instance er EC2-server-id'et (som i-190e22e5). Hvordan man importerer en ressource er normalt angivet i dokumentationen nederst på dens side.

    Nedenfor er en importkommando, der synkroniserer aws_iam_user-ressourcen, som du tilføjede til din Terraform-konfiguration sammen med IAM-brugeren i kapitel 2 (der erstatter naturligvis dit navn med yevgeniy.brikman):

    $ terraform import aws_iam_user.existing_user yevgeniy.brikman

    Terraform kalder AWS API for at finde din IAM-bruger og oprette en tilstandsfiltilknytning mellem den og ressourcen aws_iam_user.existing_user i din Terraform-konfiguration. Fra nu af, når du kører plankommandoen, vil Terraform vide, at IAM-brugeren allerede eksisterer og vil ikke forsøge at oprette den igen.

    Det er værd at bemærke, at hvis du allerede har mange ressourcer, som du vil importere til Terraform, kan det være besværligt at skrive koden manuelt og importere hver enkelt ad gangen. Så det er værd at kigge nærmere på et værktøj som Terraforming (http://terraforming.dtan4.net/), som automatisk kan importere kode og tilstand fra din AWS-konto.

    Refaktorering kan have sine faldgruber

    Refaktorering er en almindelig praksis i programmering, hvor du ændrer kodens interne struktur, mens du lader den eksterne adfærd være uændret. Dette er for at gøre koden klarere, pænere og nemmere at vedligeholde. Refaktorering er en uundværlig teknik, der bør bruges regelmæssigt. Men når det kommer til Terraform eller et hvilket som helst andet IaC-værktøj, skal du være ekstremt forsigtig med, hvad du mener med et stykke kodes "eksterne adfærd", ellers vil der opstå uventede problemer.

    For eksempel er en almindelig type refactoring at erstatte navnene på variabler eller funktioner med mere forståelige. Mange IDE'er har indbygget understøttelse af refactoring og kan automatisk omdøbe variabler og funktioner gennem hele projektet. I almene programmeringssprog er dette en triviel procedure, som du måske ikke tænker over, men i Terraform skal du være yderst forsigtig med dette, ellers kan du opleve udfald.

    For eksempel har webserver-cluster-modulet en inputvariabel cluster_name:

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

    Forestil dig, at du begyndte at bruge dette modul til at implementere en mikrotjeneste kaldet foo. Senere vil du omdøbe din tjeneste til bar. Denne ændring kan virke triviel, men i virkeligheden kan den forårsage serviceforstyrrelser.

    Faktum er, at webserver-cluster-modulet bruger cluster_name-variablen i en række ressourcer, herunder navneparameteren 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 du ændrer navneparameteren på en ressource, vil Terraform slette den gamle version af den ressource og oprette en ny i stedet. Men hvis den ressource er en ALB, mellem at slette den og downloade en ny version, vil du ikke have en mekanisme til at omdirigere trafik til din webserver. Ligeledes, hvis en sikkerhedsgruppe slettes, vil dine servere begynde at afvise enhver netværkstrafik, indtil en ny gruppe er oprettet.

    En anden type refactoring, du måske er interesseret i, er at ændre Terraform ID'et. Lad os tage ressourcen aws_security_group i webserver-cluster-modulet som et eksempel:

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

    Identifikationen af ​​denne ressource kaldes instans. Forestil dig, at du under refactoring besluttede at ændre det til et mere forståeligt (efter din mening) navn cluster_instance:

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

    Hvad vil der ske i sidste ende? Det er rigtigt: en forstyrrelse.

    Terraform knytter hvert ressource-id til skyudbyder-id'et. For eksempel er iam_user knyttet til AWS IAM-bruger-id'et, og aws_instance er knyttet til AWS EC2-server-id'et. Hvis du ændrer ressource-id'et (f.eks. fra instans til cluster_instance, som det er tilfældet med aws_security_group), til Terraform vil det se ud som om 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, mens dine servere begynder at afvise enhver netværkstrafik.

    Her er fire vigtige lektioner, du bør tage med fra denne diskussion.

    • Brug altid plankommandoen. Det kan afsløre alle disse problemer. Gennemgå dets output omhyggeligt og vær opmærksom på situationer, hvor Terraform planlægger at slette ressourcer, som højst sandsynligt ikke bør slettes.
    • Opret før du sletter. Hvis du vil erstatte en ressource, skal du tænke grundigt over, om du skal oprette en erstatning, før du sletter originalen. Hvis svaret er ja, kan create_before_destroy hjælpe. Det samme resultat kan opnås manuelt ved at udføre to trin: Føj først en ny ressource til konfigurationen og kør appliceringskommandoen, og fjern derefter den gamle ressource fra konfigurationen og brug appliceringskommandoen igen.
    • Ændring af identifikatorer kræver ændring af tilstand. Hvis du vil ændre det id, der er knyttet til en ressource (f.eks. omdøbe aws_security_group fra instans til cluster_instance) uden at slette ressourcen og oprette en ny version af den, skal du opdatere Terraform-tilstandsfilen i overensstemmelse hermed. Gør aldrig dette manuelt - brug i stedet kommandoen terraform tilstand. Når du omdøber identifikatorer, skal du køre kommandoen terraform state mv, som har følgende syntaks:
      terraform state mv <ORIGINAL_REFERENCE> <NEW_REFERENCE>

      ORIGINAL_REFERENCE er et udtryk, der refererer til ressourcen i dens nuværende form, og NEW_REFERENCE er, hvor du vil flytte den. For eksempel, når du omdøber gruppen aws_security_group fra instans til cluster_instance, skal du køre følgende kommando:

      $ terraform state mv 
         aws_security_group.instance 
         aws_security_group.cluster_instance

      Dette fortæller Terraform, at den tilstand, der tidligere var forbundet med aws_security_group.instance, nu skal være forbundet med aws_security_group.cluster_instance. Hvis efter at have omdøbt og kørt denne kommando terraform plan ikke viser nogen ændringer, så gjorde du alt korrekt.

    • Nogle indstillinger kan ikke ændres. Parametrene for mange ressourcer er uforanderlige. Hvis du prøver at ændre dem, vil Terraform slette den gamle ressource og oprette en ny i stedet. Hver ressourceside vil normalt angive, hvad der sker, når du ændrer en bestemt indstilling, så sørg for at tjekke dokumentationen. Brug altid plankommandoen og overvej at bruge create_before_destroy-strategien.

    Udskudt konsistens er i overensstemmelse... med udsættelse

    Nogle cloud-udbyderes API'er, såsom AWS, er asynkrone og har forsinket konsistens. Asynkroni betyder, at grænsefladen straks kan returnere et svar uden at vente på, at den anmodede handling er fuldført. Forsinket konsistens betyder, at ændringer kan tage tid at udbrede sig i hele systemet; mens dette sker, kan dine svar være inkonsekvente og afhængige af, hvilken datakilde replika, der reagerer på dine API-kald.

    Forestil dig for eksempel, at du foretager et API-kald til AWS og beder den om at oprette en EC2-server. API'en returnerer et "succesfuldt" svar (201 Oprettet) næsten øjeblikkeligt uden at vente på, at serveren selv bliver oprettet. Hvis du prøver at oprette forbindelse til det med det samme, vil det næsten helt sikkert mislykkes, fordi AWS på det tidspunkt stadig initialiserer ressourcer, eller alternativt er serveren endnu ikke startet. Desuden, hvis du foretager et nyt opkald for at få oplysninger om denne server, kan du modtage en fejlmeddelelse (404 ikke fundet). Sagen er, at informationen om denne EC2-server stadig kan spredes i hele AWS, før den bliver tilgængelig overalt, du skal vente et par sekunder.

    Hver gang du bruger en asynkron API med doven konsistens, skal du med jævne mellemrum prøve din anmodning igen, indtil handlingen er fuldført og spredes gennem systemet. Desværre giver AWS SDK ikke nogen gode værktøjer til dette, og Terraform-projektet plejede at lide af en masse fejl 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 opretter en ressource (som et undernet) og prøver derefter at få nogle oplysninger om det (som id'et for det nyoprettede undernet), og Terraform kan ikke finde det. De fleste af disse fejl (inklusive 6813) er blevet rettet, men de dukker stadig op fra tid til anden, især når Terraform tilføjer understøttelse af en ny ressourcetype. Dette er irriterende, men forårsager i de fleste tilfælde ingen skade. Når du kører terraform application igen, burde alt fungere, da informationen på dette tidspunkt allerede vil være spredt i hele systemet.

    Dette uddrag er præsenteret fra bogen af ​​Evgeniy Brikman "Terraform: infrastruktur på kodeniveau".

Kilde: www.habr.com

Tilføj en kommentar