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

2. Каждый раз, когда вы вносите изменение в свою инфраструктуру, сквозной тест делает следующее:

а) применяет изменение инфраструктуры к тестовой среде;

б) выполняет проверку тестовой среды (например, с помощью Selenium для проверки вашего кода с точки зрения конечного пользователя), чтобы убедиться в том, что все работает.

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

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

Например, очень важно проверить, можно ли выкатывать обновления инфраструктуры без простоя. Созданный вами модуль asg-rolling-deploy должен поддерживать скользящие развертывания с нулевым временем простоя, но как это проверить? Специально для этого добавим автоматический тест.

Вам будет достаточно внести лишь несколько исправлений в тест test/hello_world_integration_test.go, который вы только что написали, так как внутри hello-world-app используется модуль asg-rolling-deploy. Для начала нужно сделать доступной переменную server_text в файле live/stage/services/hello-world-app/variables.tf:

variable "server_text" {

  description = "The text the web server should return"

  default     = "Hello, World"

  type        = string

}

Передайте эту переменную модулю hello-world-app в файле live/stage/services/hello-world-app/main.tf:

module "hello_world_app" {

  source = "../../../../modules/services/hello-world-app"

  server_text = var.server_text

  environment            = var.environment

  db_remote_state_bucket = var.db_remote_state_bucket

  db_remote_state_key    = var.db_remote_state_key

  instance_type      = "t2.micro"

  min_size           = 2

  max_size           = 2

  enable_autoscaling = false

}

В скрипте пользовательских данных модуля hello-world-app используется аргумент server_text, поэтому при каждом изменении этой переменной происходит развертывание с (как мы надеемся) нулевым временем простоя. Проверим это, добавив в конец файла test/hello_world_integration_test.go, сразу после validate_app, еще одну стадию тестирования под названием redeploy_app:

func TestHelloWorldAppStageWithStages(t *testing.T) {

        t.Parallel()

        // Сохраняем функцию в переменную с коротким именем,

        // просто чтобы примеры с кодом было легче уместить

        // на страницах этой книги.

        stage := test_structure.RunTestStage

        // Развертываем БД MySQL

        defer stage(t, "teardown_db", func() { teardownDb(t, dbDirStage) })

        stage(t, "deploy_db", func() { deployDb(t, dbDirStage) })

        // Развертываем приложение hello-world-app

        defer stage(t, "teardown_app", func() { teardownApp(t, appDirStage) })

        stage(t, "deploy_app", func() { deployApp(t, dbDirStage, appDirStage) })

        // Проверяем, работает ли hello-world-app

        stage(t, "validate_app", func() { validateApp(t, appDirStage) })

        // Развертываем hello-world-app заново

        stage(t, "redeploy_app", func() { redeployApp(t, appDirStage) })

}

Теперь реализуйте новый метод redeployApp:

func redeployApp(t *testing.T, helloAppDir string) {

        helloOpts := test_structure.LoadTerraformOptions(t, helloAppDir)

        albDnsName := terraform.OutputRequired(t, helloOpts, "alb_dns_name")

        url := fmt.Sprintf("http://%s", albDnsName)

        // Начинаем проверять раз в секунду, возвращает ли приложение 200 OK

        stopChecking := make(chan bool, 1)

        waitGroup, _ := http_helper.ContinuouslyCheckUrl(

                t,

                url,

                stopChecking,

                1*time.Second,

        )

        // Обновляем текст сервера и развертываем заново

        newServerText := "Hello, World, v2!"

        helloOpts.Vars["server_text"] = newServerText

        terraform.Apply(t, helloOpts)

        // Проверяем, развернута ли новая версия

        maxRetries := 10

        timeBetweenRetries := 10 * time.Second

http_helper.HttpGetWithRetryWithCustomValidation(

                t,

                url,

&tls.Config{},

                maxRetries,

                timeBetweenRetries,

                func(status int, body string) bool {

                        return status == 200 &&

                                strings.Contains(body, newServerText)

                },

        )

        // Завершаем проверку

        stopChecking <- true

        waitGroup.Wait()

}

Этот метод делает следующее.

1. Использует метод http_helper.ContinuouslyCheckUrl из состава Terratest, чтобы запустить в фоне горутину (легковесный поток, управляемый средой ­выполнения Go), которая раз в секунду делает HTTP-запрос типа GET по URL-адресу ALB и проваливает тест при получении любого ответа, кроме 200OK.

2. Обновляет переменную server_text, которая выполняет terraformapply, чтобы инициировать скользящее развертывание.

3. По завершении развертывания убеждается в том, что сервер возвращает в виде ответа новое значение server_text.

4. Останавливает горутину, которая непрерывно проверяет URL-адрес ALB.

Если после запуска этот тест дойдет до заключительной стадии, вы увидите, как непрерывные HTTP-вызовы типа GET к URL-адресу ALB начнут чередоваться с выводом команды terraformapply нашего скользящего развертывания:

$ SKIP_teardown_db=true \

  SKIP_teardown_app=true \

  go test -timeout 30m -run 'TestHelloWorldAppStageWithStages'

(...)

Making an HTTP GET call to URL

http://hello-world-test-thlMBF-168938547.us-east-2.elb.amazonaws.com

Got response 200 and err from URL at

http://hello-world-test-thlMBF-168938547.us-east-2.elb.amazonaws.com

Making an HTTP GET call to URL

http://hello-world-test-thlMBF-168938547.us-east-2.elb.amazonaws.com

Got response 200 and err from URL at

http://hello-world-test-thlMBF-168938547.us-east-2.elb.amazonaws.com

(...)

Running command terraform with args

[apply -input=false -lock=false -auto-approve]

(...)

Making an HTTP GET call to URL

http://hello-world-test-thlMBF-168938547.us-east-2.elb.amazonaws.com

Got response 200 and err from URL at

http://hello-world-test-thlMBF-168938547.us-east-2.elb.amazonaws.com

Making an HTTP GET call to URL

http://hello-world-test-thlMBF-168938547.us-east-2.elb.amazonaws.com

Got response 200 and err from URL at

http://hello-world-test-thlMBF-168938547.us-east-2.elb.amazonaws.com

(...)

PASS

Ok  terraform-up-and-running  551.04s

Успешное прохождение этого теста означает, что модули hello-world-app и asg-rolling-deploy, как и обещалось, способны выполнять скользящие развертывания с нулевым временем простоя! Каждый раз, когда ваш сквозной тест вносит инкрементальное изменение в вашу тестовую среду, вы можете использовать эту стратегию, чтобы убедиться в том, что развертывание не создает перебоев в работе.


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

Большая часть этой главы посвящена автоматическим тестам на основе Terratest, но существуют два других подхода к тестированию, которые не помешает иметь в своем арсенале:

• статический анализ;

• тестирование свойств.

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


Статический анализ

Существует несколько инструментов, которые умеют анализировать код Terraform без его запуска. Расскажу про основные.

•terraformvalidate. Эта команда встроена в Terraform, с ее помощью в коде можно искать синтаксические ошибки и опечатки (немного напоминает компилятор).

• tflint (https://github.com/wata727/tflint). «Линтер» для Terraform, который может просканировать ваш код в поисках распространенных ошибок и потенциальных проблем на основе встроенных правил.

• HashiCorp Sentinel (https://www.hashicorp.com/sentinel). Фреймворк типа «политика как код», который позволяет следить за соблюдением правил в разных продуктах HashiCorp. Например, вы можете создать политику, согласно которой вам нельзя будет создавать в своем коде правила группы безопасности, которые разрешают исходящие соединения с 0.0.0.0/0. На момент написания этой книги Sentinel можно использовать только в связке с продуктами HashiCorp Enterprise, включая Terraform Enterprise.


Тестирование свойств

Существует целый ряд средств тестирования для проверки определенных «свойств» вашей инфраструктуры:

• kitchen-terraform (https://github.com/newcontext-oss/kitchen-terraform);

• rspec-terraform (https://github.com/bsnape/rspec-terraform);

• serverspec (https://serverspec.org/);

• inspec (https://www.inspec.io/);

• goss (https://github.com/aelsabbahy/goss).

Большинство этих инструментов предоставляют предметно-ориентированный язык