Upozorníme na několik úskalí, včetně těch, které se týkají cyklů, podmínkových výrazů a metod nasazení, stejně jako na obecnější problémy, které se týkají Terraformu jako celku:
- parametry count a for_each mají omezení;
- omezení nasazení s nulovým časem výpadku;
- i dobrý plán může selhat;
- refaktoring může mít svá úskalí;
- odložená konzistence souvisí… s odkladem.
Parametry count a for_each mají omezení
V příkladech této kapitoly jsou parametry count a výraz for_each aktivně využívány v cyklech a podmínkové logice. Dobře fungují, ale mají dvě důležitá omezení, o kterých je třeba vědět.
- V count a for_each nelze odkazovat na žádné výstupní proměnné zdroje.
- count a for_each nelze použít v konfiguraci modulu.
V count a for_each nelze odkazovat na žádné výstupní proměnné zdroje
Představte si, že je potřeba nasadit několik serverů EC2 a z nějakého důvodu nechcete použít ASG. Váš kód může vypadat takto:
resource "aws_instance" "example_1" {
count = 3
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t2.micro"
}
Podívejme se na ně postupně.
Jelikož je parametru count přiřazena statická hodnota, tento kód bude fungovat bez problémů: když spustíte příkaz apply, vytvoří tři servery EC2. Ale co když chcete nasadit jeden server v každé dostupné zóně (Availability Zone nebo AZ) v rámci aktuálního regionu AWS? Můžete udělat tak, aby váš kód načetl seznam zón ze zdroje dat aws_availability_zones a poté „cyklicky“ prošel každou z nich a vytvořil v ní server EC2 s použitím parametru count a přístupu k poli podle indexu:
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 také bude skvěle fungovat, 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? Abychom to předvedli, nejjednodušší je vzít zdroj random_integer, který, jak lze odtušit z názvu, vrací náhodné celé číslo:
resource "random_integer" "num_instances" {
min = 1
max = 3
}Tento kód generuje náhodné číslo od 1 do 3. Podíváme se, co se stane, když se pokusíme použít výstup result 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 provedete terraform plan pro tento kód, obdržíte následující chybu:
Error: Invalid count argument
on main.tf line 30, in resource "aws_instance" "example_3":
30: count = random_integer.num_instances.result
Hodnota "count" závisí na atributech zdroje, které nelze určit do doby aplikace, takže Terraform nemůže předpovědět, kolik instancí bude vytvořeno. Pro obcházení tohoto problému použijte argument -target k tomu, abyste nejprve aplikovali pouze zdroje, od kterých count závisí.Terraform vyžaduje, aby byly count a for_each vypočítány v plánovací fázi, před vytvořením nebo změnou jakýchkoli zdrojů. To znamená, že count a for_each mohou odkazovat na literály, proměnné, datové zdroje a dokonce seznamy zdrojů (pokud je jejich délku možné určit během plánování), ale nemohou odkazovat na vypočtené výstupní proměnné zdroje.
count a for_each nelze použít v konfiguraci modulu
Občas můžete mít pokušení přidat parametr count do konfigurací 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 count uvnitř modulu k vytvoření tří kopií zdroje webserver-cluster. Nebo možná budete chtít udělat připojení modulu volitelným v závislosti na nějaké logické podmínce tím, že přidělíte jeho parametru count hodnotu 0. Takový kód může vypadat celkem rozumně, avšak při provádění terraform plan dostanete tuto chybu:
Error: Reserved argument name in module block
on main.tf line 13, in module "count_example":
13: count = 3
Jméno "count" je rezervováno pro použití v budoucí verzi Terraform.Bohužel v době vydání Terraform 0.12.6 není podpora pro používání count nebo for_each ve zdroji module. Podle poznámek k vydání Terraform 0.12 (http://bit.ly/3257bv4) plánuje HashiCorp tuto funkčnost přidat v budoucnu, takže v závislosti na tom, kdy tuto knihu čtete, může již být dostupná. Pro ověření, .
Omezení nasazení s nulovým časem prostojů
Použití bloku create_before_destroy spolu s ASG je skvělým řešením pro organizaci nasazení s nulovým prostoje, pokud nepočítáme jednu drobnost: pravidla automatického škálování přitom nejsou podporována. Jinými slovy, to resetuje velikost ASG zpět na min_size při každém nasazení, což může být problém, pokud jste používali pravidla automatického škálování k navýšení počtu spuštěných serverů.
Například modul webserver-cluster obsahuje několik zdrojů aws_autoscaling_schedule, které v 9 hodin zvyšují počet serverů v clusteru z dvou na deset. Pokud provedete nasazení, dejme tomu, ve 11 hodin, nová skupina ASG se nenačte s deseti, ale pouze se dvěma servery a zůstane v tomto stavu až do 9 hodin následujícího dne.
Toto omezení lze obejít několika způsoby.
- Změnit parametr recurrence v aws_autoscaling_schedule z 0 9 * * * („spouštět v 9 hodin“) na něco jako 0-59 9-17 * * * („spouštět každou minutu od 9 hodin do 17 hodin“). Pokud je v ASG již deset serverů, opětovné provedení tohoto pravidla automatického škálování nic nezmění, což je přesně to, co potřebujeme. Ale pokud byla skupina ASG nasazena nedávno, toto pravidlo zaručuje, že maximálně za minutu dosáhne počet jejích serverů deseti. To není úplně elegantní přístup, a velké skoky z deseti na dva servery a zpět také mohou způsobit problémy uživatelům.
- Vytvořit vlastní skript, který používá API AWS pro zjištění počtu aktivních serverů v ASG, vyvolat ho pomocí externího zdroje dat (viz část „Externí zdroj dat“ na str. 249) a přiřadit parametru desired_capacity skupiny ASG hodnotu vrácenou tímto skriptem. Tak bude každý nový instance ASG vždy spouštěna se stejnou kapacitou, což komplikuje údržbu našeho Terraform kódu.
Samozřejmě, ideálně by měla být v Terraformu zabudovaná podpora nasazení s nulovým prostoje, ale k datu květen 2019 tým HashiCorp neplánoval přidat tuto funkčnost ().
Správný plán může být neúspěšně realizován
Někdy při provádění příkazu plan vznikne celkem správný plán nasazení, avšak příkaz apply vrátí chybu. Zkuste například přidat zdroj aws_iam_user se stejným názvem, který jste použili pro uživatele IAM, kterého jste vytvořili dříve v kapitole 2:
resource "aws_iam_user" "existing_user" {
# Zde vložte jméno již existujícího uživatele IAM,
# abyste si procvičili použití příkazu terraform import
name = "yevgeniy.brikman"
}Nyní, pokud spustíte příkaz plan, Terraform vyprodukuje na první pohled celkem rozumný plán nasazení:
Terraform provede následující akce:
# aws_iam_user.existing_user bude vytvořen
+ resource "aws_iam_user" "existing_user" {
+ arn = (známé po aplikaci)
+ force_destroy = false
+ id = (známé po aplikaci)
+ name = "yevgeniy.brikman"
+ path = "\/"
+ unique_id = (známé po aplikaci)
}
Plán: 1 k přidání, 0 ke změně, 0 k zničení.Pokud spustíte příkaz apply, dojde k následující chybě:
Error: Error creating IAM User yevgeniy.brikman: EntityAlreadyExists:
Uživatel s názvem yevgeniy.brikman již existuje.
na main.tf řádek 10, v resource "aws_iam_user" "existing_user":
10: resource "aws_iam_user" "existing_user" {Problém je samozřejmě v tom, že uživatel IAM se stejným názvem již existuje. A to se může stát nejen s uživateli IAM, ale i téměř jakýmkoli zdrojem. Možná někdo vytvořil tento zdroj ručně nebo pomocí příkazového řádku, ale ať už je to jakkoli, shoda identifikátorů vede ke konfliktům. Tato chyba má mnoho variant, které často překvapí nováčky v Terraformu.
Klíčovým bodem je, že příkaz terraform plan bere v úvahu pouze ty zdroje, které jsou uvedeny v souboru stavu Terraform. Pokud byly zdroje vytvořeny nějakým jiným způsobem (například ručně, kliknutím v konzoli AWS), nebudou zahrnuty do souboru stavu a proto je Terraform nebude brát v úvahu při provádění příkazu plan. V důsledku toho se zdánlivě správný plán může ukázat jako neúspěšný.
Z toho se dají vyvodit dvě ponaučení.
- Pokud jste již začali pracovat s Terraformem, nepoužívejte nic jiného. Pokud je část vaší infrastruktury spravována pomocí Terraformu, již ji nelze měnit ručně. Jinak nejenže riskujete podivné chyby Terraformu, ale také podkopáváte mnohé výhody IaC, protože kód již nebude přesným vyjádřením vaší infrastruktury.
- Pokud již máte nějakou infrastrukturu, použijte příkaz import. Pokud začínáte používat Terraform s již existující infrastrukturou, můžete ji přidat do souboru stavu pomocí příkazu terraform import. Tím Terraform zjistí, jakou infrastrukturou má spravovat. Příkaz import přijímá dva argumenty. Prvním je adresa zdroje ve vašich konfiguračních souborech. Zde platí stejná syntaxe, jako v odkazech na zdroje: _. (např. aws_iam_user.existing_user). Druhým argumentem je identifikátor zdroje, který chcete importovat. Například pro zdroj aws_iam_user slouží jako ID uživatelské jméno (např. yevgeniy.brikman), zatímco ID zdroje aws_instance bude identifikátor serveru EC2 (např. i-190e22e5). To, jak importovat zdroj, je obvykle uvedeno v dokumentaci na konci jeho stránky.
Níže je zobrazen příkaz import, který synchronizuje zdroj aws_iam_user, který jste přidali do své konfigurace Terraform spolu se uživatelským účtem IAM v kapitole 2 (samozřejmě místo yevgeniy.brikman zadejte své jméno):
$ terraform import aws_iam_user.existing_user yevgeniy.brikmanTerraform se obrátí na API AWS, aby našel vašeho uživatele IAM a vytvořil v souboru stavu vazbu mezi ním a zdrojem aws_iam_user.existing_user ve vaší konfiguraci Terraform. Od tohoto okamžiku, když vykonáte příkaz plan, bude Terraform vědět, že uživatel IAM již existuje, a nebude se pokoušet o jeho opětovné vytvoření.
Je třeba poznamenat, že pokud máte již mnoho zdrojů, které chcete importovat do Terraformu, psaní kódu ručně a import každého z nich po jednom může být únavná činnost. Proto stojí za to se podívat na nástroj, jako je Terraforming (http://terraforming.dtan4.net/), který může automaticky importovat kód a stav z účtu AWS.
Refaktoring může mít své nástrahy
Refaktoring — běžná praxe v programování, kdy měníte vnitřní strukturu kódu, aniž byste změnili jeho vnější chování. To je nutné k tomu, aby byl kód srozumitelnější, čistší a snadněji udržovatelný. Refaktoring je nezbytná metodika, kterou je třeba pravidelně používat. Avšak když jde o Terraform nebo jakýkoli jiný nástroj IaC, je třeba být velmi opatrný, jak se chápe 'vnější chování' části kódu, jinak mohou vzniknout nečekané problémy.
Například běžný typ refaktoringu spočívá ve změně názvů proměnných nebo funkcí na srozumitelnější. Mnoho IDE má vestavěnou podporu pro refaktoring a mohou automaticky přejmenovávat proměnné a funkce v celém projektu. U jazyků obecného použití je to triviální procedura, nad kterou se příliš nezamýšlí, avšak v Terraformu je třeba dbát extrémní opatrnosti, jinak můžete narazit na problémy s funkčností.
Například modul webserver-cluster má vstupní proměnnou cluster_name:
variable "cluster_name" { description = "Název, který se použije pro všechny zdroje clusteru" type = string }Představte si, že jste začali používat tento modul pro nasazení mikroslužby s názvem foo. Později jste se rozhodli přejmenovat svou službu na bar. Tato změna se může zdát triviální, ale ve skutečnosti mohou vzniknout problémy s funkčností.
Problém je v tom, že modul webserver-cluster používá proměnnou cluster_name v celé řadě zdrojů, včetně parametru name 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 name v nějakém zdroji, Terraform odstraní starou verzi tohoto zdroje a vytvoří místo ní novou. Ale pokud je tímto zdrojem ALB, v období mezi jeho odstraněním a načtením nové verze nebudete mít mechanismus pro přesměrování provozu k vašemu webovému serveru. Podobně, pokud je odstraněna bezpečnostní skupina, 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 identifikátoru Terraform. Uvažujme například o zdroji aws_security_group v modulu webserver-cluster:
resource "aws_security_group" "instance" { # (...) }Identifikátor tohoto zdroje se nazývá instance. Představte si, že během refaktoringu jste se rozhodli změnit ho na srozumitelnější (podle vašeho názoru) název cluster_instance:
resource "aws_security_group" "cluster_instance" { # (...) }Co se nakonec stane? Správně: dojde k výpadku.
Terraform propojuje ID každého zdroje s identifikátorem poskytovatele cloudu. Například iam_user se váže na ID uživatele IAM v AWS, zatímco aws_instance na ID serveru AWS EC2. Pokud změníte identifikátor zdroje (řekněme, z instance na cluster_instance, jak je to v případě aws_security_group), pro Terraform to bude vypadat, jako byste starý zdroj odstranili a přidali nový. Pokud tyto změny použijete, Terraform odstraní starou bezpečnostní skupinu a vytvoří novou, přičemž vaši servery začnou odmítat veškerý síťový provoz.
Zde jsou čtyři hlavní lekce, které byste si měli z této diskuse odnést.
- Vždy používejte příkaz plan. Pomůže vám identifikovat všechny tyto problémy. Pečlivě si prohlédněte jeho výstup a věnujte pozornost situacím, kdy Terraform plánuje odstranit zdroje, které pravděpodobně není dobré mazat.
- Vytvářejte, než odstraníte. Pokud chcete zdroj nahradit, dobře zvažte, zda je nutné vytvořit náhradu před odstraněním originálu. Pokud je odpověď kladná, může vám pomoci create_before_destroy. Stejného výsledku lze dosáhnout ručně, když nejprve přidáte do konfigurace nový zdroj a spustíte příkaz apply, a poté odstraníte ze konfigurace starý zdroj a znovu použijete příkaz apply.
- Změna identifikátorů vyžaduje změnu stavu. Pokud chcete změnit identifikátor spojený se zdrojem (např. přejmenovat aws_security_group z instance na cluster_instance), a přitom se vyhnout odstranění zdroje a vytvoření jeho nové verze, je nutné odpovídajícím způsobem aktualizovat soubor stavu Terraform. Nikdy to nedělejte ručně — místo toho použijte příkaz terraform state. Při přejmenovávání identifikátorů byste měli použít příkaz terraform state mv, který má následující syntaxi:
terraform state mvORIGINAL_REFERENCE je výraz, který odkazuje na zdroj v jeho současné podobě, a NEW_REFERENCE je místo, kam ho chcete přesunout. Například při přejmenování skupiny aws_security_group z instance na cluster_instance byste měli provést následující příkaz:
$ terraform state mv aws_security_group.instance aws_security_group.cluster_instanceTímto sdělíte Terraformu, že stav, který se předtím vztahoval k aws_security_group.instance, by měl nyní být spojen s aws_security_group.cluster_instance. Pokud po přejmenování a spuštění tohoto příkazu terraform plan neukáže žádné změny, udělali jste vše správně.
- Některé parametry není možné měnit. Parametry mnoha zdrojů jsou neměnné. Pokud se je pokusíte změnit, Terraform odstraní starý zdroj a vytvoří nový. Na stránce každého zdroje obvykle zjistíte, co se stane při změně daného parametru, proto nezapomínejte konzultovat dokumentaci. Vždy používejte příkaz plan a zvažte vhodnost strategie create_before_destroy.
Opožděná konzistence se shoduje… s odkladem
API některých cloudových poskytovatelů, jako je AWS, jsou asynchronní a mají opožděnou konzistenci. Asynchronnost znamená, že rozhraní může okamžitě vrátit odpověď, aniž by čekalo na dokončení požadované akce. Opožděná konzistence znamená, že může trvat nějakou dobu, než se změny rozšíří po celém systému; během této doby mohou být vaše odpovědi nekonzistentní a závislé na tom, který replikátor datového zdroje reaguje na vaše API výzvy.
Představte si například, že děláte API výzvu na AWS s žádostí o vytvoření serveru EC2. API vrátí "úspěšnou" odpověď (201 Created) téměř okamžitě, aniž by čekalo na vytvoření samotného serveru. Pokud se pokusíte připojit ihned, téměř určitě nic nefunguje, protože AWS stále inicializuje zdroje, nebo je server ještě nenačtený. Navíc, pokud provedete další výzvu pro získání informací o tomto serveru, může přijít chyba (404 Not Found). Skutečností je, že informace o tomto serveru EC2 se stále mohou šířit po AWS, a aby byly dostupné všude, budete muset chvíli počkat.
Při každém použití asynchronního API s opožděnou konzistencí byste měli pravidelně opakovat svou žádost, dokud akce nebude dokončena a nebude rozšířena po systému. Bohužel, AWS SDK pro to nenabízí žádné dobré nástroje, a projekt Terraform dříve trpěl mnoha chybami jako 6813 (https://github.com/hashicorp/terraform/issues/6813):
$ terraform apply aws_subnet.private-persistence.2: InvalidSubnetID.NotFound: ID podítě 'subnet-xxxxxxx' neexistujeJinými slovy, vytváříte zdroj (například podsíť) a poté se pokoušíte získat o něm nějaké informace (například ID právě vytvořené podsítě), ale Terraform je nemůže najít. Většina těchto chyb (včetně 6813) již byla opravena, ale občas se stále objevují, zejména když Terraform přidává podporu pro nový typ zdrojů. Je to frustrující, ale ve většině případů to nezpůsobuje žádné škody. Při opakovaném spuštění terraform apply by to mělo fungovat, protože do té doby již bude informace rozšířena po systému.
Tento úryvek pochází z knihy Jevgenije Brikmana .
Zdroj: habr.com
