Terraform buktatói

Terraform buktatói
Ki fogjuk emelni a néhány rejtett veszélyt, beleértve azokat is, amelyek a ciklusokkal, if kifejezésekkel és telepítési módszerekkel, valamint a Terraform egészével kapcsolatos általános problémákkal foglalkoznak:

  • a count és for_each paramétereknek korlátozásai vannak;
  • a nullás leállási idővel rendelkező telepítések korlátai;
  • még egy jó terv is kudarcot vallhat;
  • a refaktoringnak is lehetnek csapdái;
  • a késleltetett konzisztencia... egybeesik a késlekedéssel.

A count és for_each paramétereknek korlátozásai vannak

A fejezet példáiban a count paramétert és a for_each kifejezést aktívan használják ciklusokban és feltételes logikában. Jól teljesítenek, de van két fontos korlátozás, amiről tudnod kell.

  • A count és for_each nem hivatkozhat semmilyen kimeneti változóra.
  • a count és for_each nem használható a modul konfigurációjában.

A count és for_each nem hivatkozhat semmilyen kimeneti változóra

Képzeld el, hogy több EC2 szervert kell telepítened, és valamilyen okból nem szeretnéd használni az ASG-t. A kódod így nézhet ki:

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

Vizsgáljuk meg őket egyesével.

Mivel a count paraméter statikus értéket kapott, a kód problémamentesen fog működni: amikor végrehajtod az apply parancsot, három EC2 szervert fog létrehozni. De mi van, ha azt szeretnéd, hogy egy-egy szerver minden rendelkezésre állási zónában (Availability Zone vagy AZ) legyen az aktuális AWS régión belül? Megteheted, hogy a kódod betölti a zónák listáját az aws_availability_zones adatforrásból, majd 'ciklusosan' végigsétál minden zónán, és létrehoz egy EC2 szervert a count paraméter és a tömb alapján:

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

Ez a kód is tökéletesen fog működni, mivel a count paraméter problémamentesen hivatkozhat az adatforrásokra. De mi történik, ha a létrehozandó szerverek száma egy erőforrás kimenetétől függ? Ennek bemutatására a legkönnyebb a random_integer erőforrást venni, amely, ahogy a neve is sugallja, egy véletlenszerű egész számot ad vissza:

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

Ez a kód véletlenszerű számot generál 1 és 3 között. Nézzük meg, mi történik, ha megpróbáljuk használni a random_integer.num_instances.result kimenetét az aws_instance erőforrás count paraméterében:

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

Ha ezt a kódot végrehajtjuk terraform plan segítségével, a következő hiba fog megjelenni:

Hiba: Érvénytelen count argumentum

   a main.tf 30. sorában, az "aws_instance" "example_3" erőforrásban:
   30: count = random_integer.num_instances.result

A "count" értéke olyan erőforrás-atribútumoktól függ, amelyeket nem lehet meghatározni az alkalmazásig, így a Terraform nem tudja megjósolni, hány példányt hoz létre. Ennek megkerülése érdekében használja a -target argumentumot, hogy először csak a count-ra hivatkozó erőforrásokat alkalmazza.

A Terraform megköveteli, hogy a count és for_each a tervezési szakaszban számítódjanak ki, mielőtt bármilyen erőforrást létrehoznának vagy módosítanának. Ez azt jelenti, hogy a count és for_each hivatkozhat literálokra, változókra, adatforrásokra és akár erőforráslistákra is (feltéve, hogy a hosszúságuk a tervezési szakaszban meghatározható), de nem hivatkozhat számított kimeneti változókra.

A count és for_each nem használható a modul konfigurációjában

Előfordulhat, hogy valamikor megkísérlik a count paramétert hozzáadni a modul konfigurációkhoz:

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

     count = 3

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

Ez a kód megpróbálja használni a count-ot egy modulon belül, hogy három példányt hozzon létre a webserver-cluster erőforrásból. Vagy lehet, hogy egy logikai feltétel alapján szeretné opcionalizálni a modul csatlakoztatását azzal, hogy a count paraméterének értékét 0-ra állítja. Ez a kód teljesen értelmesnek tűnik, azonban a terraform plan végrehajtásakor a következő hibát fogja kapni:

Hiba: Fenntartott argumentum név a modul blokkban

   a main.tf 13. sorában, a "count_example" modulban:
   13: count = 3

A "count" név fenntartva van a Terraform jövőbeli verzióiban való használatra.

Sajnos a Terraform 0.12.6 megjelenésekor a count vagy for_each használata a modul erőforrásban nem támogatott. A Terraform 0.12 kiadási megjegyzései szerint (http:\/\/bit.ly\/3257bv4) a HashiCorp tervezi, hogy ezt a lehetőséget a jövőben hozzáadja, így attól függően, mikor olvassa ezt a könyvet, már elérhető lehet. A pontos megismeréshez olvassa el a Terraform changelogját itt.

Zero downtime deployment korlátozások

A create_before_destroy block combined with ASG is an excellent solution for organizing zero-downtime deployments, with one caveat: auto-scaling rules are not supported in this case. To be more precise, it resets the ASG size back to min_size at each deployment, which can be problematic if you’ve used auto-scaling rules to increase the number of running servers.

For instance, the webserver-cluster module contains a couple of aws_autoscaling_schedule resources that increase the number of servers in the cluster from two to ten at 9 AM. If you deploy, say, at 11 AM, the new ASG group will load with only two servers instead of ten and will remain in that state until 9 AM the next day.

This limitation can be circumvented in several ways.

  • Change the recurrence parameter in aws_autoscaling_schedule from 0 9 * * * ("run at 9 AM") to something like 0-59 9-17 * * * ("run every minute from 9 AM to 5 PM"). If there are already ten servers in the ASG, re-executing this auto-scaling rule will not change anything, which is what we want. But if the ASG group is deployed recently, this rule ensures that within a minute at most, the number of its servers will reach ten. This isn’t a particularly elegant approach, and significant jumps from ten to two servers and back can also cause issues for users.
  • Create a custom script that uses the AWS API to determine the number of active servers in the ASG, call it using an external data source (see the "External data source" section on p. 249), and assign the desired_capacity parameter of the ASG group the value returned by this script. This way, each new ASG instance will always start with the same capacity, complicating its maintenance.

Certainly, ideally Terraform should have built-in support for zero-downtime deployments, but as of May 2019, the HashiCorp team did not plan to add this functionality (details here).

A correct plan may be poorly implemented

Néha, amikor a plan parancsot hajtjuk végre, a kimenet egy teljesen helyes telepítési terv, azonban az apply parancs hibaüzenetet ad vissza. Próbálja meg például hozzáadni az aws_iam_user erőforrást ugyanazzal a névvel, amelyet korábban a 2. fejezetben a létrehozott IAM-felhasználóhoz használt:

resource "aws_iam_user" "existing_user" {
   # Itt írja be a már létező IAM-felhasználó nevét,
   # hogy gyakorolhassa a terraform import parancs használatát
   name = "yevgeniy.brikman"
}

Most, ha végrehajtja a plan parancsot, a Terraform első pillantásra egy teljesen ésszerű telepítési tervet fog kiadni:

A Terraform a következő műveleteket fogja végrehajtani:

   # aws_iam_user.existing_user létrehozásra kerül
   + resource "aws_iam_user" "existing_user" {
         + arn                  = (a végrehajtás után ismert)
         + force_destroy   = false
         + id                    = (a végrehajtás után ismert)
         + name               = "yevgeniy.brikman"
         + path                 = "\/"
         + unique_id         = (a végrehajtás után ismert)
      }

Terv: 1 hozzáadva, 0 megváltoztatva, 0 törölve.

Ha végrehajtja az apply parancsot, a következő hiba lép fel:

Hiba: Hiba az IAM felhasználó yevgeniy.brikman létrehozásakor: EntityAlreadyExists:
A yevgeniy.brikman névvel rendelkező felhasználó már létezik.

   a main.tf 10. sorában, a resource "aws_iam_user" "existing_user":
   10: resource "aws_iam_user" "existing_user" {

A probléma természetesen abban rejlik, hogy a megadott névvel rendelkező IAM-felhasználó már létezik. Ez nemcsak IAM-felhasználókkal, hanem szinte bármilyen erőforrással is előfordulhat. Lehet, hogy valaki manuálisan vagy parancssoron keresztül hozta létre ezt az erőforrást, de az azonosítók ütközése konfliktusokat okoz. Ennek a hibának számos változata van, amelyek gyakran meglepik a Terraform újoncait.

A kulcsfontosságú dolog az, hogy a terraform plan parancs csak azokat az erőforrásokat veszi figyelembe, amelyeket a Terraform állományban spécifikáltak. Ha az erőforrások valamilyen más módon (például manuálisan, az AWS konzolján kattintva) jöttek létre, akkor nem kerülnek be az állományba, és ezért a Terraform nem fogja őket figyelembe venni a plan parancs végrehajtásakor. Ennek következtében a látszólag helyes terv nem lesz sikeres.

Két tanulságot vonhatunk le ebből.

  • Ha már elkezdett dolgozni a Terraformmal, ne használjon semmi mást. Ha az infrastruktúrájának egy része Terraformmal van kezelve, akkor azt már manuálisan nem szabad megváltoztatni. Ellenkező esetben nemcsak azzal a kockázattal jár, hogy furcsa hibaüzeneteket kap, hanem sok IaC előnyét is elveszíti, mivel a kód már nem lesz pontos tükröződése az infrastruktúrájának.
  • Ha már rendelkezik valamilyen infrastruktúrával, használja az import parancsot. Ha a Terraform-ot egy már létező infrastruktúrával kezdi el használni, azt a terraform import parancs segítségével hozzáadhatja az állapotfájlhoz. Így a Terraform tudni fogja, hogy melyik infrastruktúrával kell kezelnie. Az import parancs két argumentumot fogad el. Az első az erőforrás címe a konfigurációs fájlokban. Itt ugyanaz a szintaxis érvényes, mint az erőforrás-hivatkozásoknál: _. (például aws_iam_user.existing_user). A második argumentum az az erőforrás azonosítója, amelyet importálni kell. Mondjuk, az aws_iam_user erőforrás azonosítója a felhasználónév (például yevgeniy.brikman), míg az aws_instance erőforrás azonosítója az EC2 szerver azonosítója (például i-190e22e5). Az erőforrás importálásának módja általában a dokumentációban található meg az oldal alján.

    Az alábbiakban látható az import parancs, amely lehetővé teszi, hogy szinkronizálja az aws_iam_user erőforrást, amit hozzáadott a Terraform konfigurációjához, a 2. fejezet IAM felhasználójával együtt (természetesen yevgeniy.brikman helyett a saját nevét kell beírnia):

    $ terraform import aws_iam_user.existing_user yevgeniy.brikman

    A Terraform kapcsolatba lép az AWS API-jával, hogy megtalálja az IAM felhasználóját, és létrehozza az állapotfájlban a kapcsolatot az aws_iam_user.existing_user erőforrással a Terraform konfigurációjában. Ettől a pillanattól kezdve, amikor végrehajtja a plan parancsot, a Terraform tudni fogja, hogy az IAM felhasználó már létezik, és nem fogja megpróbálni újra létrehozni.

    Érdemes megjegyezni, hogy ha sok olyan erőforrással rendelkezik, amelyet importálni szeretne a Terraform-ba, akkor a kód kézi megírása és importálása egyenként időigényes feladat lehet. Ezért érdemes odafigyelni egy olyan eszközre, mint a Terraforming (http://terraforming.dtan4.net/), amely automatikusan importálhatja az AWS fiókból a kódot és az állapotot.

    A refaktorálásnak megvannak a maga buktatói

    Refaktorálás — elterjedt gyakorlat a programozásban, ahol a kód belső struktúráját változtatja meg, miközben a külső viselkedése változatlan marad. Ezt azért teszik, hogy a kód érthetőbbé, rendezettebbé és könnyebben karbantarthatóvá váljon. A refaktorálás egy nélkülözhetetlen módszer, amelyet rendszeresen alkalmazni kell. De amikor a Terraform-ról vagy bármely más IaC eszközről van szó, rendkívül óvatosan kell kezelni, hogy mit értünk a kód egy részének "külső viselkedése" alatt, különben előre nem látott problémák merülhetnek fel.

    Például a leggyakoribb refaktorálási forma a változók vagy függvények elnevezésének érthetőbbé tétele. Sok IDE beépített refaktorálási támogatással rendelkezik, és automatikusan át tudja nevezni a változókat és függvényeket az egész projektben. Általános célú programozási nyelvekben ez egy triviális eljárás, amire nem is kell gondolni, azonban a Terraform esetében ezzel nagyon óvatosnak kell lennünk, különben zavarok merülhetnek fel.

    Például a webserver-cluster modulban van egy bemeneti változó, a cluster_name:

    variable "cluster_name" {
       description = "Az összes klasztererőforráshoz használandó név"
       type          = string
    }

    Képzelje el, hogy ezt a modult használva elkezdi telepíteni a foo nevű mikroszolgáltatást. Később a szolgáltatása átnevezését bar névre szeretné. Ez a módosítás triviálisnak tűnhet, de valójában zavarokat okozhat.

    Az a helyzet, hogy a webserver-cluster modul használja a cluster_name változót számos erőforrásban, beleértve az ALB és a két biztonsági csoport name paraméterét is:

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

    Ha változtat a name paraméternél egy erőforrásban, a Terraform törli a régi verziót, és helyette újat hoz létre. De ha ez az erőforrás ALB, akkor a régi kiadása és az új verzió betöltése között nem lesz eszköz a forgalom átirányítására a webkiszolgálóra. Hasonlóképpen, ha egy biztonsági csoportot törölnek, a szerverei el fogják utasítani bármilyen hálózati forgalmat, amíg új biztonsági csoportot nem hoznak létre.

    Egy másik refaktorálási forma, ami érdekelheti, az Terraform azonosító megváltoztatása. Vegyük például az aws_security_group erőforrást a webserver-cluster modulban:

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

    Ennek az erőforrásnak az azonosítója instance. Képzelje el, hogy a refaktorálás során úgy döntött, hogy egy érthetőbb (szerinte) nevet szeretne adni neki, például cluster_instance:

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

    Mi fog történni? Helyes: zavar a működésben.

    A Terraform összekapcsolja az egyes erőforrások ID-ját a felhőszolgáltató azonosítójával. Például az iam_user az AWS IAM felhasználói azonosítójához van kötve, míg az aws_instance az AWS EC2 szerver ID-jához. Ha módosítja az erőforrás azonosítóját (mondjuk instance-ről cluster_instance-re, mint az aws_security_group esetében), a Terraform számára úgy fog megjelenni, mintha eltávolította volna a régi erőforrást, és egy újat hozott volna létre. Ha ezeket a módosításokat alkalmazza, a Terraform eltávolítja a régi biztonsági csoportot, és létrehoz egy másikat, ugyanakkor a szerverei elutasítják a hálózati forgalmat.

    Itt van négy alapvető tanulság, amelyet le kell vonnia ebből a megbeszélésből.

    • Mindig használja a plan parancsot. Ennek segítségével felfedezheti ezeket a buktatókat. Gondosan nézze át a kimenetet, és figyeljen arra, amikor a Terraform olyan erőforrásokat tervez eltávolítani, amelyeket valószínűleg nem kellene.
    • Hozzon létre, mielőtt eltávolítana. Ha egy erőforrást szeretne kicserélni, alaposan gondolja át, szükséges-e az eredeti eltávolítása előtt egy helyettesítőt létrehozni. Ha a válasz igen, ezt a create_before_destroy segíthet. Ugyanezt az eredményt elérheti manuálisan is, ha először hozzáadja az új erőforrást a konfigurációhoz, és végrehajtja az apply parancsot, majd eltávolítja a régi erőforrást a konfigurációból, és ismét végrehajtja az apply-t.
    • Az azonosítók módosítása állapotmódosítást igényel. Ha meg szeretné változtatni az erőforrással társított azonosítót (például az aws_security_group átnevezése instance-ről cluster_instance-re), anélkül, hogy eltávolítaná az erőforrást, és létrehozna egy új verziót, megfelelően kell frissítenie a Terraform állományt. Ezt soha ne végezze el kézzel - használja ehelyett a terraform state parancsot. Az azonosítók átnevezésekor a terraform state mv parancsot kell végrehajtani, amelynek a következő szintaxisa van:
      terraform state mv

      Az ORIGINAL_REFERENCE az aktuális formájában hivatkozik az erőforrásra, míg a NEW_REFERENCE arra a helyre utal, ahova szeretné áthelyezni. Például az aws_security_group instance-ről cluster_instance-re történő átnevezéséhez a következő parancsot kell végrehajtania:

      $ terraform state mv 
         aws_security_group.instance 
         aws_security_group.cluster_instance

      Így értesíti a Terraformot, hogy az állapot, amely korábban az aws_security_group.instance-hoz tartozott, mostantól össze kell kapcsolódnia az aws_security_group.cluster_instance-szel. Ha a nevének megváltoztatása után a terraform plan parancs nem mutat semmilyen változást, akkor helyesen járt el.

    • Egyes paramétereket nem lehet módosítani. Sok erőforrás paraméterei módosíthatatlanok. Ha megpróbálja módosítani őket, a Terraform eltávolítja a régi erőforrást, és helyette újat hoz létre. Minden erőforrás oldalán általában fel van tüntetve, mi történik egy adott paraméter megváltoztatásakor, ezért mindig ellenőrizze a dokumentációt. Mindig használja a plan parancsot, és vegye figyelembe a create_before_destroy stratégia megvalósíthatóságát.

    A halasztott konzisztencia összhangban van... a halasztással.

    Néhány felhőszolgáltató API-ja, mint például az AWS, aszinkron és halasztott konzisztenciát alkalmaz. Az aszinkronitás azt jelenti, hogy az API azonnal választhat, anélkül, hogy megvárná a kérdéses művelet befejezését. A halasztott konzisztencia azt jelenti, hogy a változások elterjedéséhez az egész rendszerben idő szükséges; amíg ez megtörténik, a válaszai lehetnek következetlenek, és attól függnek, hogy melyik adatforrás-reakció válaszol a API-hívásaira.

    Képzelje el például, hogy API-hívást végez az AWS-nél egy EC2 szerver létrehozásának kérésével. Az API szinte azonnal visszaad egy "sikeres" választ (201 Created), anélkül, hogy megvárná a szerver tényleges létrejöttét. Ha azonnal megpróbál hozzáférni, valószínű, hogy nem fog sikerülni, mivel ekkor az AWS még mindig inicializálja az erőforrásokat, vagy a szerver még nem indult el. Továbbá, ha újabb hívást kezdeményez a szerver információinak megkérésére, hibaüzenetet kaphat (404 Not Found). Az az igazság, hogy az EC2 szerver adatai még mindig terjedhetnek az AWS-en keresztül, és ennek elérhetővé válásához néhány másodperc várakozás szükséges.

    Minden alkalommal, amikor halasztott konzisztenciával rendelkező aszinkron API-t használ, időszakonként meg kell ismételnie a kérését, amíg a művelet be nem fejeződik és el nem terjed a rendszerben. Sajnos az AWS SDK nem biztosít erre jó eszközöket, és a Terraform projekt korábban számos hibával küzdött, mint például a 6813 (https://github.com/hashicorp/terraform/issues/6813):

    $ terraform apply
    aws_subnet.private-persistence.2: InvalidSubnetID.NotFound:
    A megadott alhálózati azonosító 'subnet-xxxxxxx' nem létezik

    Más szóval, erőforrást (például alhálózatot) hoz létre, majd próbál információkat szerezni róla (például a nemrég létrehozott alhálózat azonosítóját), de a Terraform nem találja őket. Az ilyen hibák többségét (beleértve a 6813-at) már javították, de időnként még mindig megjelennek, különösen amikor a Terraform új erőforrástípust támogat. Frusztráló, de a legtöbb esetben nem okoz kárt. A terraform apply újabb végrehajtásakor minden működni fog, mivel addigra az információk már elérhetők lesznek a rendszerben.

    Ez a részlet Jevgenyij Brikman könyvéből származik „Terraform: Infrastruktúra kód szinten”.

Forrás: habr.com

Megbízható hosting vásárlása weboldalak számára, DDoS védelemmel, VPS VDS szerverek. 🔥 Megbízható webtárhely vásárlása DDoS védelemmel, VPS VDS szerverek | ProHoster