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


Тестируемые модули

Вы уже написали довольно много кода в виде трех модулей: asg-rolling-deploy, alb и hello-world-app. Теперь нужно убедиться, что этот код и правда работает.

Указанные модули не корневые и не предназначены для развертывания напрямую. Чтобы их развернуть, нужно написать какой-то код Terraform, подключить нужные вам аргументы, сконфигурировать поля provider и backend и т. д. Для этого можно создать папку examples, которая будет содержать примеры использования ваших модулей. Попробуем это сделать.

Создайте файл examples/asg/main.tf следующего содержания:

provider "aws" {

  region = "us-east-2"

}

module "asg" {

  source = "../../modules/cluster/asg-rolling-deploy"

  cluster_name  = var.cluster_name

  ami           = "ami-0c55b159cbfafe1f0"

  instance_type = "t2.micro"

  min_size           = 1

  max_size           = 1

  enable_autoscaling = false

  subnet_ids = data.aws_subnet_ids.default.ids

}

data "aws_vpc" "default" {

  default = true

}

data "aws_subnet_ids" "default" {

  vpc_id = data.aws_vpc.default.id

}

Этот фрагмент кода развертывает группу ASG с одним сервером, применяя модуль asg-rolling-deploy. Попробуйте выполнить команды terraforminit и terraformapply и убедитесь, что все работает без ошибок и ASG действительно создается. Теперь добавьте файл README.md с данными инструкциями, после чего этот крошечный пример станет куда более мощным. С помощью всего нескольких файлов и строк кода вы получаете следующее.

Механизм ручных тестов. Этот демонстрационный код можно использовать при работе над модулем asg-rolling-deploy, развертывая и удаляя его по много раз путем ручного выполнения команд terraformapply и terraformdestroy. Это позволит убедиться в его предсказуемом поведении.

• Механизм автоматических тестов. Как вы увидите в главе 7, с помощью этого демонстрационного кода можно также создавать автоматические тесты для ваших модулей. Советую размещать тесты в папке test.

• Исполняемая документация. Если сохранить этот пример (вместе с файлом README.md) в системе управления версиями, каждый член вашей команды сможет его найти и понять с его помощью, как работает ваш модуль. Это также позволит запускать модуль без написания какого-либо кода. Таким образом, вы предоставляете своей команде обучающий материал и вместе с тем можете подтвердить его корректность автоматическими тестами.

У каждого модуля Terraform, который находится в папке modules, должна быть папка examples с соответствующим примером. А у последнего в этой папке должен быть подходящий тест в папке test. У каждого модуля, скорее всего, будет ряд примеров (и, следовательно, несколько тестов), которые иллюстрируют разные конфигурации и сценарии использования. Скажем, для модуля asg-rolling-deploy можно придумать другие примеры, чтобы показать, как он работает с правилами автомасштабирования, как к нему подключать балансировщики нагрузки, как ему назначить пользовательские теги и т. д.

Если все это объединить, структура каталогов типичного репозитория modules будет выглядеть так:

modules

└ examples

   └ alb

   └ asg-rolling-deploy

     └ one-instance

     └ auto-scaling

     └ with-load-balancer

     └ custom-tags

   └ hello-world-app

   └ mysql

└ modules

   └ alb

   └ asg-rolling-deploy

   └ hello-world-app

   └ mysql

└ test

   └ alb

   └ asg-rolling-deploy

   └ hello-world-app

   └ mysql

В качестве упражнения предлагаю вам превратить код RDS, который вы написали, в модуль MySQL и добавить к модулям alb, asg-rollingdeploy, mysql и hello-world-app множество разных примеров.

Разработку нового модуля крайне полезно начинать с написания демонстрационного кода. Если сразу заняться реализацией, можно легко погрязнуть в деталях, и к моменту, когда вы вернетесь к API, ваш модуль будет малопонятным и сложным в применении. Если же вы начнете с кода примера, у вас будет возможность подумать об опыте использования, выработать для своего модуля аккуратный API и затем вернуться к реализации. Поскольку код примера и так служит основным способом тестирования модуля, такой подход является разновидностью разработки через тестирование (Test-Driven Development, TDD); подробнее о тестировании мы поговорим в главе 7.

Существует еще один подход, который вы сможете оценить по достоинству, как только начнете регулярно тестировать свои модули: закрепление версий. Вы должны закрепить все свои модули за определенной версией Terraform, используя аргумент required_version. Как минимум следует требовать конкретную мажорную версию Terraform:

terraform {

  # Требуем любую версию Terraform вида 0.12.x

  required_version = ">= 0.12, < 0.13"

}

Этот код позволит использовать для вашего модуля только версии Terraform ви­да 0.12.x, но не 0.11.x или 0.13.x. Это очень важно, потому что при каждом мажорном выпуске Terraform теряет обратную совместимость: например, переход с 0.11.x на 0.12.x требует существенного изменения кода, поэтому вам вряд ли понравится, если такой переход произойдет случайно. Если добавить параметр required_version и попытаться выполнить terraformapply, используя другую версию, сразу же появится ошибка:

$ terraform apply

Error: Unsupported Terraform Core version

This configuration does not support Terraform version 0.11.11. To proceed, either choose another supported Terraform version or update the root module's version constraint. Version constraints are normally set for good reason, so updating the constraint may lead to other errors or unexpected behavior.

Для кода промышленного уровня рекомендую еще строже закреплять версию:

terraform {

  # Требуем исключительно версию Terraform 0.12.0

  required_version = "= 0.12.0"

}

Это вызвано тем, что даже переход на новые «заплаточные» версии (скажем, 0.12.0 —> 0.12.1) может создать проблемы. Иногда они содержат ошибки, а иногда у них ломается обратная совместимость (хотя в наши дни это случается реже). Но еще более серьезной проблемой является то, что файл состояния, измененный более новой версией Terraform, уже нельзя использовать в старых версиях. Представьте, к примеру, что весь ваш код развернут с помощью Terraform 0.12.0, и тут вдруг разработчик, у которого установлена версия 0.12.1, применяет к некоторым из ваших модулей команду terraformapply. В результате файлы состояния этих модулей больше не совместимы с 0.12.0, поэтому вы вынуждены обновить все свои компьютеры для разработки и CI-серверы на 0.12.1!

Ситуация, скорее всего, улучшится, когда Terraform достигнет версии 1.0.0 и на­чнет соблюдать обратную совместимость, но, пока этого не произошло, я советую привязываться к определенной версии этой системы. Это позволит избежать незапланированных обновлений. Вы сможете обновляться тогда, когда будете готовы, и делать это сразу для всех своих компьютеров и CI-серверов.

Рекомендую также закреплять версии всех ваших провайдеров:

provider "aws" {

  region = "us-east-2"

  # Разрешаем использовать любую версию провайдера AWS вида 2.x

  version = "~> 2.0"

}

Этот пример привязывает код провайдера AWS к версиям вида 2.x (синтаксис ~>2.0 эквивалентен >=2.0,<3.0). И снова, чтобы избежать случайного внесения обратно несовместимых изменений, необходимо закрепить мажорную версию. Закрепление конкретной версии зависит от провайдера. Предположим, провайдер AWS часто обновляется и имеет хорошую поддержку обратной совместимости, поэтому будет разумно закрепить только его мажорную версию и позволить автоматическое обновление заплаток, чтобы получить удобный доступ к новым возможностям. Однако все провайдеры разные, поэтому обращайте внимание на то, насколько хорошо они справляются с поддержкой обратной совместимости, и закрепляйте номера версий соответствующим образом.


Модули, готовые к повторному использованию

Следующим шагом после написания и тестирования модулей будет их выпуск. Как вы уже видели в разделе «Управление версиями» на с. 53, для этого подходят теги Git в сочетании с семантическим версионированием:

$ git tag -a "v0.0.5" -m "Create new hello-world-app module"

$ git push --follow-tags

Например, чтобы развернуть в тестовом окружении версию v0.0.5 вашего модуля hello-world-app, в файле live/stage/services/hello-world-app/main.tf следует прописать такой код:

provider "aws" {

  region = "us-east-2"

  # Разрешаем использовать любую версию провайдера AWS вида 2.x

  version = "~> 2.0"

}

module "hello_world_app" {

  # TODO: подставить сюда URL и версию вашего собственного модуля!!

  source = "git@github.com:foo/modules.git//services/hello-world-app?ref=v0.0.5"

  server_text            = "New server text"

  environment            = "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

  enable_autoscaling = false

}

Далее укажем доменное имя ALB в виде выходного параметра в файле live/stage/services/hello-world-app/outputs.tf:

output "alb_dns_name" {

  value       = module.hello_world_app.alb_dns_name

  description = "The domain name of the load balancer"

}

Теперь вы можете развернуть свой модуль с поддержкой версионирования, выполнив команды terraforminit и terraformapply:

$ terraform apply

(...)

Apply complete! Resources: 13 added, 0 changed, 0 destroyed.

Outputs:

alb_dns_name = hello-world-stage-477699288.us-east-2.elb.amazonaws.com

Если все работает хорошо, можете развернуть эту же версию (и, следовательно, этот же код) в других средах, включая промышленную. Если у вас когда-нибудь возникнет проблема, вы сможете откатиться назад, развернув более старую версию.