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@github.com:acme/modules.git//example?ref=v0.1.2
Чтобы проверить, корректно ли вы отформатировали URL, попробуйте клонировать базовый адрес в терминале с помощью git clone:
$ git clone git@github.com:
Если эта команда выполнится успешно, 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 для циклического перебора ресурсов.