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

module "webserver_cluster" {

  source = "github.com/foo/modules//webserver-cluster?ref=v0.0.1"

  cluster_name           = "webservers-stage"

  db_remote_state_bucket = "(YOUR_BUCKET_NAME)"

  db_remote_state_key    = "stage/data-stores/mysql/terraform.tfstate"

  instance_type = "t2.micro"

  min_size      = 2

  max_size      = 2

}

Если вы хотите использовать разные версии модулей без возни с Git-репози­ториями, можете загрузить модуль из архива с кодом для этой книги (https://github.com/brikis98/terraform-up-and-running-code). Мне пришлось разбить этот адрес на части, чтобы он поместился на странице. Его следует записывать одной строкой:

source = "github.com/brikis98/terraform-up-and-running-code//

  code/terraform/04-terraform-module/module-example/modules/

  services/webserver-cluster?ref=v0.1.0"

Параметр ref позволяет указать определенную фиксацию Git по ее хешу SHA1, имя ветки или, как в данном примере, конкретный тег Git. В целом я советую использовать в качестве версий модулей теги. Имена веток нестабильны, так как вы всегда получаете последнюю фиксацию в заданной ветке, которая может меняться при каждом выполнении команды init, а хеши SHA1 выглядят малопонятными. Теги Git такие же стабильные, как и фиксации (на самом деле это просто указатели на фиксации), но при этом они позволяют применять удобные и разборчивые названия.

Особенно полезной схемой именования тегов является семантическое версио­нирование (http://semver.org). Это система управления версиями в формате MAJOR.MINOR.PATCH (например, 1.0.4) с отдельными правилами относительно того, как следует инкрементировать каждый элемент номера версии. Вы должны инкрементировать:

• версию MAJOR при внесении несовместимых изменений в API;

• версию MINOR при добавлении возможностей с соблюдением обратной совместимости;

• версию PATCH при исправлении ошибок с соблюдением обратной совместимости.

Семантическое версионирование позволяет донести до пользователей модуля, какого рода изменения вы внесли и как это сказывается на процессе обновления.

Поскольку вы обновили свою конфигурацию с использованием разных URL-адресов для разных версий вашего модуля, нужно заново выполнить команду terraforminit, чтобы загрузить его код:

$ terraform init

Initializing modules...

Downloading git@github.com:brikis98/terraform-up-and-running-code.git?ref=v0.1.0

for webserver_cluster...

(...)

На этот раз вы можете видеть, что Terraform загружает код модуля из Git, а не из вашей локальной файловой системы. Когда все будет готово, вы сможете выполнить команду apply как обычно.


Закрытые Git-репозитории

Если ваш модуль находится в закрытом Git-репозитории, чтобы применять этот репозиторий в качестве источника модуля, нужно позволить Terraform в нем аутентифицироваться. Рекомендую использовать аутентификацию SSH, чтобы не пришлось хранить учетные данные для доступа к репозиторию в самом коде. Каждый разработчик сможет создать SSH-ключ и привязать его к пользователю Git. После добавления ключа в ssh-agent Terraform будет автоматически использовать его для аутентификации, если в качестве URL источника указан SSH45.

URL-адрес источника должен выглядеть так:

git@github.com:/.git//?ref=

Например:

git@github.com:acme/modules.git//example?ref=v0.1.2

Чтобы проверить, корректно ли вы отформатировали URL, попробуйте клонировать базовый адрес в терминале с помощью git clone:

$ git clone git@github.com:/.git

Если эта команда выполнится успешно, Terraform тоже сможет использовать ваш приватный репозиторий.

Теперь пройдемся по процессу внесения изменений в проекте с разными версиями модулей. Допустим, вы модифицировали модуль webserver-cluster и хотите проверить его в тестовой среде. Для начала изменения нужно зафиксировать в репозитории modules:

$ cd modules

$ git add .

$ git commit -m "Made some changes to webserver-cluster"

$ git push origin master

Затем в том же репозитории нужно создать новый тег:

$ git tag -a "v0.0.2" -m "Second release of webserver-cluster"

$ git push --follow-tags

Теперь вы можете перевести на новую версию только тот URL-адрес, который используется в тестовой среде (live/stage/services/webserver-cluster/main.tf):

module "webserver_cluster" {

  source = "git@github.com:foo/modules.git//webserver-cluster?ref=v0.0.2"

  cluster_name           = "webservers-stage"

  db_remote_state_bucket = "(YOUR_BUCKET_NAME)"

  db_remote_state_key    = "stage/data-stores/mysql/terraform.tfstate"

  instance_type = "t2.micro"

  min_size      = 2

  max_size      = 2

}

В промышленной среде (live/prod/services/webserver-cluster/main.tf) можно по-прежнему использовать версию v0.0.1 без всяких изменений:

module "webserver_cluster" {

  source = "git@github.com:foo/modules.git//webserver-cluster?ref=v0.0.1"

  cluster_name           = "webservers-prod"

  db_remote_state_bucket = "(YOUR_BUCKET_NAME)"

  db_remote_state_key    = "prod/data-stores/mysql/terraform.tfstate"

  instance_type = "m4.large"

  min_size      = 2

  max_size      = 10

}

После того как вы тщательно проверили версию v0.0.2 и убедились в ее корректности в тестовой среде, можете обновить и промышленное окружение. Но если в v0.0.2 обнаружится ошибка, это не составит большой проблемы, поскольку пользователи реальной системы не будут затронуты. Исправьте ошибку, выпустите новую версию и повторите весь процесс заново, пока ваш модуль не станет достаточно стабильным для промышленного применения.


Разработка модулей

Управление версиями модулей отлично подходит, когда развертывание происходит в общем окружении (тестовом или промышленном), но, если вы просто занимаетесь тестированием на собственном компьютере, лучше использовать локальные файловые пути. Это ускорит разработку, поскольку после внесения изменений в код модулей вы сможете сразу же выполнить команду apply в активных папках, без фиксации своего кода и публикации новой версии.

Цель этой книги — сделать процесс изучения и экспериментирования с Terraform максимально быстрым, поэтому в остальных примерах модули будут использовать локальные файловые пути.


Резюме

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

Все это может существенно помочь в построении инфраструктуры быстрым и надежным образом, поскольку разработчики смогут повторно использовать ее компоненты, которые были как следует проверены и задокументированы. Например, вы можете создать канонический модуль, который описывает процесс развертывания одного микросервиса, включая то, как запускать кластер, масштабировать его в зависимости от нагрузки и распределять запросы между серверами. Затем каждая команда сможет применять этот модуль для управления собственными микросервисами, и для этого будет достаточно лишь нескольких строчек кода.

Чтобы такой модуль подошел сразу нескольким командам, его код должен быть гибким и конфигурируемым. Например, одна команда может использовать его для развертывания одного экземпляра своего микросервиса без балансировщика нагрузки, а другой может понадобиться десяток экземпляров с распределением трафика между ними. Как в Terraform записать условные выражения? Можно ли выполнить цикл for? Можно ли с помощью Terraform выкатывать изменения в микросервисы без простоя? Этим углубленным аспектам синтаксиса Terraform посвящена глава 5.

44 Все подробности о URL-адресах источников можно найти на странице bit.ly/2TaXmZF.

45 Хорошее руководство по работе с SSH-ключами можно найти по адресу bit.ly/2ZFLJwe.

5. Работа с Terraform: циклы, условные выражения, развертывание и подводные камни

Terraform — это декларативный язык. Как уже обсуждалось в главе 1, по сравнению с процедурными языками декларативные обычно дают более точное представление о том, что на самом деле развернуто в IaC. Благодаря этому код остается компактным и в нем легче разобраться. Однако с некоторыми видами задач сложнее справиться в декларативном стиле.

Например, как повторить какой-то элемент бизнес-логики, в частности создание нескольких похожих ресурсов, без дублирования кода, учитывая, что у декларативных языков обычно нет цикла for? И если декларативный язык не поддерживает выражения if, как сконфигурировать ресурсы условным образом: скажем, написать модуль Terraform, который умеет создавать определенные ресурсы только для некоторых пользователей? И как в декларативном языке выразить сугубо процедурную концепцию, такую как развертывание с нулевым временем простоя?

К счастью, Terraform предоставляет несколько элементов языка, которые позволят вам выполнять определенные виды циклов, условных выражений и развертываний. Речь идет о метапараметре count, выражениях for_each и for, блоке жизненного цикла под названием create_before_destroy, тернарном операторе и большом количестве функций. Эта глава охватывает следующие темы.

• Циклы.

• Условные выражения.

• Развертывание с нулевым временем простоя;

• Подводные камни Terraform.


Примеры кода

Напоминаю: все примеры кода для этой книги можно найти по адресу github.com/brikis98/terraform-up-and-running-code.


Циклы

Terraform предоставляет несколько разных циклических конструкций с немного разными сценариями использования.

• Параметр count для циклического перебора ресурсов.