е-какие команды. Этот список можно продолжить. Базовую структуру тестирования, описанную в этой главе, можно применить к инфраструктуре любого вида. Однако этапы проверки будут зависеть от того, что именно вы проверяете.
Напомню: ALB возвращает 404 ввиду отсутствия в конфигурации других правил прослушивателя, а действие по умолчанию в модуле alb имеет ответ 404:
resource "aws_lb_listener" "http" {
load_balancer_arn = aws_lb.example.arn
port = local.http_port
protocol = "HTTP"
# По умолчанию возвращаем простую страницу 404
default_action {
type = "fixed-response"
fixed_response {
content_type = "text/plain"
message_body = "404: page not found"
status_code = 404
}
}
}
Итак, вы уже умеете запускать и тестировать свой код. Теперь можно приступить к внесению изменений. Каждый раз, когда вы что-то меняете (чтобы, например, действие по умолчанию возвращало 401), вам нужно использовать команду terraformapply, чтобы развернуть новый код:
$ terraform apply
(...)
Apply complete! Resources: 0 added, 1 changed, 0 destroyed.
Outputs:
alb_dns_name = hello-world-stage-477699288.us-east-2.elb.amazonaws.com
Чтобы проверить новую версию, можно заново запустить curl:
$ curl \
-s \
-o /dev/null \
-w "%{http_code}" \
hello-world-stage-477699288.us-east-2.elb.amazonaws.com
401
Когда закончите, выполните команду terraformdestroy, чтобы удалить ресурсы:
$ terraform destroy
(...)
Apply complete! Resources: 0 added, 0 changed, 5 destroyed.
Иными словами, при работе с Terraform каждому разработчику нужны хорошие примеры кода для тестирования и настоящее окружение для разработки (вроде учетной записи AWS), которое служит эквивалентом локального компьютера и используется для выполнения тестов. В процессе ручного тестирования вам, скорее всего, придется создавать и удалять большое количество компонентов инфраструктуры, и это может привести к множеству ошибок. В связи с этим окружение должно быть полностью изолировано от более стабильных сред, предназначенных для финального тестирования и в особенности для промышленного применения.
Учитывая вышесказанное, настоятельно рекомендую каждой команде разработчиков подготовить изолированную среду, в которой вы сможете создавать и удалять любую инфраструктуру без последствий. Чтобы минимизировать вероятность конфликтов между разными разработчиками (представьте, что два разработчика пытаются создать балансировщик нагрузки с одним и тем же именем), идеальным решением будет выделить каждому члену команды отдельную, полностью изолированную среду. Например, если вы используете Terraform в сочетании с AWS, каждый разработчик в идеале должен иметь собственную учетную запись, в которой он сможет тестировать все, что ему захочется55.
Очистка ресурсов после тестов
Наличие множества изолированных окружений необходимо для высокой продуктивности разработчиков, но, если не проявлять осторожность, у вас может накопиться много лишних ресурсов, которые захламят все ваши среды и будут стоить вам круглую сумму.
Чтобы держать расходы под контролем, регулярно чистите свои изолированные среды. Это ключевой вывод о тестировании № 2.
Вам как минимум следует создать в команде такую культуру, когда после окончания тестирования разработчики удаляют все, что они развернули, с помощью команды terraformdestroy. Возможно, удастся найти инструменты для очистки лишних или старых ресурсов, которые можно запускать на регулярной основе (скажем, с помощью cron). Вот некоторые примеры для разных сред развертывания.
•cloud-nuke (http://bit.ly/2OIgM9r). Это инструмент с открытым исходным кодом, который может удалить все ресурсы в вашей облачной среде. Сейчас он поддерживает целый ряд сервисов в AWS (вроде Amazon EC2 Instances, ASG, ELB и т. д.). Поддержка других ресурсов и облаков (Google Cloud, Azure) ожидается в будущем. Его ключевая особенность — возможность удаления всех ресурсов, начиная с определенного возраста. Например, cloud-nuke часто запускают ежедневно с помощью cron, чтобы удалить в каждой изолированной среде все ресурсы старше двух дней. При этом предполагается, что любая инфраструктура, которую разработчик подготовил для ручного тестирования, становится ненужной по прошествии пары суток:
$ cloud-nuke aws --older-than 48h
• Janitor Monkey (http://bit.ly/2M4GoLB). Это инструмент с открытым исходным кодом, чистящий ресурсы AWS по графику, который можно настроить (по умолчанию — раз в неделю). Он поддерживает гибкие правила, которые определяют, подлежит ли ресурс удалению, и даже может отправить владельцу ресурса уведомление за несколько дней до очистки. Это часть проекта Netflix Simian Army, в который также входит инструмент Chaos Monkey для проверки устойчивости приложений. Имейте в виду, что у проекта Simian Army больше нет активной поддержки, но разные его части подхватываются другими командами: например, на смену Janitor Monkey пришел инструмент Swabbie (http://bit.ly/2OLrOLb).
• aws-nuke (http://bit.ly/2ZB8lOe). Это инструмент с открытым исходным кодом для удаления всего содержимого учетной записи AWS. Учетные записи и ресурсы, подлежащие удалению, указываются в конфигурационном файле в формате YAML:
# Регионы для удаления
regions:
- us-east-2
# Учетные записи для удаления
accounts:
"111111111111": {}
# Удалять только эти ресурсы
resource-types:
targets:
- S3Object
- S3Bucket
- IAMRole
Запускается aws-nuke следующим образом:
$ aws-nuke -5c config.yml
Автоматические тесты
Осторожно: впереди много кода!
Написание автоматических тестов для инфраструктурного кода — занятие не для слабонервных. Этот раздел — самая сложная часть книги и требует от читателя повышенного внимания. Если вы просто пролистываете, можете его пропустить. Но если вы действительно хотите научиться тестировать свой инфраструктурный код, закатывайте рукава и готовьтесь к настоящему программированию! Код на Ruby можно не запускать (он нужен лишь для того, чтобы у вас сформировалась общая картина происходящего), а вот код на Go следует записывать и выполнять как можно активней.
Концепция автоматического тестирования состоит в том, что для проверки поведения настоящего кода пишутся тесты. В главе 8 вы узнаете, что с помощью CI-сервера эти тесты можно запускать после каждой отдельной фиксации. Если они не будут пройдены, фиксацию сразу же можно отменить или исправить. Таким образом, ваш код всегда будет в рабочем состоянии.
Существует три типа автоматических тестов.
• Модульные тесты. Проверяют функциональность одного небольшого фрагмента кода — модуля. Его определение может варьироваться, но в языках программирования общего назначения под ним понимают отдельную функцию или класс. Внешние зависимости (например, базы данных, веб-сервисы и даже файловая система) заменяются тестовыми mock-объектами. Это позволяет полностью контролировать их поведение (например, mock-объект базы данных может возвращать ответ, прописанный вручную) и убедиться в том, что ваш код справляется с множеством разных сценариев.
•Интеграционные тесты. Проверяют корректность совместной работы нескольких модулей. В языках программирования общего назначения интеграционный тест состоит из кода, который позволяет убедиться в корректном взаимодействии нескольких функций или классов. Реальные зависимости используются вперемешку с mock-объектами: например, если вы тестируете участок приложения, который обращается к базе данных, стоит тестировать его с настоящей БД, а другие зависимости, скажем, систему аутентификации, заменить mock-объектами.
•Сквозные тесты. Подразумевают запуск всей вашей архитектуры (например, приложений, хранилищ данных, балансировщиков нагрузки) и проверку работы системы как единого целого. Обычно эти тесты выполняются как бы от имени конечного пользователя: допустим, Selenium может автоматизировать взаимодействие с вашим продуктом через браузер. В сквозном тестировании везде используются реальные компоненты без каких-либо mock-объектов, а архитектура при этом является отражением настоящей промышленной системы (но с меньшим количеством или с менее дорогими серверами, чтобы сэкономить).
У каждого типа тестов свое назначение, и с их помощью можно выявить разного рода ошибки, поэтому их стоит применять совместно. Модульные тесты выполняются быстро и позволяют сразу получить представление о внесенных изменениях и проверить разнообразные комбинации. Это дает уверенность в том, что элементарные компоненты вашего кода (отдельные модули) ведут себя так, как вы ожидали. Однако тот факт, что модули работают корректно по отдельности, вовсе не означает, что они смогут работать совместно, поэтому, чтобы убедиться в совместимости ваших элементарных компонентов, нужны интеграционные тесты. С другой стороны, корректное поведение разных частей системы не гарантирует, что эта система будет работать как следует после развертывания в промышленной среде, поэтому, чтобы проверить ваш код в условиях, приближенных к реальным, необходимы сквозные тесты.
Теперь посмотрим, как писать тесты каждого из этих типов для кода Terraform.
Модульные тесты
Чтобы понять, как писать модульные тесты для кода Terraform, можно сначала посмотреть на то, как это делается в языках программирования общего назначения, таких как Ruby. Взгляните на код веб-сервера, написанный на Ruby:
class WebServer < WEBrick::HTTPServlet::AbstractServlet
def do_GET(request, response)
case request.path
when "/"
response.status = 200
response['Content-Type'] = 'text/plain'
response.body = 'Hello, World'