Terraform-faloj

Terraform-faloj
Ni reliefigu kelkajn malfacilaĵojn, inkluzive de tiuj rilataj al bukloj, se deklaroj kaj deplojteknikoj, same kiel pli ĝeneralajn problemojn, kiuj influas Terraform ĝenerale:

  • la count kaj for_each parametroj havas limojn;
  • limigi nul malfunkciajn deplojojn;
  • eĉ bona plano povas malsukcesi;
  • refactoring povas havi siajn fakojn;
  • prokrastita kohereco kongruas... kun prokrasto.

La count kaj for_each parametroj havas limigojn

La ekzemploj en ĉi tiu ĉapitro faras ampleksan uzon de la count-parametro kaj la for_each-esprimo en bukloj kaj kondiĉa logiko. Ili funkcias bone, sed ili havas du gravajn limojn, pri kiuj vi devas esti konscia.

  • Count kaj for_each ne povas referenci ajnajn rimedajn eligvariablojn.
  • count kaj for_each ne povas esti uzataj en modula agordo.

count kaj for_each ne povas referenci ajnajn rimedajn eligvariablojn

Imagu, ke vi devas disfaldi plurajn EC2-servilojn kaj ial vi ne volas uzi ASG. Via kodo povus esti tia:

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

Ni rigardu ilin unu post la alia.

Ĉar la count-parametro estas agordita al statika valoro, ĉi tiu kodo funkcios senprobleme: kiam vi rulas la aplikan komandon, ĝi kreos tri EC2-servilojn. Sed kio se vi volus disfaldi unu servilon en ĉiu Havebleca Zono (AZ) ene de via nuna AWS-regiono? Vi povas igi vian kodon ŝargi liston de zonoj de la aws_availability_zones datumfonto kaj poste trapasi ĉiun kaj krei EC2-servilon en ĝi uzante la kalkul-parametron kaj tabelindeksan aliron:

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

Ĉi tiu kodo ankaŭ funkcios bone, ĉar la kalkul-parametro povas referenci datumfontojn sen problemoj. Sed kio okazas se la nombro da serviloj, kiujn vi bezonas krei, dependas de la eligo de iu rimedo? Por pruvi tion, la plej facila maniero estas uzi la random_enteger-rimedon, kiu, kiel la nomo sugestas, resendas hazardan entjeron:

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

Ĉi tiu kodo generas hazardan nombron inter 1 kaj 3. Ni vidu, kio okazas se ni provas uzi la eligon de ĉi tiu rimedo en la count-parametro de la aws_instance rimedo:

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

Se vi rulas terraform planon sur ĉi tiu kodo, vi ricevos la jenan eraron:

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 postulas ke count kaj for_each estu kalkulitaj dum la planadfazo, antaŭ ol iuj rimedoj estas kreitaj aŭ modifitaj. Ĉi tio signifas, ke count kaj for_each povas rilati al literaloj, variabloj, datumfontoj, kaj eĉ rimedlistoj (kondiĉe ke ilia longo povas esti determinita je planado), sed ne al komputitaj rimedproduktaj variabloj.

count kaj for_each ne povas esti uzataj en modula agordo

Iam vi povus esti tentata aldoni kalkul-parametron al via modula agordo:

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

     count = 3

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

Ĉi tiu kodo provas uzi kalkulon ene de modulo por krei tri kopiojn de la retservilo-grupo-rimedo. Aŭ vi eble volas fari la konekton de modulo laŭvola surbaze de iu Bulea kondiĉo agordante ĝian kalkulparametron al 0. Ĉi tio povus aspekti kiel racia kodo, sed vi ricevos ĉi tiun eraron kiam vi rulas teraforman planon:

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.

Bedaŭrinde, ekde Terraform 0.12.6, uzi count aŭ for_each en modula rimedo ne estas subtenata. Laŭ la eldonnotoj de Terraform 0.12 (http://bit.ly/3257bv4), HashiCorp planas aldoni ĉi tiun kapablon estonte, do depende de kiam vi legos ĉi tiun libron, ĝi eble jam estos disponebla. Por ekscii certe, legu la ŝanĝprotokolon de Terraform ĉi tie.

Limigoj de Nulaj Malfunkciaj Deplojoj

Uzi la create_before_destroy-blokon en kombinaĵo kun ASG estas bonega solvo por krei nul-malfunkciajn deplojojn, krom unu averto: aŭtoskalaj reguloj ne estas subtenataj. Aŭ por esti pli preciza, ĉi tio restarigas la ASG-grandecon al min_size ĉe ĉiu deplojo, kio povus esti problemo se vi uzus aŭtoskalaj reguloj por pliigi la nombron da serviloj kurantaj.

Ekzemple, la retservilo-grupo-modulo enhavas paron da aws_autoscaling_schedule rimedoj, kiu je la 9-a matene pliigas la nombron da serviloj en la areto de du ĝis dek. Se vi deplojiĝas je, ekzemple, 11 a.m., la nova ASG ekfunkciiĝos per nur du serviloj anstataŭ dek kaj restos tiel ĝis la 9a a.m. de la sekva tago.

Ĉi tiu limigo povas esti evitita en pluraj manieroj.

  • Ŝanĝu la ripetiĝan parametron en aws_autoscaling_schedule de 0 9 * * * ("kuru je 9 am") al io kiel 0-59 9-17 * * * ("kuru ĉiun minuton de 9 am ĝis 5 pm"). Se ASG jam havas dek servilojn, ruli ĉi tiun aŭtomatan regulon denove nenion ŝanĝos, kio estas kion ni volas. Sed se la ASG nur lastatempe estis deplojita, ĉi tiu regulo certigos, ke en maksimume unu minuto la nombro de ĝiaj serviloj atingos dek. Ĉi tio ne estas tute eleganta aliro, kaj grandaj saltoj de dek al du serviloj kaj reen ankaŭ povas kaŭzi problemojn por uzantoj.
  • Kreu kutiman skripton, kiu uzas la AWS-API por determini la nombron da aktivaj serviloj en la ASG, voku ĝin per ekstera datumfonto (vidu "Eksteran Datumfonton" sur paĝo 249), kaj agordu la parametron wish_capacity de la ASG al la valoro redonita de la skripto. Tiel, ĉiu nova ASG-instanco ĉiam funkcios kun la sama kapablo kiel la ekzistanta Terraform-kodo kaj malfaciligas ĝin konservi.

Kompreneble, Terraform ideale havus enkonstruitan subtenon por nulmalfunkciaj deplojoj, sed ekde majo 2019, la HashiCorp-teamo ne planis aldoni ĉi tiun funkcion (detaloj - ĉi tie).

La ĝusta plano povas esti malsukcese efektivigita

Kelkfoje la plan-komando produktas tute ĝustan deplojplanon, sed la aplika komando resendas eraron. Provu, ekzemple, aldoni la aws_iam_user-rimedon kun la sama nomo, kiun vi uzis por la IAM-uzanto, kiun vi kreis pli frue en Ĉapitro 2:

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

Nun, se vi rulas la planan komandon, Terraform eligos ŝajne akcepteblan deplojplanon:

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.

Se vi rulas la aplikan komandon, vi ricevos la jenan eraron:

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

La problemo, kompreneble, estas ke IAM-uzanto kun tiu nomo jam ekzistas. Kaj ĉi tio povas okazi ne nur al IAM-uzantoj, sed al preskaŭ ajna rimedo. Eblas, ke iu kreis ĉi tiun rimedon permane aŭ uzante la komandlinion, sed ĉiuokaze, kongruaj identigiloj kondukas al konfliktoj. Estas multaj variaĵoj de ĉi tiu eraro, kiuj ofte surprizas novulojn al Terraform.

La ŝlosila punkto estas, ke la komando de terraform-plano nur konsideras tiujn rimedojn, kiuj estas specifitaj en la ŝtatdosiero de Terraform. Se rimedoj estas kreitaj alimaniere (ekzemple, permane alklakante en la AWS-konzolo), ili ne finiĝos en la ŝtatdosiero kaj tial Terraform ne konsideros ilin kiam ekzekuto de la plana komando. Kiel rezulto, plano, kiu ŝajnas ĝusta unuavide, estos malsukcesa.

Estas du lecionoj por lerni de ĉi tio.

  • Se vi jam komencis labori kun Terraform, ne uzu ion alian. Se parto de via infrastrukturo estas administrita per Terraform, vi ne plu povas modifi ĝin permane. Alie, vi ne nur riskas strangajn erarojn de Terraform, sed vi ankaŭ neas multajn el la avantaĝoj de IaC ĉar la kodo ne plu estos preciza reprezentado de via infrastrukturo.
  • Se vi jam havas iun infrastrukturon, uzu la importkomandon. Se vi komencas uzi Terraform kun ekzistanta infrastrukturo, vi povas aldoni ĝin al la ŝtatdosiero per la komando de importado de terraform. Tiel Terraform scios, kian infrastrukturon oni devas administri. La importkomando prenas du argumentojn. La unua estas la rimedadreso en viaj agordaj dosieroj. La sintakso ĉi tie estas la sama kiel por rimedligoj: _. (kiel aws_iam_user.existing_user). La dua argumento estas la ID de la importota rimedo. Ni diru, ke la rimeda ID aws_iam_user estas la uzantnomo (ekzemple, yevgeniy.brikman), kaj la rimeda ID aws_instance estas la EC2-servila ID (kiel i-190e22e5). Kiel importi rimedon estas kutime indikita en la dokumentaro ĉe la malsupro de ĝia paĝo.

    Malsupre estas importa komando, kiu sinkronigas la rimedon aws_iam_user, kiun vi aldonis al via Terraform-agordo kune kun la IAM-uzanto en Ĉapitro 2 (anstataŭigante vian nomon por yevgeniy.brikman, kompreneble):

    $ terraform import aws_iam_user.existing_user yevgeniy.brikman

    Terraform vokos la AWS API por trovi vian IAM-uzanto kaj krei ŝtatdosier-asocion inter ĝi kaj la rimedo aws_iam_user.existing_user en via Terraform-agordo. De nun, kiam vi rulas la planan komandon, Terraform scios, ke la IAM-uzanto jam ekzistas kaj ne provos krei ĝin denove.

    Indas noti, ke se vi jam havas multajn rimedojn, kiujn vi volas importi en Terraform, permane skribi la kodon kaj importi ĉiun unuope povas esti ĝenaĵo. Do indas serĉi ilon kiel Terraforming (http://terraforming.dtan4.net/), kiu povas aŭtomate importi kodon kaj ŝtaton de via AWS-konto.

    Refaktorado povas havi siajn faŭltojn

    Refaktorado estas ofta praktiko en programado kie vi ŝanĝas la internan strukturon de la kodo lasante la eksteran konduton senŝanĝa. Ĉi tio estas por fari la kodon pli klara, pli neta kaj pli facile konservebla. Refaktorado estas nemalhavebla tekniko, kiu devas esti uzata regule. Sed kiam temas pri Terraform aŭ iu ajn alia IaC-ilo, vi devas esti ege singarda pri tio, kion vi volas diri per la "ekstera konduto" de peco de kodo, alie neatenditaj problemoj aperos.

    Ekzemple, ofta speco de refaktorado estas anstataŭigi la nomojn de variabloj aŭ funkcioj kun pli kompreneblaj. Multaj IDEoj havas enkonstruitan subtenon por refactoring kaj povas aŭtomate renomi variablojn kaj funkciojn tra la projekto. En ĝeneraluzeblaj programlingvoj, tio estas banala proceduro pri kiu vi eble ne pensas, sed en Terraform vi devas esti ege singarda pri tio, alie vi povas sperti malfunkciojn.

    Ekzemple, la retservilo-grupo-modulo havas enigan variablon cluster_name:

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

    Imagu, ke vi komencis uzi ĉi tiun modulon por disfaldi mikroservon nomatan foo. Poste, vi volas renomi vian servon al bar. Ĉi tiu ŝanĝo povas ŝajni bagatela, sed fakte ĝi povas kaŭzi servajn interrompojn.

    La fakto estas, ke la retservilo-grupo-modulo uzas la variablon cluster_name en kelkaj rimedoj, inkluzive de la nomparametro de du sekurecaj grupoj kaj la 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]
    }

    Se vi ŝanĝas la nomparametron sur rimedo, Terraform forigos la malnovan version de tiu rimedo kaj kreos novan anstataŭe. Sed se tiu rimedo estas ALB, inter forigi ĝin kaj elŝuti novan version, vi ne havos mekanismon por redirekti trafikon al via retservilo. Same, se sekureca grupo estas forigita, viaj serviloj komencos malakcepti ajnan retan trafikon ĝis nova grupo estas kreita.

    Alia speco de refaktorado, pri kiu vi povus interesiĝi, estas ŝanĝi la Terraform-ID. Ni prenu la rimedon aws_security_group en la retservilo-grupo-modulo kiel ekzemplon:

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

    La identigilo de ĉi tiu rimedo nomiĝas ekzemplero. Imagu, ke dum refaktorado vi decidis ŝanĝi ĝin al pli komprenebla (laŭ via) nomo cluster_instance:

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

    Kio okazos finfine? Ĝuste: interrompo.

    Terraform asocias ĉiun rimedan ID kun la nuba provizanto ID. Ekzemple, iam_user estas asociita kun la AWS IAM uzantidentigilo, kaj aws_instance estas asociita kun la AWS EC2-servila ID. Se vi ŝanĝas la rimedan ID (diru de ekzemplero al cluster_instance, kiel estas la kazo kun aws_security_group), al Terraform ĝi aperos kvazaŭ vi forigis la malnovan rimedon kaj aldonis novan. Se vi aplikas ĉi tiujn ŝanĝojn, Terraform forigos la malnovan sekurecan grupon kaj kreos novan, dum viaj serviloj komencas malakcepti ajnan retan trafikon.

    Jen kvar ĉefaj lecionoj, kiujn vi devus preni el ĉi tiu diskuto.

    • Ĉiam uzu la planan komandon. Ĝi povas malkaŝi ĉiujn ĉi tiujn malbonojn. Revizu ĝian eliron zorge kaj atentu situaciojn kie Terraform planas forigi rimedojn, kiuj plej verŝajne ne devus esti forigitaj.
    • Kreu antaŭ ol vi forigi. Se vi volas anstataŭigi rimedon, pripensu zorge ĉu vi devas krei anstataŭaĵon antaŭ ol forigi la originalon. Se la respondo estas jes, create_before_destroy povas helpi. La sama rezulto povas esti atingita permane per du paŝoj: unue aldonu novan rimedon al la agordo kaj rulu la aplikan komandon, kaj poste forigu la malnovan rimedon de la agordo kaj uzu la apliki komandon denove.
    • Ŝanĝi identigilojn postulas ŝanĝi ŝtaton. Se vi volas ŝanĝi la ID asociitan kun rimedo (ekzemple, renomi aws_security_group de ekzemplero al cluster_instance) sen forigi la rimedon kaj krei novan version de ĝi, vi devas ĝisdatigi la ŝtatdosieron Terraform laŭe. Neniam faru tion permane - anstataŭe uzu la teraform-ŝtatan komandon. Kiam vi renomas identigilojn, vi devus ruli la komandon mv de terraform state, kiu havas la jenan sintakson:
      terraform state mv <ORIGINAL_REFERENCE> <NEW_REFERENCE>

      ORIGINAL_REFERENCE estas esprimo kiu rilatas al la rimedo en ĝia nuna formo, kaj NEW_REFERENCE estas kie vi volas movi ĝin. Ekzemple, kiam vi renomas la grupon aws_security_group de ekzemplo al cluster_instance, vi devas ruli la sekvan komandon:

      $ terraform state mv 
         aws_security_group.instance 
         aws_security_group.cluster_instance

      Ĉi tio diras al Terraform, ke la stato, kiu antaŭe estis asociita kun aws_security_group.instance, nun devus esti asociita kun aws_security_group.cluster_instance. Se post renomado kaj rulado de ĉi tiu komanda teraforma plano ne montras ŝanĝojn, tiam vi faris ĉion ĝuste.

    • Iuj agordoj ne povas esti ŝanĝitaj. La parametroj de multaj rimedoj estas neŝanĝeblaj. Se vi provas ŝanĝi ilin, Terraform forigos la malnovan rimedon kaj kreos novan anstataŭe. Ĉiu rimeda paĝo kutime indikos kio okazas kiam vi ŝanĝas apartan agordon, do nepre kontrolu la dokumentadon. Ĉiam uzu la planan komandon kaj konsideru uzi la strategion create_before_destroy.

    Prokrastita konsistenco kongruas... kun prokrasto

    Iuj API-oj de nubaj provizantoj, kiel AWS, estas nesinkronaj kaj prokrastis konsistencon. Nesinkronio signifas, ke la interfaco povas tuj resendi respondon sen atendi ke la petita ago finiĝos. Prokrastita konsistenco signifas ke ŝanĝoj povas daŭri tempon por disvastigi ĉie en la sistemo; dum tio okazas, viaj respondoj povas esti malkonsekvencaj kaj dependas de kiu datumfonto-repliko respondas al viaj API-vokoj.

    Imagu, ekzemple, ke vi faras API-vokon al AWS petante ĝin krei EC2-servilon. La API resendos "sukcesan" respondon (201 Kreita) preskaŭ tuj, sen atendi ke la servilo mem estos kreita. Se vi provos konektiĝi al ĝi tuj, ĝi preskaŭ certe malsukcesos ĉar tiutempe AWS ankoraŭ pravalorigas rimedojn aŭ, alternative, la servilo ankoraŭ ne startis. Krome, se vi faras alian vokon por ricevi informojn pri ĉi tiu servilo, vi eble ricevos eraron (404 Ne Trovita). La afero estas, ke la informoj pri ĉi tiu EC2-servilo ankoraŭ povas esti disvastigita tra AWS antaŭ ol ĝi estos disponebla ĉie, vi devos atendi kelkajn sekundojn.

    Kiam ajn vi uzas nesinkronan API kun maldiligenta konsistenco, vi devas periode reprovi vian peton ĝis la ago finiĝas kaj disvastiĝas tra la sistemo. Bedaŭrinde, la AWS SDK ne provizas iujn bonajn ilojn por ĉi tio, kaj la projekto Terraform kutimis suferis multajn cimojn kiel 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

    Alivorte, vi kreas rimedon (kiel subreto) kaj poste provas akiri iom da informoj pri ĝi (kiel la ID de la nove kreita subreto), kaj Terraform ne povas trovi ĝin. Plej multaj el ĉi tiuj cimoj (inkluzive de 6813) estis korektitaj, sed ili ankoraŭ aperas de tempo al tempo, precipe kiam Terraform aldonas subtenon por nova rimeda tipo. Ĉi tio ĝenas, sed plejofte ne kaŭzas damaĝon. Kiam vi rulas terraform apply denove, ĉio devus funkcii, ĉar ĝis tiu ĉi tempo la informoj jam disvastiĝos tra la sistemo.

    Ĉi tiu eltiraĵo estas prezentita el la libro de Evgeniy Brikman "Terraform: infrastrukturo sur la kodnivelo".

fonto: www.habr.com

Aldoni komenton