Нека да подчертаем няколко подводни камъка, включително свързаните с цикли, условни изрази и методи на внедряване, както и по-общи проблеми, които се отнасят до Terraform като цяло:
- параметрите count и for_each имат ограничения;
- ограничения за внедрения без време за престой;
- дори добър план може да се окаже неуспешен;
- рефакторингът може да има свои капани;
- отложената последователност съвпада... с отлагането.
Параметрите count и for_each имат ограничения
В примерите от тази глава параметрът count и изразът for_each активно се прилагат в цикли и условна логика. Те показват добри резултати, но имат два важни ограничения, за които трябва да знаете.
- Не може да се направи референция към изходни променливи на ресурсите в count и for_each.
- Не може да се използват count и for_each в конфигурацията на модула.
Не може да се направи референция към изходни променливи на ресурсите в count и for_each.
Представете си, че трябва да внедрите няколко EC2 сървъра и по някаква причина не искате да използвате ASG. Вашият код може да изглежда така:
resource "aws_instance" "example_1" {
count = 3
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t2.micro"
}
Нека ги разгледаме поотделно.
Тъй като параметърът count има статична стойност, този код ще работи без проблеми: когато изпълните командата apply, той ще създаде три EC2 сървъра. Но какво ако искате да внедрите по един сървър във всяка зона на достъп (Availability Zone или AZ) в текущия регион на AWS? Можете да настроите кода си да зареди списък от зони от данни aws_availability_zones и след това да обиколи всяка от тях и да създаде EC2 сървър, използвайки параметър count и достъп до масива по индекс:
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" {}Този код също ще работи прекрасно, тъй като параметърът count може без проблем да реферира към източниците на данни. Но какво ще стане, ако броят на сървърите, които трябва да създадете, зависи от изхода на някакъв ресурс? За да демонстрираме това, е най-лесно да вземем ресурс random_integer, който, както може да се предположи от името, връща случаен цяло число:
resource "random_integer" "num_instances" {
min = 1
max = 3
}Този код генерира случайно число от 1 до 3. Нека видим какво ще се случи, ако се опитаме да използваме резултата result на този ресурс в параметъра count на ресурса aws_instance:
resource "aws_instance" "example_3" {
count = random_integer.num_instances.result
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t2.micro"
}Ако изпълните terraform plan за този код, ще получите следната грешка:
Грешка: Невалиден аргумент count
на main.tf ред 30, в ресурс "aws_instance" "example_3":
30: count = random_integer.num_instances.result
Стойността "count" зависи от атрибутите на ресурса, които не могат да се определят до прилагане, така че Terraform не може да предвиди колко инстанции ще бъдат създадени. За да заобиколите това, използвайте аргумента -target, за да приложите първо само ресурсите, от които зависи count.Terraform изисква count и for_each да бъдат изчислени на етапа на планиране, преди да бъдат създадени или променени каквито и да е ресурси. Това означава, че count и for_each могат да се отнасят за литерали, променливи, източници на данни и дори списъци с ресурси (при условие, че дължината им може да се определи по време на планирането), но не и за изчисляеми изходни променливи на ресурса.
count и for_each не могат да се използват в конфигурацията на модула
В един момент можете да почувствате изкушение да добавите параметър count в конфигурациите на модула:
module "count_example" {
source = "..\/..\/..\/..\/modules\/services\/webserver-cluster"
count = 3
cluster_name = "terraform-up-and-running-example"
server_port = 8080
instance_type = "t2.micro"
}Този код се опитва да използва count вътре в модула, за да създаде три копия на ресурса webserver-cluster. Или, може би, искате да направите свързването на модула опционално в зависимост от булево условие, задавайки параметъра count на стойност 0. Такива код ще изглежда напълно разумно, но при изпълнението на terraform plan ще получите следната грешка:
Грешка: Запазено име на аргумент в модулния блок
на main.tf ред 13, в модул "count_example":
13: count = 3
Името "count" е запазено за използване в бъдеща версия на Terraform.За съжаление, към момента на излизане на Terraform 0.12.6 използването на count или for_each в ресурса module не се поддържа. Според бележките за издаване на Terraform 0.12 (http://bit.ly/3257bv4) компанията HashiCorp планира да добави тази функционалност в бъдеще, така че в зависимост от момента, в който четете тази книга, тя вече може да бъде налична. За да разберете със сигурност, .
Ограничения при разполагания с нулево време на престой
Използването на блока create_before_destroy в комбинация с ASG е отлично решение за организиране на разгръщания с нулево време на неизползване, с едно забележимо изключение: правилата за автоматично мащабиране не се поддържат. Или, ако бъдем по-точни, това нулира размера на ASG обратно до min_size при всяко разгръщане, което може да създаде проблем, ако сте използвали правила за автоматично мащабиране, за да увеличите броя на активните сървъри.
Например, модулът webserver-cluster съдържа два ресурса aws_autoscaling_schedule, които в 9:00 увеличават броя на сървърите в клъстера от два до десет. Ако разгръщането се извърши, да речем, в 11:00, новата група ASG няма да се зареди с десет, а само с два сървъра и ще остане в това състояние до 9:00 следващия ден.
Това ограничение може да бъде заобиколено по няколко начина.
- Да смените параметъра recurrence в aws_autoscaling_schedule от 0 9 * * * ("стартиране в 9:00") на нещо като 0-59 9-17 * * * ("стартиране всяка минута от 9:00 до 17:00"). Ако в ASG вече има десет сървъра, повторното прилагане на това правило за автоматично мащабиране няма да промени нищо, което точно искаме. Но ако групата ASG е разгръщана съвсем наскоро, това правило гарантира, че максимум след минута броят на сървърите ѝ ще достигне десет. Това не е много елегантен подход, а значителните колебания от десет до два сървъра и обратно също могат да предизвикат проблеми у потребителите.
- Да се създаде потребителски скрипт, който прилага API на AWS за определяне на броя на активните сървъри в ASG, да го извикате с помощта на външен източник на данни (вижте раздела "Външен източник на данни" на стр. 249) и да зададете на параметъра desired_capacity на групата ASG стойността, върната от този скрипт. По този начин, всеки нов екземпляр на ASG винаги ще се стартира със същата мощност, което усложнява обслужването на кода ни в Terraform.
Разбира се, в идеалния случай в Terraform трябва да има вградена поддръжка за разгръщания с нулево време на неизползване, но към май 2019 година екипът на HashiCorp не планираше да добавя тази функционалност ().
Коректният план може да бъде неуспешно реализиран
Понякога, когато изпълните команда plan, получавате изцяло коректен план за разгръщане, но командата apply дава грешка. Опитайте, например, да добавите ресурс aws_iam_user със същото име, което сте използвали за IAM потребителя, създаден от вас по-рано в глава 2:
resource "aws_iam_user" "existing_user" {
# Въведете името на вече съществуващия IAM потребител,
# за да се упражните в използването на командата terraform import
name = "yevgeniy.brikman"
}Сега, ако изпълните командата plan, Terraform ще изведе на пръв поглед напълно разумен план за разгръщане:
Terraform ще извърши следните действия:
# aws_iam_user.existing_user ще бъде създаден
+ resource "aws_iam_user" "existing_user" {
+ arn = (известно след прилагане)
+ force_destroy = false
+ id = (известно след прилагане)
+ name = "yevgeniy.brikman"
+ path = "\/"
+ unique_id = (известно след прилагане)
}
План: 1 за добавяне, 0 за промяна, 0 за унищожаване.Ако изпълните командата apply, ще получите следната грешка:
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" {Проблемът, разбира се, е, че IAM потребител с такова име вече съществува. И това може да се случи не само с IAM потребители, но и почти с всеки ресурс. Възможно е някой да е създал този ресурс ръчно или с помощта на командния ред, но независимо от обстоятелствата, съвпадението на идентификаторите води до конфликти. Тази грешка има много разновидности, които често изненадват начинаещите в Terraform.
Ключовият момент е, че командата terraform plan взема предвид само ресурсите, които са посочени в файла на състоянието на Terraform. Ако ресурсите са създадени по друг начин (например ръчно, с кликване на бутона в консолата на AWS), те няма да попаднат в файла на състоянието и следователно Terraform няма да ги вземе предвид при изпълнение на командата plan. В резултат на това, на пръв поглед коректният план ще се окаже неуспешен.
От това могат да се извлекат два урока.
- Ако вече сте започнали работа с Terraform, не използвайте нищо друго. Ако част от вашата инфраструктура се управлява с помощта на Terraform, не трябва да я променяте ръчно. В противен случай не само, че рискувате да получите странни грешки в Terraform, но също така отнемате много предимства на IaC, тъй като кодът вече няма да е точно представление на вашата инфраструктура.
- Ако вече имате някаква инфраструктура, използвайте командата import. Ако започвате да използвате Terraform с вече съществуваща инфраструктура, можете да я добавите към файла със състояние с помощта на командата terraform import. Така Terraform ще знае каква инфраструктура трябва да управлява. Командата import приема два аргумента. Първият служи за адрес на ресурса във вашите конфигурационни файлове. Тук синтаксисът е същият като при линковете към ресурси: _. (като aws_iam_user.existing_user). Вторият аргумент е идентификаторът на ресурса, който трябва да бъде импортиран. Например, за ID на ресурса aws_iam_user той е името на потребителя (като yevgeniy.brikman), а ID на ресурса aws_instance ще бъде идентификаторът на сървъра EC2 (като i-190e22e5). Как да импортирате ресурс обикновено е посочено в документацията в долната част на страницата му.
По-долу е показана командата import, която позволява да синхронизирате ресурса aws_iam_user, който добавихте във вашата конфигурация Terraform заедно с потребителя IAM в глава 2 (разбира се, вместо yevgeniy.brikman трябва да поставите вашето име):
$ terraform import aws_iam_user.existing_user yevgeniy.brikmanTerraform ще се свърже с AWS API, за да намери вашия потребител IAM и да създаде връзка в файла със състояние между него и ресурса aws_iam_user.existing_user във вашата конфигурация Terraform. От този момент, когато изпълнявате командата plan, Terraform ще знае, че потребителят IAM вече съществува и няма да се опитва да го създаде отново.
Следва да се отбележи, че ако вече имате много ресурси, които искате да импортирате в Terraform, ръчното писане на код и импортирането на всеки от тях поотделно може да се окаже трудоемко. Затова е разумно да се обърнете към инструмент като Terraforming (http://terraforming.dtan4.net/), който може автоматично да импортира код и състояние от вашия AWS акаунт.
Рефакторирането може да има своите капани
Рефактиране е разпространена практика в програмирането, когато променяте вътрешната структура на кода, оставяйки външното поведение непроменено. Това е необходимо, за да стане кодът по-разбираем, подреден и лесен за поддръжка. Рефакторирането е незаменима методика, която трябва да се прилага редовно. Но, когато става въпрос за Terraform или други средства за IaC, трябва да бъдете изключително внимателни по отношение на това, какво точно се има предвид под „външното поведение“ на участъка код, в противен случай могат да възникнат непредвидими проблеми.
Например, единствената често срещана форма на рефакторинг е подмяна на имената на променливи или функции с по-ясни. Много IDE разполагат с вградена поддръжка за рефакторинг и могат автоматично да преименуват променливи и функции в целия проект. В езиците за общо предназначение това е тривиална процедура, за която не е нужно да се замисляте, но в Terraform с това трябва да сте изключително внимателни, иначе може да се сблъскате с прекъсване в работата.
Например, модулът webserver-cluster разполага с входяща променлива cluster_name:
variable "cluster_name" { description = "Името, което да се използва за всички ресурси на клъстера" type = string }Представете си, че започвате да използвате този модул за разгръщане на микросервиз с име foo. По-късно решавате да преименувате услугата си на bar. Това изменение може да изглежда тривиално, но в реалността може да причини прекъсвания.
Причината е, че модулът webserver-cluster използва променливата cluster_name в множество ресурси, включително параметър name за две групи за сигурност и 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] }Ако промените параметъра name в някой ресурс, Terraform ще изтрие старата версия на този ресурс и ще създаде нова. Но ако такъв ресурс е ALB, в периода между изтриването му и зареждането на новата версия, няма да разполагате с механизъм за пренасочване на трафика към вашия уеб сървър. По същия начин, ако бъде изтрита група за сигурност, вашите сървъри ще започнат да отказват всеки мрежов трафик, докато не бъде създадена нова група.
Друг вид рефакторинг, който може да ви заинтересува, е промяната на идентификатора на Terraform. Нека да вземем за пример ресурса aws_security_group в модула webserver-cluster:
resource "aws_security_group" "instance" { # (...) }Идентификаторът на този ресурс се нарича instance. Представете си, че по време на рефакторинг решавате да го промените на по-ясно (според вас) име cluster_instance:
resource "aws_security_group" "cluster_instance" { # (...) }Какво ще се случи в крайна сметка? Правилно: прекъсване на работа.
Terraform свързва ID на всеки ресурс с идентификатора на облачния доставчик. Например, iam_user се свързва с идентификатора на потребителя IAM в AWS, а aws_instance — с ID на сървъра AWS EC2. Ако промените идентификатора на ресурса (да речем, от instance на cluster_instance, какъвто е случаят с aws_security_group), за Terraform това ще изглежда като да сте изтрили стария ресурс и добавили нов. Когато приложите тези промени, Terraform ще изтрие старата група за сигурност и ще създаде нова, а междувременно вашите сървъри ще започнат да отхвърлят всякакъв мрежов трафик.
Ето четири основни урока, които трябва да извлечете от това обсъждане.
- Винаги използвайте командата plan. С нея можете да откриете всички тези проблеми. Внимателно преглеждайте изхода й и следете ситуациите, в които Terraform планира да изтрие ресурси, които вероятно не трябва да бъдат изтрити.
- Създавайте, преди да изтриете. Ако искате да замените ресурс, внимателно обмислете дали трябва да създадете заместител преди да изтриете оригинала. Ако отговорът е положителен, в това може да помогне create_before_destroy. Същият резултат можете да постигнете ръчно, като извършите два стъпки: първо добавете новия ресурс в конфигурацията и стартирайте командата apply, а след това изтрийте стария ресурс от конфигурацията и използвайте командата apply отново.
- Промяната на идентификатори изисква промяна на състоянието. Ако искате да промените идентификатора, свързан с ресурса (например, да преименувате aws_security_group от instance на cluster_instance), като избягвате изтриването на ресурса и създаването на нова версия, е необходимо да актуализирате файла за състояние на Terraform по съответния начин. Никога не правете това ръчно — вместо това използвайте командата terraform state. При преименуването на идентификатори трябва да изпълните командата terraform state mv, която има следния синтаксис:
terraform state mvОригиналната_Референция е израз, който сочи към ресурса в текущия му вид, а Новата_Референция е мястото, на което искате да го преместите. Например, при преименуването на групата aws_security_group от instance на cluster_instance трябва да изпълните следната команда:
$ terraform state mv aws_security_group.instance aws_security_group.cluster_instanceТака ще кажете на Terraform, че състоянието, което преди е било свързано с aws_security_group.instance, сега трябва да бъде свързано с aws_security_group.cluster_instance. Ако след преименуването и изпълнението на тази команда terraform plan не покаже промени, значи всичко е направено правилно.
- Някои параметри не могат да бъдат променяни. Параметрите на множество ресурси са непроменими. Ако опитате да ги промените, Terraform ще изтрие стария ресурс и ще създаде нов вместо него. На страницата на всеки ресурс обикновено се посочва какво се случва при промяна на определен параметър, така че не забравяйте да проверите документацията. Винаги използвайте командата plan и обмисляйте целесъобразността на стратегията create_before_destroy.
Отложената консистентност е… в съответствие с отлагането
API на някои облачни доставчици, като AWS, са асинхронни и имат отложена консистентност. Асинхронността означава, че интерфейсът може веднага да върне отговор, без да изчаква завършването на заявеното действие. Отложената консистентност означава, че разпространението на промените из цялата система може да отнеме време; докато това се случва, вашите отговори могат да бъдат неконсистентни и да зависят от това, коя реплика на източника на данни отговаря на вашите API повиквания.
Представете си, че правите API повикване към AWS с искане за създаване на EC2 сървър. API ще върне "успешен" отговор (201 Created) почти веднага, без да изчаква самото сървърно създаване. Ако веднага опитате да се свържете с него, почти сигурно няма да успеете, тъй като в този момент AWS все още инициализира ресурсите или, като вариант, сървърът все още не е стартирал. Освен това, ако направите още едно повикване, за да получите информация за този сървър, може да получите грешка (404 Not Found). Факт е, че информацията за този EC2 сървър все още може да се разпространява в AWS и за да стане достъпна навсякъде, ще трябва да изчакате няколко секунди.
При всяко използване на асинхронно API с отложена консистентност трябва периодично да повтаряте запитването си, докато действието не завърши и не се разпространи в системата. За съжаление, AWS SDK не предлага добри инструменти за това и проектът Terraform първоначално е страдал от много грешки, подобни на 6813 (https://github.com/hashicorp/terraform/issues/6813):
$ terraform apply aws_subnet.private-persistence.2: InvalidSubnetID.NotFound: Subnet ID 'subnet-xxxxxxx' не съществуваС други думи, създавате ресурс (например подсет) и след това се опитвате да получите информация за него (като ID на току-що създадената подсет), а Terraform не може да я намери. Повечето от тези грешки (включително 6813) вече са поправени, но от време на време все още се появяват, особено когато в Terraform добавят поддръжка за нов тип ресурси. Това е досадно, но в повечето случаи не носи никаква вреда. При повторно изпълнение на terraform apply всичко трябва да проработи, тъй като до този момент информацията вече ще е разпространена из системата.
Този откъс е взет от книгата на Евгений Брикман .
Източник: habr.com
