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

Как видите, Terraform хочет внести два изменения: заменить старую конфигурацию запуска новой с обновленным полем user_data и модифицировать уже имеющуюся группу автомасштабирования так, чтобы она ссылалась на новую конфигурацию запуска. Проблема в том, что во втором случае изменения не вступят в силу, пока ASG не запустит новые серверы EC2. Как же заставить ASG их развернуть?

Как вариант, вы можете уничтожить группу ASG (например, с помощью команды terraformdestroy) и затем создать ее заново (скажем, выполнив terraformapply). Но проблема этого способа в том, что после удаления старого экземпляра ASG и до загрузки нового ваши пользователи будут испытывать перебои в работе. Вместо этого лучше сделать развертывание с нулевым временем простоя. Для этого нужно сначала создать новую группу ASG и затем удалить старую. Оказывается, именно это делает параметр жизненного цикла create_before_destroy, с которым вы впервые столкнулись в главе 2. Рассмотрим, как организовать развертывание с нулевым временем простоя, используя этот параметр жизненного цикла47.

1. Сконфигурируйте параметр name для ASG так, чтобы он напрямую зависел от имени конфигурации запуска. При каждом изменении этой конфигурации (которое происходит в результате обновления AMI или пользовательских данных) будет меняться и ее название, а вместе с ним и имя ASG. Это заставит Terraform заменить группу автомасштабирования.

2. Присвойте true параметру create_before_destroy группы ASG, чтобы каждый раз, когда ее нужно заменить, система Terraform сначала создавала ее замену, и только потом удаляла оригинал.

3. Присвойте параметру min_elb_capacity группы ASG значение min_size, принадлежащее кластеру. Благодаря этому, прежде чем уничтожать оригинал, Terraform будет ждать, пока как минимум min_size серверов из новой группы ASG не пройдут проверку работоспособности в ALB.

Так должен выглядеть обновленный ресурс aws_autoscaling_group в файле modules/services/webserver-cluster/main.tf:

resource "aws_autoscaling_group" "example" {

  # Создаем явную зависимость от имени конфигурации запуска,

  # чтобы вместе с ней заменялась и группа ASG

  name = "${var.cluster_name}-${aws_launch_configuration.example.name}"

  launch_configuration = aws_launch_configuration.example.name

  vpc_zone_identifier  = data.aws_subnet_ids.default.ids

  target_group_arns    = [aws_lb_target_group.asg.arn]

  health_check_type    = "ELB"

  min_size = var.min_size

  max_size = var.max_size

  # Ждем, пока проверку работоспособности не пройдет как минимум

  # столько серверов, прежде чем считать завершенным развертывание ASG

  min_elb_capacity = var.min_size

  # При замене этой группы ASG сначала создаем ее новую версию

  # и только потом удаляем старую

  lifecycle {

    create_before_destroy = true

  }

  tag {

    key                 = "Name"

    value               = var.cluster_name

    propagate_at_launch = true

  }

  dynamic "tag" {

    for_each = {

      for key, value in var.custom_tags:

       key => upper(value)

      if key != "Name"

    }

    content {

      key                 = tag.key

      value               = tag.value

      propagate_at_launch = true

    }

  }

}

Если снова выполнить команду plan, результат будет примерно таким:

Terraform will perform the following actions:

  # module.webserver_cluster.aws_autoscaling_group.example must be replaced

+/- resource "aws_autoscaling_group" "example" {

      ~ id     = "example-2019" -> (known after apply)

      ~ name   = "example-2019" -> (known after apply) # forces replacement

        (...)

    }

  # module.webserver_cluster.aws_launch_configuration.example must be replaced

+/- resource "aws_launch_configuration" "example" {

      ~ id              = "terraform-2019" -> (known after apply)

        image_id        = "ami-0c55b159cbfafe1f0"

        instance_type   = "t2.micro"

      ~ name            = "terraform-2019" -> (known after apply)

      ~ user_data       = "bd7c0a" -> "4919a" # forces replacement

        (...)

    }

    (...)

Plan: 2 to add, 2 to change, 2 to destroy.

Главное, на что следует обратить внимание, — это строка forcesreplacement напротив параметра name ресурса aws_autoscaling_group. Это означает, что Terraform заменит этот ресурс новой группой ASG с новым образом AMI или новыми пользовательскими данными. Выполните команду apply, чтобы инициировать развертывание, и проследите за тем, как этот процесс работает.

Сначала у нас запущена оригинальная группа ASG, скажем, версии v1 (рис. 5.2).

Вы обновляете некоторые аспекты конфигурации запуска (например, переходите на образ AMI с кодом версии v2) и выполняете команду apply. Это заставляет Terraform начать процесс развертывания нового экземпляра ASG с кодом версии v2 (рис. 5.3).

После 1–2 минут серверы в новой группе ASG завершили загрузку, подключились к базе данных, зарегистрировались в ALB и начали проходить проверку работоспособности. На этом этапе обе версии вашего приложения, v1 и v2, работают параллельно, и то, какую из них видит пользователь, зависит от того, куда ALB направил его запрос (рис. 5.4).

Рис. 5.2. Сначала у вас есть исходная группа ASG, выполняющая код версии v1

Рис. 5.3. Terraform начинает развертывание нового экземпляра ASG с кодом версии v2

Рис. 5.4. Серверы в новой группе ASG загрузились, подключились к БД, зарегистрировались в ALB и начали обслуживать запросы

После того, как min_elb_capacity серверов из кластера ASG версии v2 зарегистрировалось в ALB, Terraform начинает удалять старую группу ASG. Сначала отменяется их регистрация в ALB, а затем они выключаются (рис. 5.5).

Через 1–2 минуты старая группа ASG исчезнет, и у вас останется только версия v2 вашего приложения в новом экземпляре ASG (рис. 5.6).

Весь процесс у вас всегда будут оставаться рабочие серверы, обслуживающие запросы от ALB, поэтому простоя не наблюдается. Открыв URL-адрес ALB в своем браузере, вы должны увидеть что-то похожее на рис. 5.7.

Рис. 5.5. Серверы из старой группы ASG начинают выключаться

Рис. 5.6. Теперь остается только новая группа ASG, которая выполняет код версии v2

Получилось! Сервер с новым сообщением был развернут. Можете провести увлекательный эксперимент: внесите еще одно изменение в параметр server_text (например, поменяйте текст на foobar) и выполните команду apply. Если вы работаете в Linux/Unix/OS X, можете открыть отдельную вкладку терминала и запустить однострочный bash-скрипт, который будет циклически вызывать curl, обращаясь к ALB раз в секунду. Это наглядно продемонстрирует, как происходит развертывание с нулевым временем простоя:

$ while true; do curl http://; sleep 1; done

Рис. 5.7. Новый код уже развернут

Где-то на протяжении первой минуты ответ должен оставаться прежним: Newservertext. Затем вы заметите чередование Newservertext и foobar. Значит, новые серверы зарегистрировались в ALB и прошли проверку работоспособности. Еще через минуту сообщение Newservertext исчезнет, и вы будете видеть только foobar. Это означает, что старая группа ASG была отключена. Вывод будет выглядеть примерно так (для ясности я вывожу только содержимое тегов

):

New server text

New server text

New server text

New server text

New server text

New server text

foo bar

New server text

foo bar

New server text

foo bar

New server text

foo bar

New server text

foo bar

New server text

foo bar

foo bar

foo bar

foo bar

foo bar

foo bar

У этого подхода есть еще одно преимущество: если во время развертывания что-то пойдет не так, Terraform автоматически откатит все назад. Например, если в версии v2 вашего приложения допущена ошибка, из-за которой оно не может загрузиться, серверы из новой группы ASG не будут зарегистрированы в ALB. Terraform будет ждать регистрации min_elb_capacity серверов из ASG v2 на протяжении отрезка времени длиной wait_for_capacity_timeout (по умолчанию десять минут). После этого развертывание считается неудачным, серверы ASG v2 удаляются, а Terraform завершает работу с ошибкой (тем временем версия v1 вашего приложения продолжает нормально работать в оригинальной группе ASG).


Подводные камни Terraform

После рассмотрения всех этих советов и приемов стоит сделать шаг назад и выделить несколько подводных камней, включая те, что связаны с циклами, выражениями if и методиками развертывания, а также с более общими проблемами, которые касаются Terraform в целом:

• параметры count и for_each имеют ограничения;

• ограничения развертываний с нулевым временем простоя;

• даже хороший план может оказаться неудачным;

• рефакторинг может иметь свои подвохи;

• отложенная согласованность согласуется… с отлагательством.


Параметры count и for_each имеют ограничения

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

• В count и for_each нельзя ссылаться ни на какие выходные переменные ресурса.

•count и for_each нельзя использовать в конфигурации модуля.

Рассмотрим их по очереди.


В count и for_each нельзя ссылаться ни на какие выходные переменные ресурса

Представьте, что нужно развернуть несколько серверов EC2 и по какой-то причине вы не хотите использовать ASG. Ваш код может быть таким:

resource "aws_instance" "example_1" {

  count         = 3

  ami           = "ami-0c55b159cbfafe1f0"