Terraform: инфраструктура на уровне кода — страница 36 из 65

Ниже показана команда import, позволяющая синхронизировать ресурс aws_­iam­_user, который вы добавили в свою конфигурацию Terraform вместе с пользователем IAM в главе 2 (естественно, вместо yevgeniy.brikman нужно подставить ваше имя):

$ terraform import aws_iam_user.existing_user yevgeniy.brikman

Terraform обратится к API AWS, чтобы найти вашего пользователя 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 = "The name to use for all the cluster resources"

  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. Никогда не делайте этого вручную — используйте вместо этого команду terraformstate. При переименовании идентификаторов следует выполнить команду terraformstatemv, которая имеет следующий синтаксис:

terraform state mv

ORIGINAL_REFERENCE — это выражение, ссылающееся на ресурс в его текущем виде, а NEW_REFERENCE — то место, куда вы хотите его переместить. Например, при переименовании группы aws_security_group с instance на cluster_instance нужно выполнить следующую команду:

$ terraform state mv \

  aws_security_group.instance \

  aws_security_group.cluster_instance

Так вы сообщите Terraform, что состояние, которое ранее относилось к aws_se­­curity_group.instance, теперь должно быть связано с aws_security_gro­up.cluster_instance. Если после переименования и запуска этой команды terraformplan не покажет никаких изменений, значит, вы все сделали правильно.

• Некоторые параметры нельзя изменять. Параметры многих ресурсов неизменяемые. Если попытаться их изменить, Terraform удалит старый ресурс и создаст вместо него новый. На странице каждого ресурса обычно указывается, что происходит при изменении того или иного параметра, поэтому не забывайте сверяться с документацией. Всегда используйте команду plan и рассматривайте целесообразность применения стратегии create_before_destroy.


Отложенная согласованность согласуется… с отлагательством

API некоторых облачных провайдеров, таких как AWS, асинхронные и имеют отложенную согласованность. Асинхронность означает, что интерфейс может сразу же вернуть ответ, не дожидаясь завершения запрошенного действия. Отложенная согласованность значит, что для распространения изменений по всей системе может понадобиться время; пока это происходит, ваши ответы могут быть несогласованными и зависеть от того, какая реплика источника данных отвечает на ваши API-вызовы.

Представьте, к примеру, что вы делаете API-вызов к AWS с просьбой создать сервер EC2. API вернет «успешный» ответ (201Created) практически мгновенно, не дожидаясь создания самого сервера. Если вы сразу же попытаетесь к нему подключиться, почти наверняка ничего не получится, поскольку в этот момент AWS все еще инициализирует ресурсы или, как вариант, сервер еще не загрузился. Более того, если вы сделаете еще один вызов, чтобы получить информацию об этом сервере, может прийти ошибка (404NotFound). Дело в том, что сведения об этом сервере 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 добавляют поддержку нового типа ресурсов. Это раздражает, но в большинстве случаев не несет никакого вреда. При повторном выполнении terraformapply все должно заработать, поскольку к этому моменту информация уже распространится по системе.


Резюме

Несмотря на свою декларативную сущность, Terraform включает целый ряд конструкций, которые делают его на удивление гибким и выразительным. Это, к примеру, касается переменных и модулей (см. главу 4), параметра count, выражений for_each, for и create_before_destroy, а также встроенных функций, которые вы могли видеть в этой главе. Вы познакомились с множеством приемов симуляции условных выражений, поэтому потратьте некоторое время на чтение документации по функциям (https://www.terraform.io/docs/configuration/functions.html) и позвольте разыграться своему «внутреннему хакеру». Главное, не переусердствуйте, так как кто-то все равно должен поддерживать ваш код; просто постарайтесь научиться создавать аккуратны