В мире 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"]
launch_template {
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.
Triggers:
triggers— это конструкция в Terraform, которая указывает, при каких условиях ресурс должен быть пересоздан. В нашем случае, каждый раз, когда версияlaunch_templateвaws_autoscaling_group.exampleизменяется, Terraform запустит Ansible playbook. Это гарантирует, что после каждого обновления ASG Ansible будет вызван для отслеживания статусаinstance refresh.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.
Первая проверка:
updated_asg_status.results[0].instances
| map(attribute='launch_template.version')
| union([updated_asg_status.results[0].launch_template.version])
| length == 1Эта проверка выполняет следующие действия:
Извлекает версии шаблонов запуска всех инстансов в ASG.
Соединяет полученный список версий с версией шаблона запуска ASG.
Проверяет, что все версии совпадают, то есть список содержит только одну уникальную версию.
Вторая проверка:
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 поможет построить более эффективную и надежную систему обновления сервисов.
