Terraform buktatói

Terraform buktatói
Kiemeljünk néhány buktatót, beleértve a ciklusokkal, if utasításokkal és telepítési technikákkal kapcsolatosakat, valamint általánosabb problémákat, amelyek általában a Terraformot érintik:

  • a count és a for_each paraméterek korlátozottak;
  • nulla leállások korlátozása;
  • még egy jó terv is meghiúsulhat;
  • a refaktorálásnak megvannak a buktatói;
  • a halasztott koherencia összhangban van... a halasztással.

A count és a for_each paraméterek korlátozottak

A fejezet példái széles körben használják a count paramétert és a for_each kifejezést ciklusokban és feltételes logikában. Jól teljesítenek, de van két fontos korlátjuk, amellyel tisztában kell lenni.

  • A Count és a for_each nem hivatkozhat egyetlen erőforrás kimeneti változóra sem.
  • count és for_each nem használhatók a modulkonfigurációban.

count és for_each nem hivatkozhat egyetlen erőforrás kimeneti változóra sem

Képzelje el, hogy több EC2-kiszolgálót kell telepítenie, és valamilyen oknál fogva nem akarja használni az ASG-t. A kódod a következő lehet:

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

Nézzük meg őket egyenként.

Mivel a count paraméter statikus értékre van állítva, ez a kód problémamentesen fog működni: az apply parancs futtatásakor három EC2 szervert hoz létre. De mi van akkor, ha a jelenlegi AWS-régión belül minden elérhetőségi zónában (AZ) egy szervert szeretne telepíteni? Beállíthatja, hogy kódja betöltse a zónák listáját az aws_availability_zones adatforrásból, majd mindegyiken végigfusson, és létrehozzon benne egy EC2-kiszolgálót a count paraméterrel és a tömbindex hozzáféréssel:

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 jól működik, mivel a count paraméter probléma nélkül hivatkozhat adatforrásokra. De mi történik, ha a létrehozandó szerverek száma valamilyen erőforrás kimenetétől függ? Ennek demonstrálására a legegyszerűbb a random_integer erőforrás használata, 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 egy 1 és 3 közötti véletlenszámot generál. Nézzük meg, mi történik, ha megpróbáljuk ennek az erőforrásnak a kimenetét használni 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 terraform tervet futtat ezen a kódon, a következő hibaüzenetet kapja:

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.

A Terraform megköveteli a count és for_each kiszámítását a tervezési szakaszban, mielőtt bármilyen erőforrást létrehoznának vagy módosítanának. Ez azt jelenti, hogy a count és a for_each vonatkozhat literálokra, változókra, adatforrásokra és akár erőforráslistákra is (amennyiben ezek hossza az ütemezés időpontjában meghatározható), de nem a számított erőforrás kimeneti változókra.

count és for_each nem használhatók a modulkonfigurációban

Előfordulhat, hogy egy nap kísértést érezhet egy count paraméter hozzáadása a modul konfigurációjához:

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 a modulon belüli számlálást használni a webszerver-fürt erőforrás három példányának létrehozásához. Vagy választhatóvá szeretné tenni egy modul csatlakoztatását valamilyen logikai feltétel alapján, ha a count paraméterét 0-ra állítja. Ez ésszerű kódnak tűnhet, de a terraform terv futtatásakor ez a hibaüzenet jelenik meg:

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.

Sajnos a Terraform 0.12.6-tól kezdve a count vagy a for_each használata nem támogatott egy modulerőforrásban. A Terraform 0.12 kiadási megjegyzései szerint (http://bit.ly/3257bv4) a HashiCorp azt tervezi, hogy a jövőben hozzáadja ezt a képességet, így attól függően, hogy mikor olvassa ezt a könyvet, már elérhető lehet. Hogy biztosan megtudja, olvassa el a Terraform változásnaplóját itt.

Az állásidő nulla telepítésének korlátai

A create_before_destroy blokk ASG-vel kombinálva nagyszerű megoldást jelent a nulla leállási idejű telepítések létrehozására, kivéve egy figyelmeztetést: az automatikus skálázási szabályok nem támogatottak. Pontosabban, ez minden telepítéskor visszaállítja az ASG méretét a min_size értékre, ami problémát jelenthet, ha automatikus skálázási szabályokat használ a futó kiszolgálók számának növelésére.

Például a webszerver-fürt modul egy pár aws_autoscaling_schedule erőforrást tartalmaz, amely reggel 9 órakor kettőről tízre növeli a fürtben lévő kiszolgálók számát. Ha mondjuk délelőtt 11-kor telepíti, az új ASG csak két szerverrel indul tíz helyett, és így marad másnap reggel 9 óráig.

Ez a korlátozás többféleképpen megkerülhető.

  • Módosítsa az aws_autoscaling_schedule ismétlődési paraméterét 0 9 * * * értékről ("futás 9 órakor") valami ilyesmire: 0-59 9-17 * * * ("futtatás percenként reggel 9 és délután 5 óra között"). Ha az ASG-nek már tíz szervere van, akkor ennek az automatikus skálázási szabálynak az újbóli futtatása nem változtat semmit, amit szeretnénk. De ha az ASG-t csak nemrégiben telepítették, ez a szabály biztosítja, hogy legfeljebb egy percen belül a szervereinek száma elérje a tízet. Ez nem teljesen elegáns megközelítés, és a nagy ugrások tízről két szerverre és vissza szintén problémákat okozhatnak a felhasználóknak.
  • Hozzon létre egy egyéni parancsfájlt, amely az AWS API-t használja az ASG-ben lévő aktív kiszolgálók számának meghatározására, hívja meg külső adatforrás használatával (lásd: „Külső adatforrás”, 249. oldal), és állítsa be az ASG kívánt_kapacitás paraméterét a A script. Így minden új ASG-példány mindig ugyanazon a kapacitáson fog futni, mint a meglévő Terraform-kód, és megnehezíti a karbantartást.

Természetesen a Terraform ideális esetben beépített támogatással rendelkezik az állásidő nélküli telepítésekhez, de 2019 májusában a HashiCorp csapata nem tervezte, hogy hozzáadja ezt a funkciót (részletek - itt).

Előfordulhat, hogy a helyes tervet sikertelenül hajtják végre

Néha a plan parancs tökéletesen helyes telepítési tervet állít elő, de az apply parancs hibát ad vissza. Próbálja meg például hozzáadni az aws_iam_user erőforrást ugyanazzal a névvel, mint a 2. fejezetben korábban létrehozott IAM-felhasználónál:

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

Most, ha futtatja a terv parancsot, a Terraform egy ésszerűnek tűnő telepítési tervet ad ki:

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.

Ha futtatja az application parancsot, a következő hibaüzenetet kapja:

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

A probléma természetesen az, hogy már létezik ilyen nevű IAM-felhasználó. És ez nem csak az IAM felhasználókkal, hanem szinte minden erőforrással megtörténhet. Lehetséges, hogy valaki manuálisan vagy a parancssor használatával hozta létre ezt az erőforrást, de mindkét esetben az azonosítók egyeztetése ütközésekhez vezet. Ennek a hibának számos változata létezik, amelyek gyakran váratlanul érik a Terraform újoncait.

A lényeg az, hogy a terraform terv parancs csak azokat az erőforrásokat veszi figyelembe, amelyek a Terraform állapotfájlban vannak megadva. Ha az erőforrásokat más módon (például manuálisan, az AWS konzolon kattintással) hozza létre, akkor azok nem kerülnek az állapotfájlba, ezért a Terraform nem veszi figyelembe őket a terv parancs végrehajtása során. Ennek eredményeként az első pillantásra helyesnek tűnő terv sikertelennek bizonyul.

Ebből két tanulság is levonható.

  • Ha már elkezdett dolgozni a Terraformmal, ne használjon mást. Ha az infrastruktúra egy részét a Terraform segítségével kezelik, akkor azt manuálisan már nem módosíthatja. Ellenkező esetben nemcsak furcsa Terraform hibákat kockáztat, hanem az IaC számos előnyét is megfosztja, mivel a kód többé nem fogja pontosan ábrázolni az infrastruktúrát.
  • Ha már rendelkezik valamilyen infrastruktúrával, használja az import parancsot. Ha már meglévő infrastruktúrával kezdi használni a Terraformot, a terraform import paranccsal hozzáadhatja az állapotfájlhoz. Így a Terraform tudni fogja, milyen infrastruktúrát kell kezelni. Az import parancsnak két argumentuma van. Az első az erőforrás címe a konfigurációs fájlokban. A szintaxis itt ugyanaz, mint az erőforráshivatkozásoknál: _. (mint az aws_iam_user.existing_user). A második argumentum az importálandó erőforrás azonosítója. Tegyük fel, hogy az aws_iam_user erőforrás-azonosító a felhasználónév (például yevgeniy.brikman), az aws_instance erőforrás-azonosító pedig az EC2-kiszolgálóazonosító (mint például az i-190e22e5). Az erőforrás importálásának módját általában az oldal alján található dokumentáció tartalmazza.

    Az alábbiakban látható egy import parancs, amely szinkronizálja az aws_iam_user erőforrást, amelyet hozzáadott a Terraform konfigurációjához a 2. fejezetben szereplő IAM felhasználóval együtt (természetesen a yevgeniy.brikman helyett az Ön nevét):

    $ terraform import aws_iam_user.existing_user yevgeniy.brikman

    A Terraform meghívja az AWS API-t, hogy megtalálja az IAM-felhasználót, és állapotfájltársítást hozzon létre közte és az aws_iam_user.existing_user erőforrás között a Terraform konfigurációjában. Mostantól a terv parancs futtatásakor a Terraform tudni fogja, hogy az IAM felhasználó már létezik, és nem próbálja meg újra létrehozni.

    Érdemes megjegyezni, hogy ha már sok erőforrással rendelkezik, amelyet importálni szeretne a Terraformba, akkor a kód manuális megírása és egyenkénti importálása gondot okozhat. Érdemes tehát egy olyan eszközt keresni, mint a Terraforming (http://terraforming.dtan4.net/), amely képes automatikusan importálni kódot és állapotot az AWS-fiókjából.

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

    Refaktorálás bevett gyakorlat a programozásban, ahol megváltoztatjuk a kód belső szerkezetét, miközben a külső viselkedést változatlanul hagyjuk. Ennek célja, hogy a kód tisztább, rendezettebb és könnyebben karbantartható legyen. A refaktorálás nélkülözhetetlen technika, amelyet rendszeresen kell alkalmazni. De ha Terraformról vagy bármely más IaC-eszközről van szó, rendkívül óvatosnak kell lennie azzal kapcsolatban, hogy mit ért egy kódrészlet „külső viselkedése” alatt, különben váratlan problémák merülnek fel.

    Például az átalakítás gyakori típusa a változók vagy függvények nevének érthetőbbre cserélése. Sok IDE beépített támogatással rendelkezik az újrafeldolgozáshoz, és automatikusan át tudja nevezni a változókat és a függvényeket a projekt során. Az általános célú programozási nyelvekben ez egy triviális eljárás, amelyre talán nem is gondolna, de a Terraformban ezzel rendkívül óvatosan kell eljárni, különben kimaradások léphetnek fel.

    Például a webszerver-fürt modulnak van egy fürt_neve bemeneti változója:

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

    Képzelje el, hogy ezzel a modullal kezdte el telepíteni a foo nevű mikroszolgáltatást. Később át szeretné nevezni a szolgáltatást bar névre. Ez a változás triviálisnak tűnhet, de a valóságban szolgáltatási zavarokat okozhat.

    A tény az, hogy a webszerver-fürt modul a cluster_name változót használja számos erőforrásban, beleértve a két biztonsági csoport név paraméterét és az ALB-t:

    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 módosítja egy erőforrás névparaméterét, a Terraform törli az erőforrás régi verzióját, és újat hoz létre a helyén. De ha ez az erőforrás egy ALB, akkor a törlés és az új verzió letöltése között nem lesz olyan mechanizmus, amely a forgalmat a webszerverre irányítja át. Hasonlóképpen, ha egy biztonsági csoportot törölnek, a kiszolgálók elkezdenek elutasítani minden hálózati forgalmat, amíg új csoport nem jön létre.

    Az újrafaktorálás másik típusa, amely érdekelheti, a Terraform ID 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óját példánynak nevezik. Képzelje el, hogy az átalakítás során úgy döntött, hogy egy érthetőbb (az Ön véleménye szerint) cluster_instance névre változtatja:

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

    Mi lesz a végén? Így van: zavar.

    A Terraform minden erőforrás-azonosítót társít a felhőszolgáltató azonosítójához. Például az iam_user az AWS IAM felhasználói azonosítóhoz, az aws_instance pedig az AWS EC2 szerverazonosítóhoz van társítva. Ha módosítja az erőforrásazonosítót (például példányról cluster_példányra, mint az aws_security_group esetében), akkor Terraformra módosítja, úgy fog megjelenni, mintha törölte volna a régi erőforrást, és hozzáadna egy újat. Ha alkalmazza ezeket a módosításokat, a Terraform törli a régi biztonsági csoportot, és újat hoz létre, miközben a kiszolgálók elkezdik elutasítani a hálózati forgalmat.

    Íme négy legfontosabb tanulság, amelyet le kell vonnia ebből a beszélgetésből.

    • Mindig használja a plan parancsot. Felfedi mindezeket a buktatókat. Gondosan tekintse át a kimenetet, és figyeljen azokra a helyzetekre, amikor a Terraform olyan erőforrások törlését tervezi, amelyeket valószínűleg nem kellene törölni.
    • Törlés előtt hozzon létre. Ha le akar cserélni egy erőforrást, az eredeti törlése előtt alaposan gondolja át, hogy létre kell-e hoznia egy cserét. Ha a válasz igen, a create_before_destroy segíthet. Ugyanezt az eredményt kézzel is elérheti két lépés végrehajtásával: először adjon hozzá egy új erőforrást a konfigurációhoz, és futtassa az apply parancsot, majd távolítsa el a régi erőforrást a konfigurációból, és használja újra az apply parancsot.
    • Az azonosítók megváltoztatásához állapotváltás szükséges. Ha módosítani szeretné az erőforráshoz társított azonosítót (például átnevezni az aws_security_group-ot példányról fürtpéldányra) anélkül, hogy törölné az erőforrást, és új verziót hozna létre, ennek megfelelően frissítenie kell a Terraform állapotfájlt. Soha ne tegye ezt kézzel – használja inkább a terraform state parancsot. Az azonosítók átnevezésekor futtassa a terraform state mv parancsot, amely a következő szintaxissal rendelkezik:
      terraform state mv <ORIGINAL_REFERENCE> <NEW_REFERENCE>

      Az ORIGINAL_REFERENCE egy kifejezés, amely az erőforrásra a jelenlegi formájában hivatkozik, és a NEW_REFERENCE az a hely, ahová át szeretné helyezni. Például, amikor az aws_security_group csoportot példányról cluster_instance névre nevezi át, a következő parancsot kell futtatnia:

      $ terraform state mv 
         aws_security_group.instance 
         aws_security_group.cluster_instance

      Ez azt mondja a Terraformnak, hogy a korábban az aws_security_group.instance fájlhoz társított állapotot most az aws_security_group.cluster_instance-hoz kell társítani. Ha a parancs átnevezése és futtatása után a terraform terv nem mutat változást, akkor mindent helyesen csinált.

    • Egyes beállítások nem módosíthatók. Sok erőforrás paraméterei megváltoztathatatlanok. Ha megpróbálja megváltoztatni őket, a Terraform törli a régi erőforrást, és újat hoz létre a helyére. Az egyes erőforrásoldalak általában jelzik, hogy mi történik egy adott beállítás megváltoztatásakor, ezért feltétlenül ellenőrizze a dokumentációt. Mindig használja a plan parancsot, és fontolja meg a create_before_destroy stratégia használatát.

    A halasztott következetesség összhangban van... a halasztással

    Egyes felhőszolgáltatók API-jai, például az AWS, aszinkronok és késleltetett konzisztenciával rendelkeznek. Az aszinkronitás azt jelenti, hogy az interfész azonnal válaszolhat anélkül, hogy megvárná a kért művelet befejezését. A késleltetett konzisztencia azt jelenti, hogy a változásoknak időbe telhet a rendszerben való terjedése; Amíg ez megtörténik, a válaszok következetlenek lehetnek, és attól függhetnek, hogy melyik adatforrás-replika válaszol az API-hívásokra.

    Képzelje el például, hogy API-hívást kezdeményez az AWS-nek, és megkéri, hogy hozzon létre egy EC2-kiszolgálót. Az API „sikeres” választ (201 Created) küld szinte azonnal, anélkül, hogy megvárná magának a szervernek a létrehozását. Ha azonnal megpróbál csatlakozni hozzá, akkor szinte biztosan sikertelen lesz, mert ekkor az AWS még mindig inicializálja az erőforrásokat, vagy a kiszolgáló még nem indult el. Ezenkívül, ha újabb hívást kezdeményez, hogy információkat szerezzen erről a szerverről, hibaüzenetet kaphat (404 Nem található). A helyzet az, hogy az EC2-szerverrel kapcsolatos információk továbbra is terjeszthetők az AWS-ben, mielőtt mindenhol elérhetővé válna, várnia kell néhány másodpercet.

    Valahányszor lusta következetességű aszinkron API-t használ, időnként újra meg kell próbálnia a kérést, amíg a művelet be nem fejeződik és át nem terjed a rendszeren. Sajnos az AWS SDK nem biztosít ehhez megfelelő eszközöket, és a Terraform projekt korábban sok olyan hibától szenvedett, mint a 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

    Más szóval, létrehoz egy erőforrást (például egy alhálózatot), majd megpróbál információt szerezni róla (például az újonnan létrehozott alhálózat azonosítóját), és a Terraform nem találja azt. Ezeknek a hibáknak a többségét (beleértve a 6813-at is) kijavították, de időről időre még mindig felbukkannak, különösen, amikor a Terraform támogatást ad egy új erőforrástípushoz. Ez bosszantó, de a legtöbb esetben nem okoz kárt. A terraform application ismételt futtatásakor mindennek működnie kell, mert ekkorra az információ már elterjedt a rendszerben.

    Ezt a részletet Jevgenyij Brikman könyvéből mutatjuk be "Terraform: infrastruktúra kódszinten".

Forrás: will.com

Hozzászólás