Asynchrony in Ansible

We may simply not want the SSH connection to remain active throughout the operation, or we may want to run multiple processes at the same time and check them later. Or another use case, perhaps to start one or more processes without even bothering to check their status. All this can be achieved with asynchronous actions.

Let’s say that as part of our web application deployment, there is a script that monitors and performs some checks, say healthchecks on the web server. He is on the way opt/monitor/checks.py. The scripts perform various checks, such as a stability check, to ensure that the web server stays online for at least five minutes without any problems. So obviously it takes at least five minutes for this script to finish executing.

We don’t want Ansible to maintain an SSH session until the script has finished executing. We want to tell Ansible that this is an asynchronous task (task), so we need to run it and check it, say, every minute. We use the directive asyncto specify the maximum time we expect the task to run. In our case, let’s say six minutes, given the other checks that need to be done.

Directive async tells Ansible that this is an asynchronous task, so just let it run and check later.

And how often does Ansible check the status of a script?

By default, Ansible checks every 10 seconds. If we want to change this, we can use the poll directive. Checking every 10 seconds is too often for us, and we don’t want to wait five minutes because what if the script fails after the first minute? We also don’t want to spend the remaining four minutes waiting for Ansible to check the status. In this connection, checking every minute seems like a good idea.

Set the poll value to 60. Let’s add another task to monitor, for example, databases and give it six minutes to run and poll every 60 seconds.

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

What’s going on here? When Ansible starts executing, it executes the first task first, and will still wait for that task to complete.

Async doesn’t mean Ansible will run it and move forward. It will monitor the state of the script and wait until the specified time has elapsed. In this case 360 ​​seconds.

So in this case, it will run the first check, then wait about six minutes for it to complete, then run the second script to check the database, and wait another six minutes for it to complete.

If we ran them in parallel, we could save a lot of time.

So we need to run the first task, and have Ansible immediately jump to the second task and start the second check, and then wait for both tasks to complete at the end. This can be done by setting the poll value to zero.

By setting the poll value to zero, we are asking Ansible not to wait, but to go straight to the next task.

In this case, Ansible will move on to the next task. However, immediately after the launch of the second task, it will complete its work. We didn’t do anything to make Ansible wait for the task to complete at the end.

To do this, you first need to register the result of the tasks in a variable. So, in this case, we are logging the result of the first task that tracks the web application into a variable named webapp_result. We also register the result of the second task, which is to monitor the database, into a variable 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

So we will get a new task to the end, which is called task status check. To check the status of a task, use the module 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

Thus, one of the parameters that the module accepts async_statusis the task ID, and we could get the task ID for the previous task using webapp_resultwhich we have registered, and .ansible_job_id.

So here we are passing in the job id for the previous task and then waiting for that task to complete. However, it is worth remembering that not all modules support asynchrony.

Let’s take a look at another example of asynchronous actions.

We need to add a playbook to the playbook that will monitor the web app for 5 minutes to make sure the app is ok, but we don’t want to keep the ssh connection open all the time. We also have a second database monitoring play, both plays will run in parallel. We will register the execution results in variables:

-
  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

In today’s world of software development, the need for fast and automated deployment is becoming increasingly important. However, the use of tools such as Kubernetes is not always available or justified. Ansible, on the other hand, is a flexible and powerful tool that can be used in any environment. I invite you to free webinar, in which my colleagues will share with you their knowledge and experience in using Ansible for deploying applications. You’ll learn how to set up an automated deployment process with Ansible and Docker, bypassing the complexity and need for Kubernetes.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *