Terraform fallgropar

Terraform fallgropar
Låt oss lyfta fram några fallgropar, inklusive de som är relaterade till loopar, if-satser och distributionstekniker, såväl som mer allmänna problem som påverkar Terraform i allmänhet:

  • count och for_each parametrar har begränsningar;
  • begränsa driftstopp för noll driftstopp;
  • även en bra plan kan misslyckas;
  • refaktorering kan ha sina fallgropar;
  • uppskjuten koherens överensstämmer... med uppskov.

Parametrarna count och for_each har begränsningar

Exemplen i det här kapitlet använder i stor utsträckning parametern count och uttrycket for_each i loopar och villkorlig logik. De presterar bra, men de har två viktiga begränsningar som du måste vara medveten om.

  • Count och for_each kan inte referera till några resursutdatavariabler.
  • count och for_each kan inte användas i modulkonfiguration.

count och for_each kan inte referera till några resursutdatavariabler

Föreställ dig att du behöver distribuera flera EC2-servrar och av någon anledning inte vill använda ASG. Din kod kan vara så här:

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

Låt oss titta på dem en efter en.

Eftersom count-parametern är inställd på ett statiskt värde kommer den här koden att fungera utan problem: när du kör kommandot applicera kommer den att skapa tre EC2-servrar. Men vad händer om du ville distribuera en server i varje tillgänglighetszon (AZ) inom din nuvarande AWS-region? Du kan låta din kod läsa in en lista över zoner från datakällan aws_availability_zones och sedan gå igenom var och en och skapa en EC2-server i den med hjälp av count-parametern och arrayindexåtkomst:

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

Den här koden kommer också att fungera bra, eftersom parametern count kan referera till datakällor utan problem. Men vad händer om antalet servrar du behöver skapa beror på resultatet från någon resurs? För att demonstrera detta är det enklaste sättet att använda resursen random_integer, som, som namnet antyder, returnerar ett slumpmässigt heltal:

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

Den här koden genererar ett slumptal mellan 1 och 3. Låt oss se vad som händer om vi försöker använda utdata från denna resurs i count-parametern för resursen aws_instance:

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

Om du kör terraform plan på den här koden får du följande felmeddelande:

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 att count och for_each beräknas under planeringsfasen, innan några resurser skapas eller ändras. Detta innebär att count and for_each kan referera till bokstavliga tal, variabler, datakällor och till och med resurslistor (så länge deras längd kan bestämmas vid schemaläggning), men inte till beräknade resursutdatavariabler.

count och for_each kan inte användas i modulkonfiguration

Någon gång kan du bli frestad att lägga till en räkneparameter till 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"
}

Den här koden försöker använda count inuti en modul för att skapa tre kopior av webbserver-klusterresursen. Eller så kanske du vill göra anslutningen av en modul valfri baserat på något booleskt tillstånd genom att ställa in dess count-parameter till 0. Detta kan se ut som rimlig kod, men du får det här felet när du kör 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.

Tyvärr, från och med Terraform 0.12.6, stöds inte användning av count eller for_each i en modulresurs. Enligt Terraform 0.12 release notes (http://bit.ly/3257bv4) planerar HashiCorp att lägga till denna funktion i framtiden, så beroende på när du läser den här boken kan den redan vara tillgänglig. För att säkert ta reda på läs Terraforms ändringslogg här.

Begränsningar för Noll driftstopp

Att använda create_before_destroy-blocket i kombination med ASG är en utmärkt lösning för att skapa driftsättningar utan driftstopp, förutom en varning: regler för automatisk skalning stöds inte. Eller för att vara mer exakt, detta återställer ASG-storleken till min_size vid varje distribution, vilket kan vara ett problem om du använder regler för automatisk skalning för att öka antalet servrar som körs.

Webserver-klustermodulen innehåller till exempel ett par aws_autoscaling_schedule-resurser, som klockan 9 på morgonen ökar antalet servrar i klustret från två till tio. Om du implementerar vid, säg, 11:9, kommer den nya ASG att starta upp med bara två servrar istället för tio och förbli så till XNUMX:XNUMX nästa dag.

Denna begränsning kan kringgås på flera sätt.

  • Ändra upprepningsparametern i aws_autoscaling_schedule från 0 9 * * * ("kör klockan 9 på morgonen") till något i stil med 0-59 9-17 * * * ("kör varje minut från 9:5 till XNUMX:XNUMX"). Om ASG redan har tio servrar kommer inte att ändra någonting att köra denna autoskalningsregel igen, vilket är vad vi vill. Men om ASG bara nyligen har distribuerats, kommer denna regel att säkerställa att antalet servrar på högst en minut når tio. Detta är inte ett helt elegant tillvägagångssätt, och stora hopp från tio till två servrar och tillbaka kan också orsaka problem för användarna.
  • Skapa ett anpassat skript som använder AWS API för att bestämma antalet aktiva servrar i ASG, anropa det med hjälp av en extern datakälla (se "Extern datakälla" på sidan 249) och ställ in ASG:s parameter wish_capacity till värdet som returneras av manuset. På så sätt kommer varje ny ASG-instans alltid att köras med samma kapacitet som den befintliga Terraform-koden och gör den svårare att underhålla.

Naturligtvis skulle Terraform helst ha inbyggt stöd för driftsättningar utan driftstopp, men från och med maj 2019 hade HashiCorp-teamet inga planer på att lägga till denna funktionalitet (detaljer - här).

Den korrekta planen kan misslyckas implementeras

Ibland producerar plankommandot en helt korrekt distributionsplan, men kommandot applicera returnerar ett fel. Försök till exempel att lägga till resursen aws_iam_user med samma namn som du använde för IAM-användaren som du skapade tidigare i kapitel 2:

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

Nu, om du kör plankommandot, kommer Terraform att mata ut en till synes rimlig distributionsplan:

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.

Om du kör kommandot applicera får du följande felmeddelande:

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 är förstås att det redan finns en IAM-användare med det namnet. Och detta kan hända inte bara för IAM-användare, utan för nästan alla resurser. Det är möjligt att någon skapat den här resursen manuellt eller med hjälp av kommandoraden, men hur som helst leder matchande ID till konflikter. Det finns många varianter av detta fel som ofta överraskar nykomlingar till Terraform.

Nyckelpunkten är att kommandot terraform plan endast tar hänsyn till de resurser som anges i Terraforms tillståndsfil. Om resurser skapas på något annat sätt (till exempel manuellt genom att klicka i AWS-konsolen) kommer de inte att hamna i tillståndsfilen och därför kommer Terraform inte att ta hänsyn till dem när plankommandot utförs. Som ett resultat kommer en plan som verkar korrekt vid första anblicken att visa sig vara misslyckad.

Det finns två lärdomar att dra av detta.

  • Om du redan har börjat arbeta med Terraform, använd inget annat. Om en del av din infrastruktur hanteras med Terraform kan du inte längre ändra den manuellt. Annars riskerar du inte bara konstiga Terraform-fel, utan du förnekar också många av fördelarna med IaC eftersom koden inte längre kommer att vara en korrekt representation av din infrastruktur.
  • Om du redan har en viss infrastruktur, använd importkommandot. Om du börjar använda Terraform med befintlig infrastruktur kan du lägga till den i tillståndsfilen med kommandot terraform import. På så sätt kommer Terraform att veta vilken infrastruktur som behöver hanteras. Importkommandot tar två argument. Den första är resursadressen i dina konfigurationsfiler. Syntaxen här är densamma som för resurslänkar: _. (som aws_iam_user.existing_user). Det andra argumentet är ID för resursen som ska importeras. Låt oss säga att resurs-ID:t aws_iam_user är användarnamnet (till exempel yevgeniy.brikman), och resurs-ID:t aws_instance är EC2-server-ID (som i-190e22e5). Hur man importerar en resurs anges vanligtvis i dokumentationen längst ner på dess sida.

    Nedan finns ett importkommando som synkroniserar aws_iam_user-resursen som du lade till i din Terraform-konfiguration tillsammans med IAM-användaren i kapitel 2 (ersätter naturligtvis yevgeniy.brikman med ditt namn):

    $ terraform import aws_iam_user.existing_user yevgeniy.brikman

    Terraform kommer att anropa AWS API för att hitta din IAM-användare och skapa en tillståndsfilassociation mellan den och resursen aws_iam_user.existing_user i din Terraform-konfiguration. Från och med nu, när du kör plankommandot, kommer Terraform att veta att IAM-användaren redan finns och kommer inte att försöka skapa den igen.

    Det är värt att notera att om du redan har många resurser som du vill importera till Terraform kan det vara besvärligt att skriva koden manuellt och importera var och en åt gången. Så det är värt att titta på ett verktyg som Terraforming (http://terraforming.dtan4.net/), som automatiskt kan importera kod och status från ditt AWS-konto.

    Refaktorering kan ha sina fallgropar

    Refaktorering är en vanlig praxis inom programmering där man ändrar kodens interna struktur samtidigt som man lämnar det externa beteendet oförändrat. Detta för att göra koden tydligare, snyggare och lättare att underhålla. Refaktorering är en oumbärlig teknik som bör användas regelbundet. Men när det kommer till Terraform eller något annat IaC-verktyg måste du vara extremt försiktig med vad du menar med det "externa beteendet" för en kodbit, annars kommer oväntade problem att uppstå.

    Till exempel är en vanlig typ av refactoring att ersätta namnen på variabler eller funktioner med mer begripliga. Många IDE:er har inbyggt stöd för refactoring och kan automatiskt byta namn på variabler och funktioner under hela projektet. I allmänna programmeringsspråk är detta en trivial procedur som du kanske inte tänker på, men i Terraform måste du vara extremt försiktig med detta, annars kan du uppleva avbrott.

    Webserver-klustermodulen har till exempel en indatavariabel klusternamn:

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

    Föreställ dig att du började använda den här modulen för att distribuera en mikrotjänst som heter foo. Senare vill du byta namn på din tjänst till bar. Denna förändring kan tyckas trivial, men i verkligheten kan den orsaka tjänsteavbrott.

    Faktum är att webserver-klustermodulen använder variabeln cluster_name i ett antal resurser, inklusive namnparametern för två säkerhetsgrupper och ALB:n:

    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]
    }

    Om du ändrar namnparametern på en resurs kommer Terraform att ta bort den gamla versionen av den resursen och skapa en ny i dess ställe. Men om den resursen är en ALB, mellan att ta bort den och ladda ner en ny version, kommer du inte att ha en mekanism för att omdirigera trafik till din webbserver. På samma sätt, om en säkerhetsgrupp raderas, kommer dina servrar att börja avvisa all nätverkstrafik tills en ny grupp skapas.

    En annan typ av refactoring du kan vara intresserad av är att ändra Terraform ID. Låt oss ta resursen aws_security_group i webserver-klustermodulen som ett exempel:

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

    Identifieraren för denna resurs kallas instans. Föreställ dig att du under refactoring bestämde dig för att ändra det till ett mer förståeligt (enligt din åsikt) namn cluster_instance:

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

    Vad kommer att hända i slutändan? Det stämmer: en störning.

    Terraform associerar varje resurs-ID med molnleverantörs-ID. Till exempel är iam_user associerad med AWS IAM-användar-ID och aws_instance är associerad med AWS EC2-server-ID. Om du ändrar resurs-ID (säg från instans till cluster_instance, som är fallet med aws_security_group), till Terraform kommer det att se ut som om du tagit bort den gamla resursen och lagt till en ny. Om du tillämpar dessa ändringar kommer Terraform att ta bort den gamla säkerhetsgruppen och skapa en ny, medan dina servrar börjar avvisa all nätverkstrafik.

    Här är fyra viktiga lärdomar du bör ta med dig från den här diskussionen.

    • Använd alltid plankommandot. Det kan avslöja alla dessa problem. Granska dess utdata noggrant och var uppmärksam på situationer där Terraform planerar att ta bort resurser som med största sannolikhet inte borde raderas.
    • Skapa innan du tar bort. Om du vill ersätta en resurs, fundera noga på om du behöver skapa en ersättning innan du tar bort originalet. Om svaret är ja, kan create_before_destroy hjälpa till. Samma resultat kan uppnås manuellt genom att utföra två steg: lägg först till en ny resurs i konfigurationen och kör kommandot applicera, och ta sedan bort den gamla resursen från konfigurationen och använd appliceringskommandot igen.
    • Ändring av identifierare kräver ändring av tillstånd. Om du vill ändra ID som är kopplat till en resurs (till exempel byta namn på aws_security_group från instans till cluster_instance) utan att ta bort resursen och skapa en ny version av den, måste du uppdatera Terraform-tillståndsfilen därefter. Gör aldrig detta manuellt - använd istället kommandot terraform state. När du byter namn på identifierare bör du köra kommandot terraform state mv, som har följande syntax:
      terraform state mv <ORIGINAL_REFERENCE> <NEW_REFERENCE>

      ORIGINAL_REFERENCE är ett uttryck som refererar till resursen i dess nuvarande form, och NEW_REFERENCE är dit du vill flytta den. Till exempel, när du byter namn på gruppen aws_security_group från instans till cluster_instance, måste du köra följande kommando:

      $ terraform state mv 
         aws_security_group.instance 
         aws_security_group.cluster_instance

      Detta talar om för Terraform att tillståndet som tidigare var associerat med aws_security_group.instance nu ska associeras med aws_security_group.cluster_instance. Om efter att ha bytt namn och kört det här kommandot terraform plan inte visar några ändringar, så gjorde du allt korrekt.

    • Vissa inställningar kan inte ändras. Parametrarna för många resurser är oföränderliga. Om du försöker ändra dem kommer Terraform att ta bort den gamla resursen och skapa en ny i dess ställe. Varje resurssida kommer vanligtvis att indikera vad som händer när du ändrar en viss inställning, så se till att kontrollera dokumentationen. Använd alltid plankommandot och överväg att använda strategin create_before_destroy.

    Uppskjuten konsekvens överensstämmer... med uppskov

    Vissa molnleverantörers API:er, som AWS, är asynkrona och har fördröjd konsistens. Asynkroni innebär att gränssnittet omedelbart kan returnera ett svar utan att vänta på att den begärda åtgärden ska slutföras. Fördröjd konsistens innebär att ändringar kan ta tid att spridas i hela systemet; medan detta händer kan dina svar vara inkonsekventa och beroende på vilken datakällareplik som svarar på dina API-anrop.

    Föreställ dig till exempel att du gör ett API-anrop till AWS och ber den att skapa en EC2-server. API:et kommer att returnera ett "lyckat" svar (201 Skapad) nästan omedelbart, utan att vänta på att själva servern ska skapas. Om du försöker ansluta till den direkt kommer det nästan säkert att misslyckas eftersom AWS fortfarande initierar resurser eller, alternativt, servern har ännu inte startat. Om du dessutom ringer ett nytt samtal för att få information om den här servern kan du få ett felmeddelande (404 Not Found). Saken är den att informationen om denna EC2-server fortfarande kan spridas över hela AWS innan den blir tillgänglig överallt, du måste vänta några sekunder.

    När du använder ett asynkront API med lat konsistens måste du regelbundet försöka igen med din begäran tills åtgärden slutförs och sprider sig genom systemet. Tyvärr tillhandahåller AWS SDK inga bra verktyg för detta, och Terraform-projektet brukade lida av många buggar 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 andra ord, du skapar en resurs (som ett subnät) och försöker sedan få lite information om den (som ID för det nyskapade subnätet), och Terraform kan inte hitta den. De flesta av dessa buggar (inklusive 6813) har åtgärdats, men de dyker fortfarande upp då och då, speciellt när Terraform lägger till stöd för en ny resurstyp. Detta är irriterande, men orsakar i de flesta fall ingen skada. När du kör terraform applicera igen bör allt fungera, eftersom informationen vid det här laget redan har spridits över hela systemet.

    Detta utdrag presenteras från boken av Evgeniy Brikman "Terraform: infrastruktur på kodnivå".

Källa: will.com

Lägg en kommentar