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

Идеальный сценарий (или то, к чему вы на самом деле стремитесь) — это то, что я называю золотым правилом Terraform.

Основная ветка репозитория с текущей инфраструктурой должна один в один соответствовать тому, что на самом деле развернуто в промышленной среде.

Разберем это предложение на части, начав с конца.

«…что на самом деле развернуто». Чтобы гарантировать, что код Terraform в репозитории текущей инфраструктуры — актуальное представление того, что на самом деле развернуто, вы никогда не должны вносить сторонние изменения. Начав использовать Terraform, вы должны перестать менять свою инфраструктуру через веб-консоль, ручные API-вызовы или любой другой механизм. Как вы уже видели в главе 5, сторонние изменения не только приводят к сложным ошибкам, но и нивелируют многие преимущества от применения технологии IaC как таковой.

• «…один в один соответствовать…». При просмотре репозитория с текущей инфраструктурой мне нужна возможность быстро определить, какие ресурсы и в какой среде были развернуты. То есть каждый ресурс должен полностью соответствовать какой-то строчке кода, сохраненной в репозитории. На первый взгляд это кажется очевидным, но здесь на удивление легко ошибиться. Например, как я только что упомянул, вы можете внести сторонние изменения, в результате чего в коде будет одно, а в реальности — другое. Менее очевидной ошибкой является использование рабочих областей Terraform для управления окружениями таким образом, что инфраструктура может быть развернута без соответствующего кода. Иными словами, если вы используете рабочие области, репозиторий вашей текущей инфраструктуры будет содержать лишь одну копию кода, хотя на самом деле этот код мог развернуть 3 или 30 окружений. Поэтому по одному лишь коду нельзя понять, что действительно развернуто, а это непременно приведет к ошибкам и затруднит поддержку. Таким образом, как уже описывалось в подразделе «Изоляция через рабочие области» на с. 114, вместо управления окружениями с помощью рабочих областей каждое окружение следует описывать в отдельной папке, используя отдельные файлы. Так вы сможете точно сказать, какие окружения у вас развернуты, лишь просмотрев репозиторий текущей инфраструктуры. Позже в этой главе вы увидите, как это делать с минимальным дублированием кода.

• «Основная ветка…». Для понимания того, что на самом деле развернуто в промышленной среде, должно быть достаточно просмотра одной-единственной ветки. Обычно это основная ветка, master. Значит, все изменения, которые влияют на промышленную среду, должны сохраняться непосредственно в master (вы можете создавать отдельные ветки, но только для создания запросов на включение внесенных изменений с последующим их слиянием с основной веткой) и команду terraformapply для промышленной среды необходимо выполнять только для этой ветки. Почему так, я объясню в следующем пункте.


Проблема с ветками

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

Представьте, что ваша коллега, Анна, вносит какие-то изменения в конфигурацию Terraform для приложения под названием foo, которое состоит из единственного сервера Amazon EC2:

resource "aws_instance" "foo" {

  ami           = "ami-0c55b159cbfafe1f0"

  instance_type = "t2.micro"

}

Это приложение получает много запросов, поэтому Анна решает поменять instan­ce_type с t2.micro на t2.medium:

resource "aws_instance" "foo" {

  ami           = "ami-0c55b159cbfafe1f0"

  instance_type = "t2.medium"

}

Это она увидит при выполнении команды terraformplan:

$ terraform plan

(...)

Terraform will perform the following actions:

  # aws_instance.foo will be updated in-place

  ~ resource "aws_instance" "foo" {

        ami                          = "ami-0c55b159cbfafe1f0"

        id                           = "i-096430d595c80cb53"

        instance_state               = "running"

      ~ instance_type                = "t2.micro" -> "t2.medium"

        (...)

    }

Plan: 0 to add, 1 to change, 0 to destroy.

Изменения кажутся приемлемыми, поэтому Анна развертывает их в среде Staging.

А тем временем Билл начинает вносить изменения в конфигурацию Terraform того же приложения, но в другой ветке. Он просто хочет добавить тег:

resource "aws_instance" "foo" {

  ami           = "ami-0c55b159cbfafe1f0"

  instance_type = "t2.micro"

  tags = {

    Name = "foo"

  }

}

Внесенные Анной изменения уже развернуты в Staging, но, поскольку они хранятся в другой ветке, в коде Билла по-прежнему содержится старое значение для поля instance_type. Это видит Билл, когда выполняет команду plan (следующий вывод урезан, чтобы его было легче читать):

$ terraform plan

(...)

Terraform will perform the following actions:

  # aws_instance.foo will be updated in-place

  ~ resource "aws_instance" "foo" {

        ami                          = "ami-0c55b159cbfafe1f0"

        id                           = "i-096430d595c80cb53"

        instance_state               = "running"

      ~ instance_type                = "t2.medium" -> "t2.micro"

      + tags = {

          + "Name" = "foo"

        }

        (...)

    }

Plan: 0 to add, 1 to change, 0 to destroy.

О нет, он собирается отменить изменение поля instance_type, сделанное Анной! Если девушка по-прежнему занимается тестированием в среде Staging, она будет крайне удивлена, когда сервер внезапно развернется заново и начнет вести себя иначе. Но есть и хорошие новости: если Билл внимательно прочитает вывод коман­ды plan, он сможет заметить ошибку еще до того, как она затронет Анну. Тем не менее этот пример иллюстрирует проблемы, которые могут возникнуть при развертывании изменений из разных веток в общую среду.

Установленное хранилищами Terraform блокирование здесь не поможет, так как этот конфликт не имеет никакого отношения к конкурентному изменению файла состоя­ния. Билл и Анна могут применять свои изменения с разницей в несколько недель, но проблема останется прежней. Основная причина в том, что ветвление и Terraform плохо сочетаются между собой. Terraform косвенным образом привязывает код к развернутой в реальном мире инфраструктуре. Поскольку реальность у нас с вами одна, разделение кода Terraform на ветки имеет мало смысла. Поэтому при работе с любой общей средой (например, Stage, Prod) всегда развертывайте код из одной ветки.


Локальное выполнение кода

Следующим шагом после загрузки кода на свой компьютер будет его запуск. Но есть одна загвоздка: в отличие от прикладного код Terraform не имеет такого понятия, как «локальный компьютер». Например, вы не можете развернуть AWS ASG на своем ноутбуке. Как уже обсуждалось в подразделе «Основы ручного тестирования» на с. 255, чтобы протестировать код Terraform вручную, его необходимо запустить в изолированной среде, такой как учетная запись AWS, специально выделенная для разработчиков (еще лучше, если у каждого разработчика будет своя учетная запись AWS).

Подготовив изолированную среду, вы можете начать ручное тестирование, выполнив команду 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:

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

Hello, World

Для автоматических тестов, написанных на Go, в изолированной учетной записи, предназначенной для тестирования, выполняется команда gotest:

$ go test -v -timeout 30m

(...)

PASS

ok   terraform-up-and-running   229.492s


Внесение изменений в код

Получив возможность запускать свой код Terraform, можно приступить к внесению итеративных изменений — точно так же, как вы это делали с кодом приложения. Для развертывания изменений можно заново выполнить terraformapply, а чтобы убедиться в том, что они работают, можно снова запустить curl:

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

Hello, World v2

Чтобы проверить, по-прежнему ли код проходит тесты, можно выполнить gotest:

$ go test -v -timeout 30m

(...)

PASS

ok   terraform-up-and-running   229.492s

Единственное отличие от прикладного кода в том, что тестирование инфраструктурного кода обычно занимает больше времени, поэтому вам стоит подумать над тем, как сократить этот процесс, чтобы получать результаты как можно быстрее. Из пункта «Стадии тестирования» на с. 299 вы узнали, что можно разбить тест на отдельные стадии и выполнять только те из них, которые вам нужны, что существенно ускоряет обратную связь.

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

$ SKIP_teardown_db=true \

  SKIP_teardown_app=true \

  go test -timeout 30m -run 'TestHelloWorldAppStageWithStages'

(...)

PASS

ok   terraform-up-and-running   423.650s

Затем при каждом изменении приложения вы мо