В мире DevOps, где автоматизация играет ключевую роль, управление ресурсами и процессами обновления инфраструктуры в облаке является критически важной задачей. Во многих современных проектах, особенно тех, что развертываются в облачной среде AWS, используется механизм Auto Scaling Groups (ASG) с целью достижения трех основных задач: балансировки нагрузки, повышения надежности сервиса и оптимизации стоимости эксплуатации.

Представьте себе: вы работаете в компании, развертывающей свои приложения на ресурсах Amazon. И чтобы ускорить процесс развертывания и упростить управление конфигурацией, вы используете предварительно подготовленные AMI образы. Эти образы создаются с помощью инструментов типа HashiCorp Packer (или других аналогичных) и содержат все необходимое для того, чтобы ваше приложение стартовало быстро и без сбоев. Для разворачивания самой инфраструктуры вы используете Terraform, который стал стандартом de facto во многих крупных компаниях, управляющих облачными ресурсами и использующими подход IaC (Infrastructure as Code).

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

К счастью, ASG предоставляет решение в виде instance refresh, мощного инструмента, который позволяет обновить инстансы внутри группы, минимизируя простои и обеспечивая высокую доступность. Все бы ничего, но как удостовериться, что обновление прошло успешно, особенно если речь идет о больших и сложных системах?

К сожалению, ресурсы Terraform (например тот же aws_autoscaling_group) не позволяют отслеживать прогресс и успешность выполнения операции обновления ASG в рамках instance refresh, а могут лишь запустить его. Если какие-то другие части инфраструктуры (например, обновления сертификатов или dns-записей) каким-то образом зависят от состояния и версии запущенных инстансов, то желательно проконтролировать завершение процесса обновления для получения корректного состояния инфраструктуры после завершения работы terraform.

Чтобы решить данную проблему, вводим в игру Ansible. Этот инструмент, который уже давно зарекомендовал себя в управлении конфигурацией и автоматизации, может помочь и здесь. Именно Ansible позволит нам контролировать процесс обновления и удостовериться в его успешном завершении. Таким образом, объединив Terraform и Ansible, можно создать мощное и гибкое решение для управления и обновления ASG в AWS.

1. Подготовка Terraform

Первым шагом будет создание конфигурации Terraform, которая обеспечивает необходимую структуру и процесс для обновления ASG.

resource "aws_autoscaling_group" "example" {
  desired_capacity     = 3
  max_size             = 5
  min_size             = 2
  vpc_zone_identifier  = ["subnet-0bb1c79de3EXAMPLE"]

  Т {
    id      = aws_launch_template.example.id
    version = aws_launch_template.example.latest_version
  }
  
  instance_refresh {
    strategy = "Rolling"
    preferences {
      min_healthy_percentage = 100
      instance_warmup        = 120
    }
    triggers = ["tag"]
  }

  health_check_type          = "EC2"
  force_delete               = true
  wait_for_capacity_timeout  = "0"
}

Детальный разбор блока instance_refresh:

Этот блок важен, так как он задает параметры для обновления инстансов в ASG:

  • strategy = "Rolling": Эта стратегия гарантирует, что обновление будет выполняться пошагово, что минимизирует возможные проблемы с доступностью сервисов.

  • preferences: Этот блок содержит две ключевые настройки:

    • min_healthy_percentage = 100: Указывает, что в процессе обновления должен сохраняться 100%-ный уровень здоровья группы, что крайне важно для поддержания надежности сервиса.

    • instance_warmup = 120: Это время в секундах, которое позволяет новым инстансам "прогреться" перед тем, как они будут введены в эксплуатацию.

  • triggers = ["tag"]: Это триггеры, которые инициируют обновление инстансов при изменении указанных атрибутов. Это полезно, например, при изменении тегов ресурсов.

Также следует обратить особое внимание на блок launch_template. Часто там ставят просто `version = "$Latest"`. Так делать не нужно. Если вы установите значение $Latest для version, это означает, что autoscaling-группа всегда будет использовать последнюю версию шаблона запуска при создании новых экземпляров EC2. Однако это не вызовет автоматического обновления уже запущенных экземпляров, даже если шаблон изменится.

Для того чтобы инициировать процесс обновления экземпляров (instance refresh) при изменении шаблона, вы должны использовать значение latest_version от ресурса aws_launch_template в качестве версии шаблона. Таким образом, при каждом изменении шаблона и последующем применении Terraform он будет видеть изменения в версии шаблона и инициировать обновление экземпляров.

Теперь добавим в Terraform вызов Ansible, который нам нужен для контроля процесса обновления. Для этого используем специальный ресурс Terraform, известный под именем null_resource:

resource "null_resource" "ansible_run" {
  triggers = {
    template_version = aws_autoscaling_group.example.launch_template[0].version
  }

  provisioner "local-exec" {
    command = join(" ",
      [
        "ansible-playbook ${path.module}/asg_refresh_handler.yml -i 'localhost,'",
        "-e asg_name=${aws_autoscaling_group.example.name}"
      ]
    )
}

В Terraform, null_resource — это способ выполнения действий, которые не связаны с каким-либо фактическим ресурсом облачного провайдера. Этот ресурс идеален для интеграции внешних инструментов, таких как Ansible.

  1. Triggers: triggers — это конструкция в Terraform, которая указывает, при каких условиях ресурс должен быть пересоздан. В нашем случае, каждый раз, когда версия launch_template в aws_autoscaling_group.example изменяется, Terraform запустит Ansible playbook. Это гарантирует, что после каждого обновления ASG Ansible будет вызван для отслеживания статуса instance refresh.

  2. Provisioner "local-exec": Этот provisioner указывает Terraform выполнить команду на локальной машине. В данном случае мы запускаем Ansible playbook.

    • ansible-playbook ${path.module}/asg_refresh_waiter.yml

      указывает путь к нашему playbook.

    • -i 'localhost,' задает Ansible работать на локальной машине.

    • -e asg_name=${aws_autoscaling_group.example.name} передает Ansible имя autoscaling группы, с которой нужно работать.

Таким образом, каждый раз, когда Terraform обновляет ASG из-за изменений в launch_template, он автоматически вызывает Ansible для отслеживания процесса instance refresh.

2. Составляем Ansible Playbook

Давайте теперь перейдем к разработке Ansible Playbook, который будет отслеживать процесс обновления, исходя из данных, полученных от AWS. Как можем увидеть из кода выше, нам нужен файл с именем asg_refresh_waiter.yml,который мы расположим в той же директории, что и код нашего модуля для terraform.

---
- name: ASG Refresh Handler
  hosts: localhost
  gather_facts: false
  connection: local
  tasks:

    - name: Obtain ASG Information
      amazon.aws.ec2_asg_info:
        name: '{{ asg_name }}'
      register: asg_status

    - name: Display ASG Instances
      debug:
        msg: '{{ asg_status.results[0].instances }}'

    - name: Display ASG Launch Template Info
      debug:
        msg: '{{ asg_status.results[0].launch_template }}'

    - name: Await Instance Refresh Completion
      amazon.aws.ec2_asg_info:
        name: '{{ asg_name }}'
      register: updated_asg_status
      retries: 300
      until:
        - >-
          updated_asg_status.results[0].instances
            | map(attribute='launch_template.version')
            | union([updated_asg_status.results[0].launch_template.version])
            | length == 1
        - >-
          updated_asg_status.results[0].instances
            | map(attribute='launch_template.version')
            | unique
            | length == 1
      when: asg_status.results[0].launch_template.version is defined

    - name: Display Updated Instances
      debug:
        msg: '{{ updated_asg_status.results[0].instances }}'

Разберем детали:

  • Obtain ASG Information: Этот таск извлекает текущую информацию о ASG, что позволяет оценить, требуется ли обновление и возможно ли его выполнение.

  • Display ASG Instances и Display ASG Launch Template Info: Эти задачи помогают в отладке, выводя текущую информацию о состоянии инстансов и шаблона запуска.

  • Await Instance Refresh Completion: Это сердце нашего playbook. Здесь мы используем механизм retries/until, что позволяет нам отслеживать процесс обновления до его завершения:

    • retries: 300 указывает, что таск будет повторяться до 300 раз, пока условие until не выполнится.

    • Этот таск использует условие until с двумя условиями для определения завершения процесса обновления.

Разбор условий в блоке until:

В задаче Await Instance Refresh Completion, в блоке until представлены две проверки. Эти проверки нужны для удостоверения, что все инстансы были обновлены до последней версии Launch Template.

  1. Первая проверка:

updated_asg_status.results[0].instances
  | map(attribute='launch_template.version')
  | union([updated_asg_status.results[0].launch_template.version])
  | length == 1

Эта проверка выполняет следующие действия:

  • Извлекает версии шаблонов запуска всех инстансов в ASG.

  • Соединяет полученный список версий с версией шаблона запуска ASG.

  • Проверяет, что все версии совпадают, то есть список содержит только одну уникальную версию.

  1. Вторая проверка:

updated_asg_status.results[0].instances
  | map(attribute='launch_template.version')
  | unique
  | length == 1

Вторая проверка убеждается в отсутствии различий между версиями Launch Templates среди инстансов, гарантируя, что все инстансы обновлены до последней версии.

Заключение

Если все сделано верно, то при запуске кода Terraform и появлении новой версии AMI будет обновлена версия launch_template для autoscaling группы, и автоматически будет запущен процесс instance refresh. После чего Terraform запустит Ansible playbook с указанными нами параметрами, передав плейбуку значение имени autoscaling группы.

Запущенный Ansible плейбук будет проверять состояние ASG и версию шаблонов у запущенных экземпляров машин в течение заданного времени, дожидаясь, пока все версии запущенных машин не станут той же версии, что и обновленная версия launch_template для ASG.

Приведенный пример кода Ansible плейбука довольно универсален и зависит от единственного входного параметра - имени autoscaling группы. Поэтому может быть легко использован практически в любом окружении и с любым terraform кодом без изменений.

Надеюсь, что кому-то данный пример сочетания Terraform и Ansible поможет построить более эффективную и надежную систему обновления сервисов.

Комментарии (0)