Мне очень нравится Terraform.
Помимо CloudFormation для AWS и OpenStack Heat, это один из самых полезных инструментов с открытым исходным кодом, обеспечивающих развертывание и настройку инфраструктуры на любой платформе. Однако есть один способ работы с Terraform, который меня беспокоит:
terraform plan # «Выглядит нормально; в работу!» — подумал инженер.
terraform apply
Может, это и не проблема, если вы разворачиваете софт на одной стойке в дата-центре или тестируете учетную запись AWS с ограниченными правами. В такой ситуации навредить достаточно сложно.
А если развертывание производится из-под всевидящего и всемогущего production-аккаунта или охватывает дата-центр целиком? Мне кажется, это весьма рискованно.
Интеграционное и юнит-тестирование способно решить эту проблему. Вы, наверное, спросите: «Юнит-тестирование — это как для программ?» Да, то самое юнит-тестирование!
В этой статье мы немного поговорим о том, что такое интеграционное и юнит-тестирование, а также рассмотрим проблемы и используемые на практике стратегии тестирования инфраструктуры. Мы также затронем стратегии развертывания инфраструктуры, поскольку они связаны с тестированием. Несмотря на то что в статье присутствует достаточное количество кода, глубокие познания в программировании от читателей не требуются.
Введение в тестирование программ
Юнит-тестирование используется разработчиками для проверки корректной работы отдельных функций, а интеграционное тестирование — для проверки работы функции как части приложения. Давайте проиллюстрируем разницу на примере.
Предположим, что я пишу калькулятор на псевдоязыке и сейчас занимаюсь созданием функции, складывающей два числа. Она выглядит следующим образом:
function addTwoNumbers(integer firstNumber, integer secondNumber) {
return (firstNumber + secondNumber);
}
Юнит-тест этой функции выглядит так:
function testAddTwoNumbers() {
firstNumber = 2;
secondNumber = 4;
expected = 6;
actual = addTwoNumbers(firstNumber, secondNumber);
if ( expectedResult != actualResult ) {
return testFailed("Expected %s but got %s", expected, actual);
}
return testPassed();
}
Как видно из листинга, юнит-тест проверяет, что функция addTwoNumbers умеет складывать два числа. Несмотря на кажущуюся тривиальность, такие тесты весьма важны, а их отсутствие привело к неудачам многих больших проектов!
Убедиться в том, что эта функция работает как часть всего приложения, значительно сложнее. На успех этого процесса влияет сложность самого приложения и его зависимостей. В нашем случае интеграционное тестирования калькулятора выглядит достаточно просто:
Calculator testCalculator = new Calculator();
firstNumber = 1;
secondNumber = 2;
expected = 3;
actual = testCalculator.addTwoNumbers(firstNumber, secondNumber);
# add other tests here
Test testAddTwoNumbers {
if ( expected != actual ) {
testFailed("Expected %s, but got %s.", expected, actual);
}
testPassed();
}
Мы создаем объект testCalculator класса Calculator и проверяем, работает ли функция сложения двух чисел. Если при юнит-тестировании эта функция была изолирована от других составляющих класса Calculator, при интеграционном тестировании все компоненты собраны воедино, чтобы убедиться, что изменения в addCalculator не сломали приложение целиком.
«Я деплою инфраструктуру, Карлос. Зачем мне все это?»
Сейчас объясню.
Писали ли вы когда-нибудь подобные скрипты...
function increaseDiskSize(string smbComputerName, string driveLetter) {
wmiQueryResult = new wmiObject("SELECT * FROM WIN32_LOGICALDISK WHERE DRIVELETTER = "%s"", driveLetter)
if ( wmiQuery != null ) {
# do stuff
}
}
… в надежде, что они будут работать должным образом? Перезапускали ли вы их несколько раз на тестовой машине, каждый раз делая изменения после найденных недоработок? Случалось ли так, что после запуска этой процедуры на боевом сервере она работала не так, как нужно? Теряли ли вы вечер или выходные по этой причине?
Если да, то, думаю, вы прекрасно понимаете проблемы, связанные с таким подходом.
terraform plan # «Выглядит нормально; в работу!» — подумал инженер.
terraform apply
Это то же самое!
Стратегия тестирования Terraform № 1: визуальный контроль
Terraform plan — это замечательная функция, которая дает возможность увидеть, что Terraform собирается делать. Она была создана для практиков, чтобы они могли оценить предстоящие действия, убедиться, что все выглядит хорошо, а затем в качестве финального штриха запустить terraform apply.
Скажем, вы собираетесь развернуть кластер Kubernetes в AWS, используя VPC и ключи EC2. Соответствующий код может выглядеть следующим образом (без рабочих хостов):
provider "aws" {
region = "${var.aws_region}"
}
resource "aws_vpc" "infrastructure" {
cidr_block = "10.1.0.0/16"
tags = {
Name = "vpc.domain.internal"
Environment = "dev"
}
}
resource "aws_key_pair" "keypair" {
key_name = "${var.key_name}"
public_key = "${var.public_key}"
}
resource "aws_instance" "kubernetes_controller" {
ami = "${data.aws_ami.kubernetes_instances.id}"
instance_type = "${var.kubernetes_controller_instance_size}"
count = 3
}
При использовании визуального контроля вы запустите terraform plan, убедитесь, что все хорошо (указав соответствующие переменные), и выполните terraform apply, чтобы применить изменения.
Преимущества
Разработка занимает немного времени
Такой подход хорошо работает на небольших масштабах или если вам нужно быстро написать конфигурацию, чтобы протестировать что-то новенькое. Цикл обратной связи в этом случае работает очень быстро (пишем код, terraform plan, проверяем, затем, если все хорошо, terraform apply); со временем можно научиться очень быстро штамповать новые конфигурации.
Пологая кривая обучения
Эту работу может выполнять любой человек независимо от его уровня знаний Terraform, опыта программирования и системного администрирования. В плане есть практически вся необходимая информация.
Недостатки
Сложность обнаружения ошибок
Скажем, вы прочитали этот документ о Borg и захотели развернуть пять реплик мастера Kubernetes вместо трех. Вы быстренько проверяете план и, будучи в полной уверенности, что все в порядке, запускаете его на выполнение. Но тут вдруг выясняется, что о самом главном вы забыли и все-таки развернули три мастера, а не пять!
В данном примере исправить ситуацию очень просто: поменяйте значения переменных в variables.tf
и разверните кластер заново. Однако все могло быть гораздо хуже, если бы, например, речь шла о боевом кластере, и команды, отвечающие за функциональность, уже развернули там свои сервисы.
Проблемы с масштабированием
Визуальный контроль хорош, если вы не используете Terraform очень активно.
Ситуация усложняется, когда вы начинаете разворачивать десятки и сотни различных компонентов инфраструктуры на десятках и сотнях серверов каждый. Модули помогут нам проиллюстрировать ситуацию. Взгляните на эту конфигурацию:
# main.tf
module "stack" "standard" {
source = "git@github.com:team/terraform_modules/stack.git"
number_of_web_servers = 2
number_of_db_servers = 2
aws_account_to_use = "${var.aws_account_number}"
aws_access_key = "${var.aws_access_key}"
aws_secret_key = "${var.aws_secret_key}"
aws_region_to_deploy_into = "${var.aws_region}"
…
}
Она кажется достаточно простой: в ней говорится, где находится модуль и какие он на входе получает переменные. Вы решаете посмотреть, как работает этот модуль, открываете его код и видите...
# team/terraform_modules/stack/main.tf
module "web_server" "web_servers_in_stack" {
source = "git@github.com:team/terraform_modules/core/web.git"
count = "${var.number_of_web_servers}"
type = "${coalesce(var.web_server_type, "IIS")}"
aws_access_key = "${var.aws_access_key}"
aws_secret_key = "${var.aws_secret_key}"
…
}
module "database_server" "db_servers_in_stack" {
source = "git@github.com:team/terraform_modules/core/db.git"
count = "${var.number_of_db_servers}"
type = "${coalesce(var.web_server_type, "MSSQLServerExp2016")}"
aws_access_key = "${var.aws_access_key}"
aws_secret_key = "${var.aws_secret_key}"
…
}
...еще модули! Нетрудно догадаться, что скитания по цепочкам репозиториев или директорий с целью выяснить, что делает текущая конфигурация Terraform, — это в лучшем случае утомительно, а в худшем — чревато; а выполнение этого упражнения во время сбоя еще и весьма затратно!
Стратегия тестирования Terraform № 2: сначала интеграционное тестирование
Для решения проблем, связанных с описанным выше способом тестирования, вы можете использовать инструменты типа serverspec, Goss и/или InSpec. При использовании этого подхода сначала ваши планы выполняются в «песочнице» с автоматической проверкой успешности процедуры. Если все тесты пройдены, можно переходить к развертыванию в рабочей среде. В противном случае проводится анализ ошибок и их исправление.
В случае использования цепочки Jenkins или Bamboo CI процесс может выглядеть следующим образом:
Схема 1. Пример процедуры развертывания инфраструктуры с применением интеграционного тестирования. Оранжевым цветом помечены шаги, выполняемые в «песочнице».
Это большой шаг в правильном направлении, однако здесь есть подводные камни, о которых стоит поговорить отдельно.
Преимущества
Позволяет избавиться от долгоживущих окружений разработки и поощряет использование неизменяемой (immutable) инфраструктуры
Распространенной практикой является создание долгоживущих окружений разработки или «песочниц», предназначенных для тестирования процедур обновления программ и изменений в скриптах. Эти окружения обычно имеют отличия от production, достаточные, чтобы то, что «работает в dev», сломалось в боевых условиях. Это приводит к «тестированию в production», что в итоге может повлечь простои сервиса из-за человеческих ошибок.
Хорошо написанные интеграционные тесты позволяют отказаться от этих небезопасных практик.
Каждая созданная при интеграционном тестировании «песочница» будет точной копией production, потому что каждая «песочница» в итоге станет production. Это ключевой шаг к построению неизменяемой инфраструктуры, где любое потенциальное изменение боевой системы становится частью пакета исправлений (hotfix) или будущего релиза и непосредственные изменения production запрещены и не нужны.
Это не значит, что такой подход позволяет избавиться от окружений, которыми пользуются непосредственно разработчики продукта (интеграционное тестирование программного обеспечения также ставит под вопрос необходимость использования таких окружений, но эта тема выходит за рамки данной статьи).
Документирует инфраструктуру
Вам больше не нужно продираться через многочисленные модули, чтобы разобраться с планом развертывания инфраструктуры. При наличии 100%-го покрытия кода Terraform (мы обсудим это чуть позже) тесты содержат исчерпывающую информацию и выступают в роли контракта, условий которого инфраструктура обязана придерживаться.
Позволяет тегировать версии и «релизы» инфраструктуры
Поскольку интеграционные тесты предназначены для тестирования системы в целом, они позволяют метить код Terraform с помощью тегов git или аналогичных инструментов. Эту функциональность можно использовать для отката к предыдущим состояниям (особенно в сочетании со стратегией развертывания синий-зеленый) или предоставления разработчикам возможности тестировать то, как разрабатываемая ими функциональность будет реагировать на различные версии инфраструктуры.
Могут стать первой линией обороны
Хорошо отлаженные интеграционные тесты служат системой раннего предупреждения об ошибках в сторонних приложениях.
Скажем, вы создали цепочку в Jenkins или Bamboo, в рамках которой дважды в день запускаются интеграционные тесты Terraform-инфраструктуры. При возникновении ошибок вы получаете соответствующие сообщения.
Спустя несколько дней вы получаете предупреждение о том, что интеграционной тест завершился с ошибкой. В журнале сборки вы видите ошибку Chef, в которой говорится, что он не смог установить IIS, поскольку не был найден установщик. Далее выясняется, что необходимый для установки IIS URL устарел и требует обновления.
После клонирования репозитория, в котором лежит соответствующий cookbook, обновления URL, локального перезапуска интеграционных тестов и ожидания их завершения вы создаете pull request команде-владельцу репозитория с просьбой принять изменение.
Мои поздравления! Вы только что спасли свои выходные, заранее исправив ошибку в коде, вместо ожидания начала выпуска релиза и реакции на ее появление уже в ходе этой операции.
Недостатки
Это может занять немало времени
В зависимости от создаваемых Terraform-конфигурацией ресурсов и от количества модулей, на которые они ссылаются, запуск Terraform может оказаться весьма затратным. В результате замедляются циклы обратной связи, в которых инженер делает изменение, дожидается завершения тестирования, находит причину ошибки, и цикл запускается снова. После некоторого количества итераций инженер в итоге найдет способ обойти эти тесты.
Это может оказаться достаточно затратно
Выполнение полноценных интеграционных тестов в «песочнице» подразумевает зеркалирование на короткий промежуток времени всей инфраструктуры (пусть и в меньшем масштабе). Выполнение этой процедуры для проверки небольших изменений со временем может вылиться в приличную сумму, особенно если зеркало достаточно велико.
Это не должно сильно беспокоить тех, кто работает на собственных серверах, поскольку они могут применить Terraform-конфигурации на неиспользуемом железе, а затем удалить их с помощью terraform destroy. Это, однако, приводит нас к следующему пункту, поскольку очистка и настройка для следующего прогона тестов может занять определенное время.
Сложно определить покрытие кода
В отличие от большинства языков программирования или проверенных инструментов управления конфигурациями, такими как Chef, в Terraform нет средств для получения процента конфигурации, которому соответствуют интеграционные тесты. Это значит, что решившие пойти по этому пути команды должны пристально следить за поддержанием высокого процента покрытия кода, и, скорее всего, им придется писать самостоятельно необходимые инструменты, которые, к примеру, будут сканировать все ссылки на модули и искать соответствующие им определения спецификаций.
Инструменты
kitchen-terraform — наиболее популярный в данный момент инструмент интеграционного тестирования для Terraform. Этот тот же самый Kitchen, который вы могли использовать для Chef, и аналогично test-kitchen, вы можете определить поведение Terraform с помощью .kitchen.yml
— kitchen позаботится обо всем остальном.
Goss — это простой инструмент валидации/проверки состояния, позволяющий определить желаемое состояние системы и либо сравнить с заданным определением, либо предоставить конечную точку, /health
, которая содержит информацию о том, проходит
система проверку или нет. Этот инструмент аналогичен serverspec, который позволяет добиться похожего результата (за исключением крутого веб-интерфейса проверки состояния (health-check)) и отлично работает с Ruby.
Стратегия тестирования Terraform № 2: юнит-тесты
Опытный разработчик может спросить: «Обычно перед интеграционными я пишу юнит-тесты. С Terraform то же самое?» Спасибо, что дочитали до этого момента!
Как уже отмечалось ранее, интеграционное тестирование позволяет проверить корректность взаимодействия компонентов системы. Юнит-тестирование позволяет проверить работу этих компонентов по отдельности, изолированно друг от друга. Юнит-тесты должны быть быстрыми, простыми в понимании и написании. Хорошие юнит-тесты должны запускаться везде, с сетевым соединением или без него.
Возвращаясь к нашему примеру с кластером Kubernetes, для ресурсов, которые мы определили выше, простой юнит-тест под популярный тестовый фреймворк для Ruby RSpec будет выглядеть вот так:
# spec/infrastructure/core/kubernetes_cluster_spec.rb
require 'spec_helper'
require 'set'
describe "KubernetesCluster" do
before(:all) do
@vpc_details = $terraform_plan['aws_vpc.infrastructure']
@controllers_found =
$terraform_plan['kubernetes-cluster'].select do |key,value|
key.match /aws_instance.kubernetes_controller/
end
@coreos_amis = obtain_latest_coreos_version_and_ami!
end
context "Controller" do
context "Metadata" do
it "should have retrieved EC2 details" do
expect(@controllers_found).not_to be_nil
end
end
context "Sizing" do
it "should be defined" do
expect($terraform_tfvars['kubernetes_controller_count']).not_to be_nil
end
it "should be replicated the correct number of times" do
expected_number_of_kube_controllers =
$terraform_tfvars['kubernetes_controller_count'].to_i
expect(@controllers_found.count).to eq expected_number_of_kube_controllers
end
it "should use the same AZ across all Kubernetes controllers" do
# We aren't testing that these controllers actually have AZs
# (it can be empty if not defined). We're solely testing that
# they are the same within this AZ.
azs_for_each_controller = @controllers_found.values.map do |controller_config|
controller_config['availability_zone']
end
deduplicated_az_set = Set.new(azs_for_each_controller)
expect(deduplicated_az_set.count).to eq 1
end
end
it "should be fetching the latest stable release of CoreOS for region
#{ENV['AWS_REGION']}" do
@controllers_found.keys.each do |kube_controller_resource_name|
this_controller_details =
@controllers_found[kube_controller_resource_name]
expected_ami_id = @coreos_amis[ENV['AWS_REGION']]['hvm']
actual_ami_id = this_controller_details['ami']
expect(expected_ami_id).to eq expected_ami_id
end
end
it "should use the instance size requested" do
@controllers_found.keys.each do |kube_controller_resource_name|
this_controller_details =
@controllers_found[kube_controller_resource_name]
actual_instance_size = this_controller_details['instance_type']
expected_instance_size =
$terraform_tfvars['kubernetes_controller_instance_size']
expect(expected_instance_size).to eq actual_instance_size
end
end
it "should use the key provided" do
@controllers_found.keys.each do |kube_controller_resource_name|
this_controller_details =
@controllers_found[kube_controller_resource_name]
actual_ec2_instance_key_name = this_controller_details['key_name']
expected_ec2_instance_key_name =
$terraform_tfvars['kubernetes_controller_ec2_instance_key_name']
expect(expected_ec2_instance_key_name).to eq actual_ec2_instance_key_name
end
end
end
end
Здесь есть несколько важных моментов, на которые нужно обратить внимание:
Их легко читать
Чтобы определить, что представляет из себя тест и что он должен делать, мы используем операторы, сформулированные на языке, очень близком к обычному английскому. Посмотрите на этот кусок кода:
describe "KubernetesCluster" do
before(:all) do
@vpc_details = $terraform_plan['aws_vpc.infrastructure']
@controllers_found =
$terraform_plan['kubernetes-cluster'].select do |key,value|
key.match /aws_instance.kubernetes_controller/
end
@coreos_amis = obtain_latest_coreos_version_and_ami!
end
context "Controller" do
context "Metadata" do
it "should have retrieved EC2 details" do
expect(@controllers_found).not_to be_nil
end
end
Здесь мы описываем (describe) кластер Kubernetes, выполняя некоторые операторы перед всеми (before all) другими тестами, затем выполняем серию относящихся к controller тестов, которые сфокусированы на метаданных Kubernetes-кластера. Мы ожидаем, что подробности (details) должны быть загружены из EC2 и что эти подробности представляют из себя массив controllers found.
Они быстры
При наличии сгенерированного плана Terraform (скоро мы затронем эту тему) юнит-тесты будут выполняться меньше чем за секунду. Здесь нет «песочниц», к которым надо делать запросы, и AWS API для обращения; мы просто сравниваем то, что мы написали, с тем, что из этого сгенерировал Terraform.
Их легко писать
Не считая нескольких особых случаев (см. раздел Недостатки), все тестовые примеры взяты из документации по RSpec. Здесь нет скрытых методов или подводных камней, если вы, конечно, сами их предварительно не добавили.
Итак, зачем нам юнит-тестить код Terraform?
Преимущества
Позволяет использовать разработку через тестирование (test-driven development)
Разработка через тестирование — это техника разработки программного обеспечения, при которой функциональность пишется после написания теста, определяющего, что эта функциональность делает. Таким образом, все важные методы получают описывающие их тесты. Это возможно лишь в том случае, когда юнит-тесты работают быстро, поскольку цикл обратной связи между запуском теста и написанием проходящего этот тест кода должен выполняться быстро, обычно около нескольких секунд.
В плане Terraform-конфигурации это означает, что я могут написать что-то вроде этого...
describe "VPC" do
context "Networking" do
it "should use the right CIDR" do
expected_cidr_block = $terraform_tfvars['cidr_block']
expect($terraform_plan['aws_vpc.vpc']['cidr_block']).to eq expected_cidr_block
end
end
end
… перед расписыванием main.tf
, запустить rake unit_test
(или аналогичную команду) и убедиться, что тест завершился с ошибкой, затем продолжить написание main.tf
до тех пор, пока тесты не будут завершаться успешно. Параллельно, даже не думая об этом, я: а) задокументировал инфраструктуру; б) задокументировал, что в данном контексте означает «все хорошо».
Быстрее интеграционных тестов
Юнит-тесты Terraform работают исключительно на основе заранее рассчитанного плана Terraform. Никаких дополнительных вычислений не требуется. Это значит, что вы сможете находить ошибки в вашем Terraform-коде раньше, чаще и перед тем, как начинать тратить деньги на тестовые «песочницы».
Меньше интеграционных тестов
Поскольку юнит-тесты фокусируются на отдельных деталях реализации (таких как обеспечение корректности VPC CIDR или правильности установки группы безопасности AWS), появляется возможность написать больше юнит-тестов. В сочетании с интеграционными тестами, описанными в предыдущей части статьи, они образуют методику тестирования, которая позволяет создавать надежные окружения, существенно снижая опасность человеческих ошибок.
Недостатки
Не поймите меня превратно, юнит-тесты дополняют интеграционные тесты, а не заменяют их. У них также есть недостатки:
Надежных инструментов юнит-тестирования Terraform очень и очень мало
В разделе «инструменты» вы увидите, что вариантов, работающих «из коробки», совсем немного. Следовательно, вам, скорее всего, придется придумывать что-то свое.
TDD поначалу медленнее, чем просто написание кода
Многие разработчики сначала пишут код, а потом тесты. Тех, кто практикует TDD, меньше, но их количество растет. Попытка перехода в стан последних может провалиться, так как придется тратить немало времени на написание работающих тестов (особенно при отсутствии хорошего фреймворка для тестов) или на написание актуальных тестов.
В случае Terraform потраченное время стоит того, чтобы быть уверенным в правильной работе кода с самого начала, в сравнении с отладкой непростых взаимосвязей ресурсов Terraform впоследствии. Прививание культуры «первым делом тесты» играет немаловажную роль в упрощении поддержки кода.
Кривая обучаемости для новичков может оказаться слишком крутой
Я пришел в инфраструктуру и DevOps из системного администрирования. Инструменты, обладающие языками высокого уровня, какие как Chef и Terraform, помогли упростить процедуру моего превращения. Однако при отсутствии хороших инструментов для юнит-тестирования Terraform знание программирования оказывается необходимым. Например, в своем решении я использую Ruby. Сильные команды, работающие с инфраструктурой, безусловно, должны двигаться в направлении получения программистской подготовки, но для новичков это может показаться чересчур сложным.
Инструменты (или их отсутствие)
Юнит-тестирование Terraform находится в младенческом состоянии. Однако для ищущих найдется несколько вариантов.
Пример с моей инфраструктурой.
При развертывании своей инфраструктуры я широко использую юнит-тестирование. Работа еще не закончена. Я стремлюсь к созданию Terraform-кода, который может настроить VPC, Kubernetes-кластер из пяти нод (три мастера и два рабочих узла) и зону Route53. Эта инфраструктура будет использоваться для моего блога, личного сайта и инструментов, над которыми я прокрастинирую. Для тестирования я использую RSpec и стремлюсь обеспечить независимость от провайдера (то есть создать инструмент, с помощью которого можно будет тестировать, к примеру, и AWS-, и OpenStack-ресурсы).
Смотреть в первую очередь стоит файл spec/spec_helper.rb
. Он разбирает файл переменных Terraform (который в цепочке Jenkins определен как Jenkinsfile и автоматом загружается из S3 в зависимости от настраиваемого окружения) и с его помощью из записанного на диск фиктивного файла состояния создает Terraform-план. Фиктивный файл нужен для того, чтобы Terraform всегда генерировал полный план при каждом запуске теста. Я пришел к выводу, что в запуске тестов после каждого изменения мало смысла, поскольку процедура избыточна за счет тестирования самого Terraform, а соответствующий код усложнит тесты.
Если вы когда-нибудь сохраняли tfplan на диск, то наверняка знаете, что его парсинг может оказаться непростой задачей. Это бинарник, поэтому прямой парсинг отпадает. Чтобы получить человекочитаемую версию, подойдет команда terraform show
, однако пробелы, множественные копии ресурсов, зависимостей и т. д. усложняют задачу парсинга. Я смог найти только один инструмент, способный преобразовать этот текст в машиночитаемый формат, — это tfjson от Palantir, который качественно перегоняет планы Terraform в JSON. (Я пытался обновить его Terraform-библиотеки на более свежие, но столкнулся с большим количеством проблем. Именно поэтому мой Rakefile загружает и старую, и новую версии Terraform.)
Если у вас есть предложения по улучшению этого инструмента, без колебаний присылайте мне запросы на включение (pull requests)!
Этот замечательный инструмент для выполнения юнит-тестирования Terraform-код написал мой коллега Emre Erkunt. Здесь главной целью является более высокий уровень абстракции тестирования под названием business driven development, но этот инструмент также может использоваться для написания юнит-тестов для AWS-ресурсов.
Более чистая AWS-специфичная реализация моего подхода. Это отличное решение, которое стоит посмотреть, если вам не мешает жесткая привязка к провайдеру.
В заключение: полная цепочка развертывания
Использование интеграционного и юнит-тестирования кодовой базы «инфраструктуры как код» позволяет создать процесс внедрения инфраструктуры следующего вида:
Схема 2. Процесс развертывания окружения.
Юнит-тесты выполняются локально на первой стадии процесса. Используя Terraform-план легко увидеть, что планируется к запуску. Благодаря этому мы можем создавать юнит-тесты для отдельных модулей и ресурсов, а также формулировать контракты, которым они должны соответствовать. Эта часть процесса может быть далее автоматизирована за счет коммит-хуков в Git или SVN, запускающих юнит-тесты после создания запроса на включение (pull request) для ветки master.
После прохождения юнит-тестов запускаются интеграционные тесты, для чего используется serverspec или kitchen-terraform. Эту процедуру можно выполнить локально на рабочей станции разработчика, но мы рекомендуем делать это на CI-сервере по событию создания запроса на включение, чтобы проверить качество кода в этом запросе. В процессе интеграционного тестирования код Terraform и содержащиеся в нем зависимости инструментов настройки окружений используются для создания дополнительной копии рабочего окружения, в созданных окружениях запускаются тесты, затем эти окружения уничтожаются и генерируется отчет о тестировании.
После прохождения интеграционных тестов и слияния запроса на включения с веткой master выпускается «релиз» окружения. Поскольку конфигурации Terraform уже прошли локальные и CI-тесты, «релиз» — это просто тегирование коммита соответствующим номером версии, а также использование стратегии развертывания для перенаправления входящего трафика на новое окружение. В зависимости от особенностей приложения и характера его использования может потребоваться изменение записей DNS и настроек балансировщиков нагрузки таким образом, чтобы они указывали на новую версию окружения взамен старой (развертывание типа синий-зеленый) или постепенное перенаправление трафика от старого окружения к новому (канареечное развертывание, где в качестве «канарейки» выступает новое окружение).
С помощью предложенной методики вы сделаете процесс развертывания инфраструктуры более надежным, сможете быстрее возвращаться на предыдущие работоспособные конфигурации и внедрять новые функции с гораздо большей долей уверенности в успехе.
Ссылки:
Автор: olemskoi