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

3. Создайте папку modules и реализуйте придуманный вами API в виде набора небольших, универсальных и компонуемых модулей. Используйте для этого Terraform в сочетании с другими инструментами, такими как Docker, Packer и bash. Не забудьте закрепить версии Terraform и провайдера.

4. Создайте папку test и напишите автоматические тесты для каждого примера.

Теперь пришло время обсудить написание автоматических тестов для инфраструктурного кода, чем мы и займемся в главе 7.

48 Хофштадтер Д. Гедель, Эшер, Бах: эта бесконечная гирлянда. — Самара: Бахрах-М, 2001.

49 Don’t Shave That Yak! (блог Сета, 5 марта 2005 года), bit.ly/2OK45uL.

50 Брукс Ф. Мифический человеко-месяц, или Как создаются программные системы. — СПб.: Питер, 2020.

51 Мартин Р. Чистый код. Создание, анализ и рефакторинг. Библиотека программиста. — СПб.: Питер, 2019.

52 Salus P.H. A Quarter-Century of Unix. — N.Y.: Addison-Wesley Professional, 1994.

53 Все подробности о публикации модулей можно найти на странице bit.ly/2M53hi0.

54 Полный список средств инициализации ресурсов можно найти на странице bit.ly/2M7s1pK.

7. Как тестировать код Terraform

Мир DevOps полон разных страхов: все боятся допустить простой в работе, потерять данные или быть взломанными. При внесении любого изменения вы всегда спрашиваете себя, какие последствия оно будет иметь. Будет ли оно вести себя одинаково во всех окружениях? Вызовет ли оно очередной перебой в работе? И, если это случится, насколько вам придется задержаться на работе на этот раз, чтобы все исправить? По мере роста компании все больше ставится на кон, что делает процесс развертывания еще страшнее и повышает риск ошибок. Многие компании пытаются минимизировать этот риск за счет менее частых развертываний, но в итоге каждое отдельное развертывание становится более крупным и склонным к ошибкам.

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

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

• Ручные тесты:

• основы ручного тестирования;

• очистка ресурсов после тестов.

• Автоматические тесты:

• модульные тесты;

• интеграционные тесты;

• сквозные тесты;

• другие подходы к тестированию.


Примеры кода

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


Ручные тесты

Размышляя о том, как тестировать код Terraform, полезно провести некоторые параллели с тестированием кода, написанного на языках программирования общего назначения, таких как Ruby. Представьте, что вы пишете простой веб-сервер на Ruby в файле web-server.rb:

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'

    else

      response.status = 404

      response['Content-Type'] = 'text/plain'

      response.body = 'Not Found'

    end

  end

end

Этот код вернет ответ 200OK с телом Hello,World для URL-адреса /; для любого другого адреса ответ будет 404. Как бы вы протестировали этот код вручную? Обычно для этого добавляют еще немного кода, чтобы запускать веб-сервер локально:

# Этот код выполняется, только если скрипт был вызван непосредственно

# из терминала, но не при подключении из другого файла

if __FILE__ == $0

  # Запускаем сервер локально на порте 8000

  server = WEBrick::HTTPServer.new :Port => 8000

  server.mount '/', WebServer

  # Останавливаем сервер нажатием Ctrl+C

  trap 'INT' do server.shutdown end

  # Запускаем сервер

  server.start

end

Если запустить этот файл в терминале, он загрузит веб-сервер на порте 8000:

$ ruby web-server.rb

[2019-05-25 14:11:52] INFO  WEBrick 1.3.1

[2019-05-25 14:11:52] INFO  ruby 2.3.7 (2018-03-28) [universal.x86_64-darwin17]

[2019-05-25 14:11:52] INFO  WEBrick::HTTPServer#start: pid=19767 port=8000

Чтобы проверить работу этого сервера, можно воспользоваться браузером или curl:

$ curl localhost:8000/

Hello, World

$ curl localhost:8000/invalid-path

Not Found

Теперь представьте, что мы изменили этот код, добавив в него точку входа /api, которая возвращает 201Created и тело в формате JSON:

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'

    when "/api"

      response.status = 201

      response['Content-Type'] = 'application/json'

      response.body = '{"foo":"bar"}'

    else

      response.status = 404

      response['Content-Type'] = 'text/plain'

      response.body = 'Not Found'

    end

  end

end

Чтобы вручную протестировать этот обновленный код, нажмите Ctrl+C и перезагрузите веб-сервер, запустив скрипт еще раз:

$ ruby web-server.rb

[2019-05-25 14:11:52] INFO  WEBrick 1.3.1

[2019-05-25 14:11:52] INFO  ruby 2.3.7 (2018-03-28) [universal.x86_64-darwin17]

[2019-05-25 14:11:52] INFO  WEBrick::HTTPServer#start: pid=19767 port=8000

^C

[2019-05-25 14:15:54] INFO  going to shutdown ...

[2019-05-25 14:15:54] INFO  WEBrick::HTTPServer#start done.

$ ruby web-server.rb

[2019-05-25 14:11:52] INFO  WEBrick 1.3.1

[2019-05-25 14:11:52] INFO  ruby 2.3.7 (2018-03-28) [universal.x86_64-darwin17]

[2019-05-25 14:11:52] INFO  WEBrick::HTTPServer#start: pid=19767 port=8000

Для проверки новой версии можно снова воспользоваться командой curl:

$ curl localhost:8000/api

{"foo":"bar"}


Основы ручного тестирования

Как будет выглядеть подобного рода ручное тестирование в Terraform? Например, из предыдущих глав у вас остался код для развертывания ALB. Вот фрагмент файла modules/networking/alb/main.tf:

resource "aws_lb" "example" {

  name               = var.alb_name

  load_balancer_type = "application"

  subnets            = var.subnet_ids

  security_groups    = [aws_security_group.alb.id]

}

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

    }

  }

}

resource "aws_security_group" "alb" {

  name = var.alb_name

}

# (...)

Если сравнить этот листинг с кодом на Ruby, можно заметить одно довольно очевидное отличие: AWS ALB, целевые группы, прослушиватели, группы безопасности и любые другие ресурсы нельзя развертывать на собственном компьютере.

Из этого следует ключевой вывод о тестировании № 1: тестирование кода Terraform не может проходить локально.

Это относится не только к Terraform, но и к большинству средств IaC. Единственный практичный способ выполнять ручное тестирование в Terraform — развернуть код в реальном окружении (то есть в AWS). Иными словами, самостоятельный запуск команд terraformapply и terraformdestroy, которым вы занимались, читая книгу, — это и есть ручное тестирование в Terraform.

Это одна из причин, почему так важно иметь в папке examples каждого модуля простые в развертывании примеры (см. главу 6). Чтобы протестировать модуль alb, проще всего воспользоваться демонстрационным кодом, который вы создали в examples/alb:

provider "aws" {

  region = "us-east-2"

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

  version = "~> 2.0"

}

module "alb" {

  source = "../../modules/networking/alb"

  alb_name   = "terraform-up-and-running"

  subnet_ids = data.aws_subnet_ids.default.ids

}

Чтобы развернуть этот пример, нужно выполнить команду terraformapply, как вы это неоднократно делали:

$ terraform apply

(...)

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

Outputs:

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

В конце развертывания можно использовать такой инструмент, как curl, чтобы, к примеру, убедиться, что по умолчанию ALB возвращает 404:

$ curl \

  -s \

  -o /dev/null \

  -w "%{http_code}" \

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

404


Проверка инфраструктуры

Наша инфраструктура включает в себя балансировщик нагрузки, работающий по HTTP, поэтому, чтобы убедиться в ее работоспособности, мы используем curl и HTTP-запросы. Другие типы инфраструктуры могут потребовать иные методы проверки. Например, если ваш инфраструктурный код развертывает базу данных MySQL, для его тестирования придется использовать клиент MySQL. Если ваш инфраструктурный код развертывает VPN-сервер, для его тестирования понадобится клиент VPN. Если ваш инфраструктурный код развертывает сервер, который вообще не прослушивает никаких запросов, вам придется зайти на сервер по SSH и выполнить локально ко