Teraformní nástrahy

Teraformní nástrahy
Zdůrazněme několik úskalí, včetně těch souvisejících se smyčkami, příkazy if a technikami nasazení, stejně jako obecnější problémy, které ovlivňují Terraform obecně:

  • parametry count a for_each mají omezení;
  • omezit nasazení nulových prostojů;
  • i dobrý plán může selhat;
  • refaktoring může mít svá úskalí;
  • odložená koherence je konzistentní... s odložením.

Parametry count a for_each mají omezení

Příklady v této kapitole široce využívají parametr count a výraz for_each v cyklech a podmíněné logice. Fungují dobře, ale mají dvě důležitá omezení, kterých si musíte být vědomi.

  • Count a for_each nemohou odkazovat na žádné výstupní proměnné zdroje.
  • count a for_each nelze použít v konfiguraci modulu.

count a for_each nemohou odkazovat na žádné výstupní proměnné zdroje

Představte si, že potřebujete nasadit několik EC2 serverů a z nějakého důvodu nechcete používat ASG. Váš kód by mohl být takto:

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

Pojďme se na ně jeden po druhém podívat.

Protože je parametr count nastaven na statickou hodnotu, bude tento kód fungovat bez problémů: když spustíte příkaz Apply, vytvoří se tři servery EC2. Co když ale chcete nasadit jeden server v každé zóně dostupnosti (AZ) ve vaší aktuální oblasti AWS? Můžete nechat svůj kód načíst seznam zón ze zdroje dat aws_availability_zones a poté každou z nich procházet a vytvořit v ní server EC2 pomocí parametru count a přístupu k indexu pole:

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

Tento kód bude také fungovat dobře, protože parametr count může bez problémů odkazovat na zdroje dat. Ale co se stane, když počet serverů, které potřebujete vytvořit, závisí na výstupu nějakého zdroje? Pro demonstraci je nejjednodušší použít prostředek random_integer, který, jak název napovídá, vrací náhodné celé číslo:

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

Tento kód generuje náhodné číslo mezi 1 a 3. Podívejme se, co se stane, když se pokusíme použít výstup tohoto zdroje v parametru count zdroje aws_instance:

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

Pokud spustíte plán terraform na tomto kódu, zobrazí se následující chyba:

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 vyžaduje, aby count a for_each byly vypočítány během plánovací fáze, než budou vytvořeny nebo upraveny jakékoli zdroje. To znamená, že count a for_each mohou odkazovat na literály, proměnné, zdroje dat a dokonce i seznamy zdrojů (pokud lze jejich délku určit v době plánování), ale ne na vypočítané výstupní proměnné zdroje.

count a for_each nelze použít v konfiguraci modulu

Jednoho dne můžete být v pokušení přidat parametr počtu do konfigurace modulu:

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

     count = 3

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

Tento kód se pokouší použít počet uvnitř modulu k vytvoření tří kopií prostředku webserver-cluster. Nebo můžete chtít nastavit připojení modulu jako volitelné na základě nějaké booleovské podmínky nastavením jeho parametru počtu na 0. Může to vypadat jako rozumný kód, ale při spuštění plánu terraform se zobrazí tato chyba:

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.

Bohužel od Terraformu 0.12.6 není podporováno použití count nebo for_each ve zdroji modulu. Podle poznámek k vydání Terraform 0.12 (http://bit.ly/3257bv4) plánuje HashiCorp tuto schopnost v budoucnu přidat, takže v závislosti na tom, kdy si přečtete tuto knihu, může být již k dispozici. Chcete-li to s jistotou zjistit, Přečtěte si seznam změn Terraform zde.

Omezení nulových výpadků nasazení

Použití bloku create_before_destroy v kombinaci s ASG je skvělé řešení pro vytváření nasazení s nulovými prostoji, s výjimkou jednoho upozornění: pravidla automatického škálování nejsou podporována. Nebo přesněji, toto resetuje velikost ASG zpět na min_size při každém nasazení, což by mohl být problém, pokud byste ke zvýšení počtu spuštěných serverů používali pravidla automatického škálování.

Například modul webserver-cluster obsahuje dvojici zdrojů aws_autoscaling_schedule, která v 9 hodin ráno zvyšuje počet serverů v clusteru ze dvou na deset. Pokud nasadíte řekněme v 11:9, nový ASG se spustí pouze se dvěma servery namísto deseti a zůstane tak až do XNUMX:XNUMX následujícího dne.

Toto omezení lze obejít několika způsoby.

  • Změňte parametr opakování v aws_autoscaling_schedule z 0 9 * * * (“běh v 9:0”) na něco jako 59-9 17-9 * * * (“běží každou minutu od 5:XNUMX do XNUMX:XNUMX”). Pokud má ASG již deset serverů, opětovné spuštění tohoto pravidla automatického škálování nic nezmění, což je to, co chceme. Ale pokud byl ASG nasazen teprve nedávno, toto pravidlo zajistí, že maximálně za minutu dosáhne počet jeho serverů deseti. To není úplně elegantní přístup a velké skoky z deseti na dva servery a zpět mohou uživatelům také způsobit problémy.
  • Vytvořte vlastní skript, který používá AWS API k určení počtu aktivních serverů v ASG, zavolejte jej pomocí externího zdroje dat (viz „Externí zdroj dat“ na straně 249) a nastavte parametr required_capacity ASG na hodnotu vrácenou scénář. Tímto způsobem bude každá nová instance ASG vždy běžet na stejné kapacitě jako stávající kód Terraform a bude obtížnější udržovat.

Terraform by samozřejmě v ideálním případě měl vestavěnou podporu pro nasazení s nulovými prostoji, ale od května 2019 tým HashiCorp neměl v plánu tuto funkci přidat (podrobnosti - zde).

Správný plán může být neúspěšně realizován

Někdy příkaz plan vytvoří dokonale správný plán nasazení, ale příkaz Apply vrátí chybu. Zkuste například přidat prostředek aws_iam_user se stejným názvem, jaký jste použili pro uživatele IAM, kterého jste vytvořili dříve v kapitole 2:

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

Nyní, když spustíte příkaz plan, Terraform vygeneruje zdánlivě rozumný plán nasazení:

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.

Pokud spustíte příkaz použít, zobrazí se následující chyba:

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

Problém je samozřejmě v tom, že uživatel IAM s tímto jménem již existuje. A to se může stát nejen uživatelům IAM, ale téměř každému zdroji. Je možné, že někdo vytvořil tento prostředek ručně nebo pomocí příkazového řádku, ale v každém případě shoda ID vede ke konfliktům. Existuje mnoho variant této chyby, které často zaskočí nováčky v Terraformu.

Klíčovým bodem je, že příkaz terraform plan bere v úvahu pouze ty zdroje, které jsou specifikovány ve stavovém souboru Terraform. Pokud jsou zdroje vytvořeny nějakým jiným způsobem (například ručně kliknutím v konzole AWS), neskončí ve stavovém souboru a Terraform je tedy nebude brát v úvahu při provádění příkazu plánu. Výsledkem je, že plán, který se na první pohled zdá správný, se ukáže jako neúspěšný.

Z toho plynou dvě ponaučení.

  • Pokud jste již s Terraformem začali pracovat, nic jiného nepoužívejte. Pokud je část vaší infrastruktury spravována pomocí Terraformu, nemůžete ji již ručně upravovat. V opačném případě riskujete nejen podivné chyby Terraform, ale také negujete mnoho výhod IaC, protože kód již nebude přesnou reprezentací vaší infrastruktury.
  • Pokud již nějakou infrastrukturu máte, použijte příkaz import. Pokud začínáte používat Terraform se stávající infrastrukturou, můžete ji přidat do souboru stavu pomocí příkazu terraform import. Terraform tak bude vědět, jakou infrastrukturu je třeba spravovat. Příkaz import má dva argumenty. První je adresa zdroje ve vašich konfiguračních souborech. Syntaxe je zde stejná jako u odkazů na zdroje: _. (jako aws_iam_user.existing_user). Druhý argument je ID zdroje, který má být importován. Řekněme, že ID prostředku aws_iam_user je uživatelské jméno (například yevgeniy.brikman) a ID prostředku aws_instance je ID serveru EC2 (jako i-190e22e5). Jak importovat zdroj je obvykle uvedeno v dokumentaci ve spodní části stránky.

    Níže je příkaz importu, který synchronizuje zdroj aws_iam_user, který jste přidali do své konfigurace Terraform spolu s uživatelem IAM v kapitole 2 (samozřejmě nahrazením vašeho jména yevgeniy.brikman):

    $ terraform import aws_iam_user.existing_user yevgeniy.brikman

    Terraform zavolá AWS API, aby nalezl vašeho uživatele IAM a vytvořil přidružení stavového souboru mezi ním a prostředkem aws_iam_user.existing_user ve vaší konfiguraci Terraform. Od této chvíle, když spustíte příkaz plán, Terraform bude vědět, že uživatel IAM již existuje a nebude se jej pokoušet znovu vytvořit.

    Stojí za zmínku, že pokud již máte mnoho zdrojů, které chcete importovat do Terraformu, ruční psaní kódu a import každého z nich může být problém. Takže stojí za to podívat se na nástroj, jako je Terraforming (http://terraforming.dtan4.net/), který dokáže automaticky importovat kód a stav z vašeho účtu AWS.

    Refaktoring může mít svá úskalí

    Refaktoring je běžná praxe v programování, kdy měníte vnitřní strukturu kódu, zatímco vnější chování zůstává nezměněno. To proto, aby byl kód přehlednější, přehlednější a snadněji se udržuje. Refaktoring je nepostradatelná technika, která by se měla používat pravidelně. Ale pokud jde o Terraform nebo jakýkoli jiný nástroj IaC, musíte být extrémně opatrní, co myslíte „vnějším chováním“ části kódu, jinak nastanou neočekávané problémy.

    Běžným typem refaktoringu je například nahrazení názvů proměnných nebo funkcí srozumitelnějšími. Mnoho IDE má vestavěnou podporu pro refaktoring a může automaticky přejmenovávat proměnné a funkce v průběhu projektu. V programovacích jazycích pro všeobecné použití je to triviální postup, o kterém byste možná nepřemýšleli, ale v Terraformu s tím musíte být extrémně opatrní, jinak můžete zaznamenat výpadky.

    Například modul webserver-cluster má vstupní proměnnou cluster_name:

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

    Představte si, že jste tento modul začali používat k nasazení mikroslužby s názvem foo. Později budete chtít službu přejmenovat na bar. Tato změna se může zdát triviální, ale ve skutečnosti může způsobit narušení služby.

    Faktem je, že modul webserver-cluster používá proměnnou cluster_name v řadě zdrojů, včetně parametru názvu dvou bezpečnostních skupin a 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]
    }

    Pokud změníte parametr názvu zdroje, Terraform smaže starou verzi tohoto zdroje a vytvoří na jejím místě novou. Pokud je však tímto zdrojem ALB, mezi jeho odstraněním a stažením nové verze nebudete mít mechanismus pro přesměrování provozu na váš webový server. Podobně, pokud je odstraněna skupina zabezpečení, vaše servery začnou odmítat veškerý síťový provoz, dokud nebude vytvořena nová skupina.

    Dalším typem refaktoringu, který by vás mohl zajímat, je změna Terraform ID. Vezměme si jako příklad prostředek aws_security_group v modulu webserver-cluster:

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

    Identifikátor tohoto zdroje se nazývá instance. Představte si, že jste se během refaktoringu rozhodli změnit jej na srozumitelnější (podle vašeho názoru) název cluster_instance:

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

    Co se nakonec stane? To je pravda: narušení.

    Terraform přidruží každé ID prostředku k ID poskytovatele cloudu. Například iam_user je spojen s ID uživatele AWS IAM a aws_instance je spojen s ID serveru AWS EC2. Pokud změníte ID prostředku (řekněme z instance na cluster_instance, jako je tomu u aws_security_group), na Terraform to bude vypadat, jako byste odstranili starý prostředek a přidali nový. Pokud použijete tyto změny, Terraform odstraní starou bezpečnostní skupinu a vytvoří novou, zatímco vaše servery začnou odmítat jakýkoli síťový provoz.

    Zde jsou čtyři hlavní ponaučení, která byste si z této diskuse měli odnést.

    • Vždy používejte příkaz plán. Dokáže odhalit všechny tyto zádrhele. Pečlivě zkontrolujte jeho výstup a věnujte pozornost situacím, kdy Terraform plánuje odstranit zdroje, které by s největší pravděpodobností neměly být odstraněny.
    • Před smazáním vytvořte. Pokud chcete zdroj nahradit, před smazáním originálu si dobře rozmyslete, zda potřebujete vytvořit náhradu. Pokud je odpověď ano, může pomoci create_before_destroy. Stejného výsledku lze dosáhnout ručně provedením dvou kroků: nejprve přidejte nový prostředek do konfigurace a spusťte příkaz Apply a poté odstraňte starý prostředek z konfigurace a znovu použijte příkaz Apply.
    • Změna identifikátorů vyžaduje změnu stavu. Pokud chcete změnit ID přidružené k prostředku (například přejmenovat aws_security_group z instance na cluster_instance), aniž byste prostředek odstranili a vytvořili jeho novou verzi, musíte odpovídajícím způsobem aktualizovat soubor stavu Terraform. Nikdy to nedělejte ručně – použijte místo toho příkaz terraform state. Při přejmenování identifikátorů byste měli spustit příkaz terraform state mv, který má následující syntaxi:
      terraform state mv <ORIGINAL_REFERENCE> <NEW_REFERENCE>

      ORIGINAL_REFERENCE je výraz, který odkazuje na zdroj v jeho aktuální podobě, a NEW_REFERENCE je místo, kam jej chcete přesunout. Například při přejmenování skupiny aws_security_group z instance na cluster_instance musíte spustit následující příkaz:

      $ terraform state mv 
         aws_security_group.instance 
         aws_security_group.cluster_instance

      To Terraformu říká, že stav, který byl dříve přidružen k aws_security_group.instance, by nyní měl být přidružen k aws_security_group.cluster_instance. Pokud po přejmenování a spuštění tento příkazový plán terraform neukazuje žádné změny, pak jste udělali vše správně.

    • Některá nastavení nelze změnit. Parametry mnoha zdrojů jsou neměnné. Pokud se je pokusíte změnit, Terraform smaže starý zdroj a na jeho místě vytvoří nový. Na každé stránce zdrojů bude obvykle uvedeno, co se stane, když změníte konkrétní nastavení, takže si nezapomeňte zkontrolovat dokumentaci. Vždy používejte příkaz plan a zvažte použití strategie create_before_destroy.

    Odložená konzistence je konzistentní... s odložením

    Některá rozhraní API poskytovatelů cloudu, jako je AWS, jsou asynchronní a mají zpožděnou konzistenci. Asynchronie znamená, že rozhraní může okamžitě vrátit odpověď, aniž by čekalo na dokončení požadované akce. Zpožděná konzistence znamená, že může nějakou dobu trvat, než se změny projeví v systému; zatímco k tomu dochází, vaše odpovědi mohou být nekonzistentní a závislé na tom, která replika zdroje dat odpovídá na vaše volání API.

    Představte si například, že zavoláte API do AWS a požádáte jej o vytvoření EC2 serveru. API vrátí „úspěšnou“ odpověď (201 Created) téměř okamžitě, aniž by čekalo na vytvoření samotného serveru. Pokud se k němu pokusíte připojit hned, téměř jistě selže, protože v tom okamžiku AWS stále inicializuje prostředky nebo alternativně se server ještě nespustil. Kromě toho, pokud provedete další volání, abyste získali informace o tomto serveru, může se zobrazit chyba (404 Nenalezeno). Jde o to, že informace o tomto EC2 serveru mohou být stále šířeny v AWS, než budou dostupné všude, budete muset počkat několik sekund.

    Kdykoli použijete asynchronní rozhraní API s opožděnou konzistencí, musíte požadavek pravidelně opakovat, dokud se akce nedokončí a nerozšíří se systémem. AWS SDK k tomu bohužel neposkytuje žádné dobré nástroje a projekt Terraform dříve trpěl mnoha chybami, jako je 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

    Jinými slovy, vytvoříte zdroj (jako podsíť) a pak se o něm pokusíte získat nějaké informace (jako ID nově vytvořené podsítě) a Terraform je nemůže najít. Většina těchto chyb (včetně 6813) byla opravena, ale stále se čas od času objevují, zvláště když Terraform přidává podporu pro nový typ zdroje. To je nepříjemné, ale ve většině případů nezpůsobuje žádnou škodu. Když znovu spustíte aplikaci terraform, vše by mělo fungovat, protože do této doby se informace již rozšířily po celém systému.

    Tento úryvek je uveden z knihy Jevgenije Brikmana "Terraform: infrastruktura na úrovni kódu".

Zdroj: www.habr.com

Přidat komentář