====== Асинхронность в 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
==== Пример с until ====
---
- name: user
hosts: demo
any_errors_fatal: true
tasks:
- name: PreConfig block
block:
- name: sleep
command: /bin/sleep 15
async: 1000
poll: 0
register: sleep
- debug:
var: sleep
- name: Echo
command: echo "Done"
become: true
- name: Check sleep status
async_status:
jid: {{ sleep.ansible_job_id }}
register: job_result
until: job_result.finished
retries: 100
delay: 1
become: true