Автор статьи: Рустем Галиев

IBM Senior DevOps Engineer & Integration Architect. Официальный DevOps ментор и коуч в IBM

Привет, Хабр! Давайте поговорим про асинхронность в Ansible, а именно о том, что это и зачем использовать.

В Ansible есть понятие Async Actions. Ansible устанавливает подключение к целевым серверам через SSH. Это означает, что соединения SSH остаются активными на протяжении всего выполнения таска в плейбуке. Иногда нам может понадобиться выполнить длительный таск или процесс, который превышает тайм-аут SSH.

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

Допустим, в рамках развертывания нашего веб-приложения, есть скрипт, который отслеживает и выполняет некоторые проверки, скажем healthchecks на веб-сервере. Он находится по пути opt/monitor/checks.py. Скрипты выполняют различные проверки, как например проверку стабильности, чтобы убедиться, что веб-сервер остается в сети не менее пяти минут без каких-либо проблем. Таким образом, очевидно, что для завершения выполнения этого скрипта требуется не менее пяти минут.

Мы не хотим, чтобы Ansible поддерживал сеанс SSH, пока скрипт не завершит выполнение. Мы хотим сообщить Ansible, что это асинхронная задача (таск), поэтому надо запустить ее и проверять, скажем, каждую минуту. Мы используем директиву async, чтобы указать максимальное время, в течение которого ожидаем выполнения таска. В нашем случае, скажем, шесть минут, учитывая другие проверки, которые необходимо выполнить.

Директива async сообщает Ansible, что это асинхронный таск, поэтому просто пусть запустит ее и проверит позже.

И как часто Ansible проверяет статус скрипта?

По умолчанию Ansible проверяет каждые 10 секунд. Если мы хотим изменить это, можно использовать директиву poll. Проверять каждые 10 секунд для нас слишком часто, и мы не хотим ждать пять минут, потому что вдруг скрипт выйдет в ошибку после первой минуты? Мы также не хотим тратить оставшиеся четыре минуты на ожидание, пока Ansible проверит статус. В связи с чем проверка каждую минуту кажется хорошей идеей.

Устанавливаем значение poll на 60. Давайте добавим еще один таск  для мониторинга, например, баз данных и дадим ему шесть минут на выполнение и poll каждые 60 секунд.

name: Deploy WebApp
hosts: webhost1
tasks:
   - command: /opt/monitor/checks.py
 	async: 360
 	poll: 60
	 
   - command: /opt/monitor/db.py
 	async: 360
 	poll: 60

Что здесь происходит? Когда Ansible начинает выполнение, он сначала выполняет первую таску, и все равно будет ждать завершения этой таски.

Асинхронность не означает, что Ansible будет запускать это и двигаться вперед. Он будет отслеживать состояние скрипта и ждать, пока не истечет указанное время. В данном случае 360 секунд.

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

Если бы мы запустили их параллельно, то могли бы сэкономить много времени.

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

Установив значение poll в ноль, мы просим Ansible не ждать, а сразу перейти к следующему таску.

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

Для этого сначала нужно зарегистрировать результат тасок в переменной. Итак, в этом случае мы регистрируем результат первой таски, которая отслеживает веб-приложение, в переменную с именем webapp_result. Также регистрируем результат второй таски, которая заключается в мониторинге базы данных, в переменную db_result.

name: Deploy WebApp
hosts: webhost1
tasks:
   - command: /opt/monitor/checks.py
 	async: 360
 	poll: 0
 	register: webapp_result

   - command: /opt/monitor/db.py
 	async: 360
 	poll: 0
 	register: db_result

Так мы получим новую таску до конца, которая называется проверка статуса таска. Для проверки статуса таски используем модуль async_status.

name: Deploy WebApp
hosts: webhost1
tasks:
   - command: /opt/monitor/checks.py
 	async: 360
 	poll: 0
 	register: webapp_result

   - command: /opt/monitor/db.py
 	async: 360
 	poll: 0
 	register: db_result

   - name: check_status_of_task
 	async_status: jid={{ webapp_result.ansible_job_id }}
 	register: job_result.finished
 	retries: 30

Таким образом, одним из параметров, которые принимает модуль async_status, является идентификатор таска, и мы могли бы получить идентификатор задания для предыдущего таска используя webapp_result, который мы зарегистрировали, и .ansible_job_id.

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

Давайте взглянем на еще один пример асинхронных действий

Нам нужно добавить плей в плейбук, который будет мониторить веб-апку в течении 5 минут, чтобы убедиться что с приложением все в порядке, но мы не хотим держать открытым ssh-соединение все это время. Также у нас есть второй плей на мониторинг базы данных, оба плея будут выполняться параллельно. Результаты выполнения зарегистрируем в переменные:

-
  name: Deploy a Postgres
  hosts: dbhost1
  roles:
	- python
	- postgres

-
  name: Deploy a Web Server
  hosts: webhost1
  roles:
	- python

-
  name: monitor_web_app_for_6_minutes
  hosts: web_server
  command: /opt/monitor/check.py
  async: 360
  poll: 0
  register: webapp_result

-
  name: monitor_db_for_6_minutes
  hosts: dbhost1
  command: /opt/monitor/db_check.py
  async: 360
  poll: 0
  register: database_result

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

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


  1. mcleod095
    09.08.2023 18:28
    +3

    Спасибо за очередной пересказ документации.
    Вот если бы рассказали как сделать async для модуля template или для модуля ufw вот это было бы полезно.


  1. dyadyaSerezha
    09.08.2023 18:28
    +3

    Немного небрежный текст. Используются таск (муж. род), таска (жен. род) и задача для обозначения одного и того же.

    И вот это я не понял:

    Итак, здесь мы передаем идентификатор задания для предыдущего таска

    Что за идентификатор задания, который передаётся?

    Также непонятно, что будет, если задача не завершилась в заданное время при нулевом и не нулевом значении poll.