Тераформни стапици

Тераформни стапици
Ајде да истакнеме неколку замки, вклучително и оние поврзани со јамките, ако изјавите и техниките за распоредување, како и поопшти прашања што влијаат на Terraform воопшто:

  • параметрите 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 е поставен на статична вредност, овој код ќе работи без проблеми: кога ќе ја извршите командата примени, ќе создаде три EC2 сервери. Но, што ако сакате да распоредите еден сервер во секоја зона на достапност (AZ) во вашиот сегашен AWS регион? Може да го поставите вашиот код да вчита листа на зони од изворот на податоци aws_availability_zones, а потоа да ја превртите секоја од нив и да создадете сервер EC2 во него користејќи го параметарот за броење и пристапот до индексот на низата:

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

Овој код исто така ќе работи добро, бидејќи параметарот за броење може да упатува на извори на податоци без никакви проблеми. Но, што се случува ако бројот на сервери што треба да ги креирате зависи од излезот на некој ресурс? За да се покаже ова, најлесниот начин е да се користи ресурсот random_integer, кој, како што сугерира името, враќа случаен цел број:

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

Овој код генерира случаен број помеѓу 1 и 3. Ајде да видиме што ќе се случи ако се обидеме да го користиме излезот од овој ресурс во параметарот count на ресурсот aws_instance:

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

Ако извршите тераформен план на овој код, ќе ја добиете следнава грешка:

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 бара count и for_every да се пресметаат во фазата на планирање, пред да се создадат или изменат какви било ресурси. Ова значи дека count и for_each може да се однесуваат на буквали, променливи, извори на податоци, па дури и списоци со ресурси (додека нивната должина може да се одреди во времето на закажување), но не и на променливи за излез од пресметани ресурси.

count и for_each не може да се користат во конфигурацијата на модулот

Еден ден може да бидете во искушение да додадете параметар за броење во конфигурацијата на вашиот модул:

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

     count = 3

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

Овој код се обидува да користи брои во модул за да создаде три копии од ресурсот на веб-сервер-кластерот. Или можеби ќе сакате да го направите поврзувањето на модул опционално врз основа на некоја Булова состојба со поставување на неговиот параметар за броење на 0. Ова може да изгледа како разумен код, но ќе ја добиете оваа грешка при извршување на тераформниот план:

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.

За жал, од Terraform 0.12.6, користењето count или for_each во ресурсот на модулот не е поддржано. Според белешките за издавање на Terraform 0.12 (http://bit.ly/3257bv4), HashiCorp планира да ја додаде оваа способност во иднина, па во зависност од тоа кога ќе ја прочитате оваа книга, таа може веќе да биде достапна. За да дознаете сигурно, прочитајте го дневникот за промени на Terraform овде.

Ограничувања на нула распоредувања во прекин

Користењето на блокот create_before_destroy во комбинација со ASG е одлично решение за создавање распоредувања со нула прекини, освен едно предупредување: правилата за автоматско скалирање не се поддржани. Или да бидам попрецизен, ова ја ресетира големината на ASG на min_size при секое распоредување, што може да биде проблем ако користите правила за автоматско скалирање за да го зголемите бројот на сервери што работат.

На пример, модулот веб-сервер-кластер содржи пар ресурси aws_autoscaling_schedule, кои во 9 часот го зголемуваат бројот на сервери во кластерот од два на десет. Ако се распоредите, да речеме, во 11 часот, новиот ASG ќе се подигне со само два сервери наместо десет и ќе остане така до 9 часот наредниот ден.

Ова ограничување може да се заобиколи на неколку начини.

  • Променете го параметарот за повторување во aws_autoscaling_schedule од 0 9 * * * („трчајте во 9 часот“) на нешто како 0-59 9-17 * * * („трчајте секоја минута од 9 до 5 часот“). Ако ASG веќе има десет сервери, повторното извршување на ова правило за автоматско скалирање нема да промени ништо, што е она што го сакаме. Но, ако ASG неодамна е распореден, ова правило ќе гарантира дека за максимум една минута бројот на неговите сервери ќе достигне десет. Ова не е сосема елегантен пристап, а големите скокови од десет на два сервери и назад исто така може да предизвикаат проблеми за корисниците.
  • Создадете приспособена скрипта што користи AWS API за да го одреди бројот на активни сервери во ASG, повикајте го со помош на надворешен извор на податоци (видете „Надворешен извор на податоци“ на страница 249) и поставете го параметарот посакувана_капацитет на ASG на вредноста вратена од скриптата. На овој начин, секој нов примерок на ASG секогаш ќе работи со истиот капацитет како и постоечкиот Terraform код и го отежнува одржувањето.

Се разбира, Terraform идеално би имал вградена поддршка за распоредувања со нула прекини, но од мај 2019 година, тимот на HashiCorp немаше планови да ја додаде оваа функционалност (детали - овде).

Точниот план може да биде неуспешно спроведен

Понекогаш командата plan произведува совршено правилен план за распоредување, но командата примена враќа грешка. Обидете се, на пример, да го додадете ресурсот aws_iam_user со истото име што го користевте за корисникот на IAM што го создадовте претходно во Поглавје 2:

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

Сега, ако ја извршите командата за план, Terraform ќе издаде навидум разумен план за распоредување:

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.

Ако ја извршите командата примена, ќе ја добиете следнава грешка:

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 нема да ги земе предвид при извршувањето на командата за план. Како резултат на тоа, планот што изгледа точен на прв поглед ќе испадне неуспешен.

Од ова може да се извлечат две лекции.

  • Ако веќе сте почнале да работите со Terraform, не користете ништо друго. Ако дел од вашата инфраструктура се управува со Terraform, повеќе не можете да ја менувате рачно. Во спротивно, не само што ризикувате чудни грешки на Terraform, туку и негирате многу од придобивките на IaC бидејќи кодот повеќе нема да биде точна претстава за вашата инфраструктура.
  • Ако веќе имате некоја инфраструктура, користете ја командата за увоз. Ако почнувате да го користите Terraform со постоечката инфраструктура, можете да го додадете во државната датотека користејќи ја командата за увоз на terraform. На овој начин Terraform ќе знае со каква инфраструктура треба да управува. Командата за увоз зема два аргументи. Првата е адресата на ресурсот во вашите конфигурациски датотеки. Синтаксата овде е иста како и за врските со ресурси: _. (како aws_iam_user.existing_user). Вториот аргумент е ID на ресурсот што треба да се увезе. Да речеме дека ID на ресурс aws_iam_user е корисничкото име (на пример, yevgeniy.brikman), а ID на ресурсот aws_instance е ИД на серверот EC2 (како i-190e22e5). Како да увезете ресурс обично е наведено во документацијата на дното на нејзината страница.

    Подолу е команда за увоз што го синхронизира ресурсот aws_iam_user што го додадовте во вашата конфигурација на Terraform заедно со корисникот на IAM во Поглавје 2 (се разбира, заменувајќи го вашето име за yevgeniy.brikman):

    $ terraform import aws_iam_user.existing_user yevgeniy.brikman

    Terraform ќе го повика AWS API за да го пронајде вашиот корисник на IAM и да создаде асоцијација на државна датотека помеѓу него и ресурсот aws_iam_user.existing_user во вашата конфигурација на Terraform. Отсега натаму, кога ќе ја извршите командата plan, Terraform ќе знае дека корисникот на IAM веќе постои и нема да се обидува повторно да го креира.

    Вреди да се напомене дека ако веќе имате многу ресурси што сакате да ги увезете во Terraform, рачно пишување на кодот и увоз на секој еден по еден може да биде мака. Затоа, вреди да се погледне алатка како Terraforming (http://terraforming.dtan4.net/), која може автоматски да увезува код и состојба од вашата сметка AWS.

    Рефакторирањето може да има свои стапици

    Рефакторирање е вообичаена практика во програмирањето каде што ја менувате внатрешната структура на кодот додека го оставате надворешното однесување непроменето. Ова е за да го направи кодот појасен, уреден и полесен за одржување. Рефакторирањето е незаменлива техника која треба редовно да се користи. Но, кога станува збор за Terraform или која било друга IaC алатка, мора да бидете исклучително внимателни што подразбирате под „надворешното однесување“ на парче код, инаку ќе се појават неочекувани проблеми.

    На пример, вообичаен тип на рефакторирање е замена на имињата на променливите или функциите со поразбирливи. Многу IDE имаат вградена поддршка за рефакторирање и можат автоматски да ги преименуваат променливите и функциите низ целиот проект. Во програмските јазици за општа намена, ова е тривијална процедура за која можеби нема да размислувате, но во Terraform треба да бидете исклучително внимателни со ова, инаку може да доживеете прекини.

    На пример, модулот веб-сервер-кластер има влезна променлива cluster_name:

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

    Замислете дека сте почнале да го користите овој модул за да распоредите микросервис наречен foo. Подоцна, сакате да ја преименувате вашата услуга во лента. Оваа промена може да изгледа тривијална, но во реалноста може да предизвика прекини на услугата.

    Факт е дека модулот веб-сервер-кластер ја користи променливата cluster_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]
    }

    Ако го промените параметарот за името на некој ресурс, Terraform ќе ја избрише старата верзија на тој ресурс и ќе создаде нова на нејзино место. Но, ако тој ресурс е ALB, помеѓу неговото бришење и преземањето нова верзија, нема да имате механизам да го пренасочите сообраќајот кон вашиот веб-сервер. Исто така, ако се избрише безбедносна група, вашите сервери ќе почнат да одбиваат мрежен сообраќај додека не се создаде нова група.

    Друг тип на рефакторирање што можеби ве интересира е менувањето на Terraform ID. Ајде да го земеме ресурсот aws_security_group во модулот за веб-сервер-кластер како пример:

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

    Идентификаторот на овој ресурс се нарекува пример. Замислете дека за време на рефакторирањето решивте да го промените во поразбирливо (според вас) име cluster_instance:

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

    Што ќе се случи на крајот? Така е: нарушување.

    Terraform го поврзува секој ID на ресурс со ИД на давателот на облак. На пример, iam_user е поврзан со корисничкиот ID на AWS IAM, а aws_instance е поврзан со ID на серверот AWS EC2. Ако го промените ID на ресурсот (да речеме од пример во cluster_instance, како што е случајот со aws_security_group), во Terraform ќе изгледа како да сте го избришале стариот ресурс и сте додале нов. Ако ги примените овие промени, Terraform ќе ја избрише старата безбедносна група и ќе создаде нова, додека вашите сервери ќе почнат да одбиваат каков било мрежен сообраќај.

    Еве четири клучни лекции што треба да ги земете од оваа дискусија.

    • Секогаш користете ја командата план. Може да ги открие сите овие замки. Внимателно прегледајте го неговиот излез и обрнете внимание на ситуациите каде што Terraform планира да ги избрише ресурсите кои најверојатно не треба да се избришат.
    • Креирајте пред да избришете. Ако сакате да замените ресурс, размислете добро дали треба да креирате замена пред да го избришете оригиналот. Ако одговорот е да, create_before_destroy може да помогне. Истиот резултат може да се постигне рачно со извршување на два чекори: прво додадете нов ресурс во конфигурацијата и извршете ја командата примена, а потоа отстранете го стариот ресурс од конфигурацијата и повторно употребете ја командата примена.
    • Промената на идентификаторите бара промена на состојбата. Ако сакате да го промените ID-то поврзано со ресурс (на пример, преименувај го aws_security_group од пример во cluster_instance) без да го избришете ресурсот и да создадете нова негова верзија, мора соодветно да ја ажурирате датотеката за состојбата на Terraform. Никогаш не правете го ова рачно - наместо тоа, користете ја командата тераформна состојба. Кога преименувате идентификатори, треба да ја извршите командата terraform state mv, која ја има следнава синтакса:
      terraform state mv <ORIGINAL_REFERENCE> <NEW_REFERENCE>

      ORIGINAL_REFERENCE е израз кој се однесува на ресурсот во неговата моментална форма, а NEW_REFERENCE е местото каде што сакате да го преместите. На пример, кога ја преименувате групата aws_security_group од пример во 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 ќе го избрише стариот ресурс и ќе создаде нов на негово место. Секоја страница со ресурси обично означува што се случува кога менувате одредена поставка, затоа проверете ја документацијата. Секогаш користете ја командата план и размислете за користење на стратегијата create_before_destroy.

    Одложената конзистентност е конзистентна... со одложување

    АПИ на некои даватели на облак, како што е AWS, се асинхрони и имаат одложена конзистентност. Асинхронијата значи дека интерфејсот може веднаш да врати одговор без да чека да заврши бараното дејство. Задоцнета конзистентност значи дека промените може да потрае да се пропагираат низ системот; додека ова се случува, вашите одговори може да бидат неконзистентни и зависни од тоа која реплика на изворот на податоци одговара на вашите повици на API.

    Замислете, на пример, дека правите повик API до AWS барајќи од него да создаде сервер EC2. API ќе врати „успешен“ одговор (201 Created) речиси веднаш, без да чека да се создаде самиот сервер. Ако се обидете веднаш да се поврзете со него, речиси сигурно ќе пропадне бидејќи во тој момент AWS сè уште ги иницијализира ресурсите или, алтернативно, серверот сè уште не е подигнат. Освен тоа, ако упатите друг повик за да добиете информации за овој сервер, може да добиете грешка (404 не е пронајдено). Работата е што информациите за овој EC2 сервер сè уште може да се пропагираат низ AWS пред да станат достапни насекаде, ќе мора да почекате неколку секунди.

    Секогаш кога користите асинхрон API со мрзлива конзистентност, мора периодично да го повторувате вашето барање додека дејството не заврши и не се прошири низ системот. За жал, AWS SDK не обезбедува никакви добри алатки за ова, а проектот Terraform страдаше од многу грешки како 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

    Со други зборови, создавате ресурс (како подмрежа) и потоа се обидувате да добиете некои информации за него (како ID на новосоздадената подмрежа), а Terraform не може да го најде. Повеќето од овие грешки (вклучувајќи 6813) се поправени, но тие сè уште се појавуваат од време на време, особено кога Terraform додава поддршка за нов тип на ресурси. Ова е досадно, но во повеќето случаи не предизвикува никаква штета. Кога повторно ќе ја извршите апликацијата terraform, сè треба да работи, бидејќи до овој момент информациите веќе ќе се рашират низ системот.

    Овој извадок е претставен од книгата на Евгениј Брикман „Terraform: инфраструктура на ниво на код“.

Извор: www.habr.com

Додадете коментар