Terraform pitfalls

Terraform pitfalls
Litte wy in pear falkûlen markearje, ynklusyf dy relatearre oan loops, as útspraken en ynsettechniken, lykas mear algemiene problemen dy't Terraform yn 't algemien beynfloedzje:

  • de parameters telle en foar_elke hawwe beheiningen;
  • beheine nul downtime ynset;
  • sels in goed plan kin mislearje;
  • refactoring kin syn falkûlen hawwe;
  • útstelde gearhing is konsistint ... mei útstel.

De count en for_each parameters hawwe beheiningen

De foarbylden yn dit haadstik meitsje wiidweidich gebrûk fan de count parameter en de for_each útdrukking yn loops en betingst logika. Se prestearje goed, mar se hawwe twa wichtige beheiningen dy't jo moatte bewust wêze fan.

  • Count and for_each kin net ferwize nei gjin boarne útfier fariabelen.
  • count en for_each kinne net brûkt wurde yn module konfiguraasje.

count en for_each kin net ferwize nei alle boarne útfier fariabelen

Stel jo foar dat jo ferskate EC2-tsjinners moatte ynsette en om ien of oare reden wolle jo ASG net brûke. Jo koade kin sa wêze:

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

Litte wy se ien foar ien besjen.

Sûnt de count parameter is ynsteld op in statyske wearde, dizze koade sil wurkje sûnder problemen: as jo rinne it kommando tapasse, it sil meitsje trije EC2 tsjinners. Mar wat as jo ien server wolle ynsette yn elke Availability Zone (AZ) binnen jo hjoeddeistige AWS-regio? Jo kinne jo koade in list mei sônes laden fan 'e aws_availability_zones gegevensboarne en dan troch elk trochrinne en dêr in EC2-tsjinner yn meitsje mei de telleparameter en array-yndeks tagong:

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

Dizze koade sil ek goed wurkje, om't de telleparameter sûnder problemen gegevensboarnen kin ferwize. Mar wat bart der as it oantal tsjinners dat jo moatte oanmeitsje hinget ôf fan de útfier fan guon boarne? Om dit te demonstrearjen is de maklikste manier om de random_integer boarne te brûken, dy't, lykas de namme al fermoeden docht, in willekeurich heul getal werombringt:

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

Dizze koade genereart in willekeurich getal tusken 1 en 3. Litte wy sjen wat der bart as wy besykje de útfier fan dizze boarne te brûken yn 'e telleparameter fan 'e boarne aws_instance:

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

As jo ​​​​terraform-plan op dizze koade útfiere, krije jo de folgjende flater:

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 fereasket dat count en for_elk wurde berekkene yn 'e planningsfaze, foardat alle boarnen wurde oanmakke of wizige. Dit betsjut dat count en for_each kin ferwize nei literals, fariabelen, gegevens boarnen, en sels boarne listen (sa lang as harren lingte kin wurde bepaald op skema tiid), mar net nei berekkene boarne útfier fariabelen.

count en for_each kinne net brûkt wurde yn module konfiguraasje

Eartiids kinne jo ferlet wurde om in telparameter ta te foegjen oan jo modulekonfiguraasje:

module "count_example" {
     source = "../../../../modules/services/webserver-cluster"

     count = 3

     cluster_name = "terraform-up-and-running-example"
     server_port = 8080
     instance_type = "t2.micro"
}

Dizze koade besiket tellen yn in module te brûken om trije kopyen fan 'e webserver-cluster-boarne te meitsjen. Of jo wolle it ferbinen fan in module opsjoneel meitsje, ôfhinklik fan guon Booleaanske betingsten troch de telleparameter op 0 te setten. Dit kin lykje op in ridlike koade, mar jo krije dizze flater by it útfieren fan 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.

Spitigernôch, as fan Terraform 0.12.6, mei help fan count of for_each yn in module boarne wurdt net stipe. Neffens de Terraform 0.12 release notysjes (http://bit.ly/3257bv4), HashiCorp is fan plan om dizze mooglikheid yn 'e takomst ta te foegjen, dus ôfhinklik fan wannear't jo dit boek lêze, kin it al beskikber wêze. Om der wis fan út te finen, lês it Terraform changelog hjir.

Beheinings fan Zero Downtime Deployments

It brûken fan it create_before_destroy-blok yn kombinaasje mei ASG is in geweldige oplossing foar it meitsjen fan nul-downtime-ynset, útsein ien warskôging: regels foar autoskalearring wurde net stipe. Of om krekter te wêzen, dit set de ASG-grutte werom nei min_size op elke ynset, wat in probleem kin wêze as jo autoskaalregels brûke om it oantal tsjinners te ferheegjen.

Bygelyks, de webserver-kluster-module befettet in pear aws_autoscaling_schedule-boarnen, dy't om 9 oere it oantal tsjinners yn it kluster fergruttet fan twa nei tsien. As jo ​​​​bygelyks 11 oere ynsette, sil de nije ASG opstarte mei mar twa servers ynstee fan tsien en bliuwt sa oant 9 oere de oare deis.

Dizze beheining kin op ferskate manieren omseame wurde.

  • Feroarje de werhellingsparameter yn aws_autoscaling_schedule fan 0 9 * * * ("rinne om 9 oere") nei sa'n ding as 0-59 9-17 * * * ("rinne elke minút fan 9 oere oant 5 oere"). As ASG al tsien tsjinners hat, sil it útfieren fan dizze autoskaalregel wer neat feroarje, dat is wat wy wolle. Mar as de ASG pas koartlyn ynset is, sil dizze regel derfoar soargje dat yn in maksimum fan in minút it oantal fan syn servers tsien sil berikke. Dit is net in folslein elegante oanpak, en grutte sprongen fan tsien nei twa servers en werom kinne ek problemen foar brûkers feroarsaakje.
  • Meitsje in oanpast skript dat de AWS API brûkt om it oantal aktive tsjinners yn 'e ASG te bepalen, neam it mei in eksterne gegevensboarne (sjoch "Eksterne gegevensboarne" op side 249), en set de ASG's wish_capacity parameter yn op de wearde weromjûn troch it skript. Op dizze manier sil elke nije ASG-eksimplaar altyd op deselde kapasiteit rinne as de besteande Terraform-koade en makket it dreger om te ûnderhâlden.

Fansels soe Terraform ideaal ynboude stipe hawwe foar nul-downtime-ynset, mar fan maaie 2019 hie it HashiCorp-team gjin plannen om dizze funksjonaliteit ta te foegjen (details - hjir).

It juste plan kin mislearre wurde útfierd

Soms produsearret it plan kommando in perfekt korrekt ynset plan, mar it kommando tapasse jout in flater. Besykje bygelyks de aws_iam_user-boarne ta te foegjen mei deselde namme dy't jo brûkt hawwe foar de IAM-brûker dy't jo earder makke hawwe yn haadstik 2:

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

No, as jo it plankommando útfiere, sil Terraform in skynber ridlik ynsetplan útfiere:

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.

As jo ​​​​it kommando tapasse útfiere, krije jo de folgjende flater:

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

It probleem is fansels dat in IAM-brûker mei dy namme al bestiet. En dit kin barre net allinich foar IAM-brûkers, mar foar hast elke boarne. It is mooglik dat immen dizze boarne hânmjittich makke hat of de kommandorigel brûkt, mar hoe dan ek, oerienkommende ID's liede ta konflikten. D'r binne in protte fariaasjes fan dizze flater dy't faaks nijkommers op Terraform fange troch ferrassing.

It wichtichste punt is dat it kommando terraform plan allinich rekken hâldt mei dy boarnen dy't oantsjutte binne yn it Terraform-steatbestân. As boarnen op in oare manier oanmakke wurde (bygelyks mei de hân troch te klikken yn 'e AWS-konsole), sille se net yn' e steatbestân einigje en dêrom sil Terraform se net yn 'e rekken hâlde by it útfieren fan it plankommando. Dêrtroch sil in plan dat op it earste each goed liket te wêzen mislearre.

Hjirút binne twa lessen te learen.

  • As jo ​​​​al mei Terraform binne begon te wurkjen, brûk dan neat oars. As in diel fan jo ynfrastruktuer wurdt beheard mei Terraform, kinne jo it net mear manuell wizigje. Oars riskearje jo net allinich rare Terraform-flaters, mar jo negearje ek in protte fan 'e foardielen fan IaC, om't de koade net langer in krekte fertsjintwurdiging fan jo ynfrastruktuer sil wêze.
  • As jo ​​al wat ynfrastruktuer hawwe, brûk dan it ymportkommando. As jo ​​Terraform begjinne te brûken mei besteande ynfrastruktuer, kinne jo it tafoegje oan it steatbestân mei it ymportkommando terraform. Op dizze manier wit Terraform hokker ynfrastruktuer beheard wurde moat. It ymportkommando nimt twa arguminten. De earste is it boarneadres yn jo konfiguraasjebestannen. De syntaksis hjir is itselde as foar boarne keppelings: _. (lykas aws_iam_user.existing_user). It twadde argumint is de ID fan de te ymportearjen boarne. Litte wy sizze dat de boarne ID aws_iam_user de brûkersnamme is (bygelyks yevgeniy.brikman), en de boarne ID aws_instance is de EC2-tsjinner ID (lykas i-190e22e5). Hoe't jo in boarne ymportearje, wurdt normaal oanjûn yn 'e dokumintaasje ûnderoan de side.

    Hjirûnder is in ymportkommando dat de aws_iam_user-boarne syngronisearret dy't jo tafoege hawwe oan jo Terraform-konfiguraasje tegearre mei de IAM-brûker yn haadstik 2 (ferfange jo namme foar yevgeniy.brikman, fansels):

    $ terraform import aws_iam_user.existing_user yevgeniy.brikman

    Terraform sil de AWS API neame om jo IAM-brûker te finen en in steatbestânferiening te meitsjen tusken har en de boarne aws_iam_user.existing_user yn jo Terraform-konfiguraasje. Fanôf no, as jo it plankommando útfiere, sil Terraform witte dat de IAM-brûker al bestiet en sil net besykje it opnij te meitsjen.

    It is de muoite wurdich op te merken dat as jo al in protte boarnen hawwe dy't jo wolle ymportearje yn Terraform, it manuell skriuwen fan de koade en it ymportearjen fan elk ien tagelyk in gedoe wêze kin. Dat it is de muoite wurdich om te sjen nei in ark lykas Terraforming (http://terraforming.dtan4.net/), dat automatysk koade en steat kin ymportearje fan jo AWS-akkount.

    Refactoring kin syn falkûlen hawwe

    Refactoring is in gewoane praktyk yn programmearring wêr't jo de ynterne struktuer fan 'e koade feroarje, wylst it eksterne gedrach net feroaret. Dit is om de koade dúdliker, netter te meitsjen en makliker te ûnderhâlden. Refactoring is in ûnmisbere technyk dy't regelmjittich brûkt wurde moat. Mar as it giet om Terraform of in oar IaC-ark, moatte jo ekstreem foarsichtich wêze oer wat jo bedoele mei it "eksterne gedrach" fan in stik koade, oars ûntsteane ûnferwachte problemen.

    Bygelyks, in gewoane type refactoring is it ferfangen fan de nammen fan fariabelen of funksjes mei mear begryplike. In protte IDE's hawwe ynboude stipe foar refactoring en kinne fariabelen en funksjes automatysk omneame troch it heule projekt. Yn programmeartalen foar algemiene doelen is dit in triviale proseduere dêr't jo miskien net oan tinke, mar yn Terraform moatte jo hjir ekstreem foarsichtich mei wêze, oars kinne jo ûnderbrekkings ûnderfine.

    Bygelyks, de webserver-kluster-module hat in ynfierfariabele cluster_name:

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

    Stel jo foar dat jo dizze module begongen te brûken om in mikrotsjinst mei de namme foo yn te setten. Letter wolle jo jo tsjinst omneame nei bar. Dizze feroaring kin triviaal lykje, mar yn 'e realiteit kin it tsjinstferliening feroarsaakje.

    It feit is dat de webserver-klustermodule de fariabele cluster_name brûkt yn in oantal boarnen, ynklusyf de nammeparameter fan twa feiligensgroepen en de 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]
    }

    As jo ​​feroarje de namme parameter op in boarne, Terraform sil wiskje de âlde ferzje fan dy boarne en meitsje in nije ien yn syn plak. Mar as dy boarne in ALB is, tusken it wiskjen en it downloaden fan in nije ferzje, sille jo gjin meganisme hawwe om ferkear nei jo webtsjinner troch te lieden. Likemin, as in befeiligingsgroep wurdt wiske, sille jo servers elk netwurkferkear begjinne te fersmiten oant in nije groep wurdt oanmakke.

    In oar type refactoring wêryn jo miskien ynteressearre binne is it feroarjen fan de Terraform ID. Litte wy de boarne aws_security_group yn 'e webserver-klustermodule as foarbyld nimme:

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

    De identifier fan dizze boarne hjit instance. Stel jo foar dat jo by refactoring besletten hawwe it te feroarjen nei in mear begryplike (yn jo miening) namme cluster_instance:

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

    Wat sil der op it lêst barre? Dat kloppet: in steuring.

    Terraform assosjearret elke boarne-ID mei de wolkprovider-ID. Bygelyks, iam_user is assosjearre mei de AWS IAM brûker ID, en aws_instance is assosjearre mei de AWS EC2 server ID. As jo ​​feroarje de boarne ID (sizze fan eksimplaar nei cluster_instance, sa't it gefal is mei aws_security_group), to Terraform sil ferskine as hawwe jo wiske de âlde boarne en tafoege in nije. As jo ​​dizze wizigingen tapasse, sil Terraform de âlde befeiligingsgroep wiskje en in nije meitsje, wylst jo servers elk netwurkferkear begjinne te fersmiten.

    Hjir binne fjouwer wichtige lessen dy't jo moatte nimme fuort út dizze diskusje.

    • Brûk altyd it plan kommando. It kin al dizze snags iepenbierje. Kontrolearje har útfier foarsichtich en jouwe oandacht oan situaasjes wêr't Terraform fan plan is boarnen te wiskjen dy't nei alle gedachten net wiske wurde moatte.
    • Meitsje foardat jo wiskje. As jo ​​​​in boarne ferfange wolle, tink dan goed nei oft jo in ferfanger moatte meitsje foardat jo it orizjineel wiskje. As it antwurd ja is, kin create_before_destroy helpe. Itselde resultaat kin mei de hân berikt wurde troch twa stappen út te fieren: foegje earst in nije boarne ta oan 'e konfiguraasje en fier it kommando tapasse, en ferwiderje dan de âlde boarne út 'e konfiguraasje en brûk it kommando tapasse wer.
    • It feroarjen fan identifiers fereasket feroaring fan steat. As jo ​​de ID wizigje wolle dy't ferbûn is mei in boarne (bygelyks, omneame aws_security_group fan eksimplaar nei cluster_instance) sûnder de boarne te wiskjen en in nije ferzje dêrfan te meitsjen, moatte jo de Terraform-statusbestân dêrmei bywurkje. Doch dit noait mei de hân - brûk ynstee it kommando terraform state. By it omneamen fan identifiers moatte jo it kommando terraform state mv útfiere, dat de folgjende syntaksis hat:
      terraform state mv <ORIGINAL_REFERENCE> <NEW_REFERENCE>

      ORIGINAL_REFERENCE is in útdrukking dy't ferwiist nei de boarne yn syn hjoeddeistige foarm, en NEW_REFERENCE is wêr't jo it ferpleatse wolle. Bygelyks, as jo de aws_security_group-groep omneame fan eksimplaar nei cluster_instance, moatte jo it folgjende kommando útfiere:

      $ terraform state mv 
         aws_security_group.instance 
         aws_security_group.cluster_instance

      Dit fertelt Terraform dat de steat dy't earder ferbûn wie mei aws_security_group.instance, no moat wurde ferbûn mei aws_security_group.cluster_instance. As nei it omneamen en útfieren fan dit kommando terraform plan gjin feroarings toant, dan hawwe jo alles goed dien.

    • Guon ynstellings kinne net feroare wurde. De parameters fan in protte boarnen binne net te feroarjen. As jo ​​besykje se te feroarjen, sil Terraform de âlde boarne wiskje en in nije op syn plak meitsje. Elke boarne side sil normaal oanjaan wat der bart as jo in bepaalde ynstelling feroarje, dus wês wis dat jo de dokumintaasje kontrolearje. Brûk altyd it plankommando en beskôgje it brûken fan de strategy create_before_destroy.

    Utstelde konsistinsje is konsekwint ... mei útstel

    Guon API's fan wolkproviders, lykas AWS, binne asynchrone en hawwe konsistinsje fertrage. Asynchrony betsjut dat de ynterface fuortendaliks in antwurd kin weromjaan sûnder te wachtsjen op 'e frege aksje om te foltôgjen. Fertrage konsistinsje betsjut dat feroarings tiid nimme kinne om troch it systeem te propagearjen; wylst dit bart, kinne jo antwurden inkonsistint wêze en ôfhinklik fan hokker gegevensboarne replika reagearret op jo API-oproppen.

    Stel jo bygelyks foar dat jo in API-oprop meitsje nei AWS dy't it freget om in EC2-tsjinner te meitsjen. De API sil hast direkt in "suksesfol" antwurd (201 Oanmakke) weromjaan, sûnder te wachtsjen op de tsjinner sels om te meitsjen. As jo ​​besykje der direkt mei te ferbinen, sil it hast wis mislearje, om't AWS op dat stuit noch boarnen initialisearret of, alternatyf, de tsjinner is noch net opstart. Boppedat, as jo in oare oprop meitsje om ynformaasje oer dizze tsjinner te krijen, kinne jo miskien in flater krije (404 net fûn). It ding is dat de ynformaasje oer dizze EC2-tsjinner noch troch AWS kin wurde ferspraat foardat it oeral beskikber wurdt, jo moatte in pear sekonden wachtsje.

    Elke kear as jo in asynchrone API brûke mei luie konsistinsje, moatte jo jo fersyk periodyk opnij besykje oant de aksje foltôget en troch it systeem propagearret. Spitigernôch leveret de AWS SDK hjirfoar gjin goede ark, en it Terraform-projekt hie eartiids lêst fan in protte bugs lykas 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

    Mei oare wurden, jo meitsje in boarne (lykas in subnet) en besykje dan wat ynformaasje oer te krijen (lykas de ID fan it nij oanmakke subnet), en Terraform kin it net fine. De measte fan dizze bugs (ynklusyf 6813) binne reparearre, mar se ferskine noch altyd fan tiid ta tiid, foaral as Terraform stipe foeget foar in nij type boarne. Dit is ferfelend, mar yn 'e measte gefallen feroarsaket gjin skea. As jo ​​​​terraform opnij útfiere, moat alles wurkje, om't de ynformaasje op dit stuit al ferspraat is oer it systeem.

    Dit úttreksel wurdt presintearre út it boek fan Evgeniy Brikman "Terraform: ynfrastruktuer op koadenivo".

Boarne: www.habr.com

Add a comment