Ansible Async Actions
Introduction
When running Ansible playbooks, you might encounter tasks that take a long time to complete - like installing large packages, running system updates, or waiting for services to start. By default, Ansible executes tasks synchronously, meaning it waits for each task to complete before moving on to the next one. This can lead to inefficient playbook execution and potential SSH timeout issues with long-running operations.
Ansible's Async Actions feature solves this problem by allowing tasks to run in the background while your playbook continues with other tasks. This is particularly useful for:
- Preventing SSH timeouts with long-running tasks
- Running multiple tasks in parallel across different hosts
- Starting a process and checking its status later
- Improving overall playbook execution efficiency
In this guide, we'll explore how to implement asynchronous task execution in Ansible and demonstrate practical use cases for this powerful feature.
Understanding Ansible's Default Execution
Before diving into async actions, let's understand how Ansible executes tasks by default:
By default, Ansible runs each task sequentially across all hosts. If a task takes a long time on one host, all other executions have to wait, which may cause SSH timeouts or significantly slow down your playbook execution.
Async Action Basics
To run a task asynchronously, you need to specify two parameters:
async
: Maximum runtime in seconds (how long the task can run)poll
: How frequently Ansible should check back on the task (in seconds)
Simple Async Task Example
- name: Update system packages in the background
ansible.builtin.apt:
update_cache: yes
upgrade: dist
async: 3600 # Allow the task to run for up to 1 hour
poll: 0 # Don't wait for the task to finish (fire and forget)
The poll: 0
setting means Ansible won't wait for the task to complete - it will start the task and immediately move on.
Checking Status of Async Tasks
When you run a task with poll: 0
, Ansible assigns a job ID that you can use to check the status later:
- name: Start a long-running operation
ansible.builtin.command: /usr/bin/long_running_operation
async: 3600
poll: 0
register: async_result
- name: Check on long-running operation
ansible.builtin.async_status:
jid: "{{ async_result.ansible_job_id }}"
register: job_result
until: job_result.finished
retries: 30
delay: 10
In this example:
- We start a task asynchronously and register its result
- We use the
async_status
module to periodically check if the task has finished - The
until
loop continues checking until the task completes or reaches the maximum number of retries
Practical Examples
Let's look at some practical use cases for async actions:
Example 1: System Updates Across Multiple Servers
---
- name: Update all web servers simultaneously
hosts: webservers
tasks:
- name: Update apt cache and upgrade packages
ansible.builtin.apt:
update_cache: yes
upgrade: dist
async: 1800 # 30 minutes
poll: 0
register: update_result
# Do other tasks while updates run in background
- name: Ensure critical services are running
ansible.builtin.service:
name: "{{ item }}"
state: started
loop:
- nginx
- redis
- memcached
# Check on the status of the updates
- name: Wait for system updates to complete
ansible.builtin.async_status:
jid: "{{ update_result.ansible_job_id }}"
register: job_result
until: job_result.finished
retries: 60
delay: 30
This playbook begins system updates on all web servers simultaneously, performs other tasks while updates run in the background, and then checks back on the update status.
Example 2: Waiting for Service Readiness
- name: Restart database server
ansible.builtin.service:
name: postgresql
state: restarted
async: 300
poll: 0
register: restart_job
- name: Wait for database to become available
ansible.builtin.async_status:
jid: "{{ restart_job.ansible_job_id }}"
register: job_result
until: job_result.finished
retries: 30
delay: 10
- name: Verify database connectivity
ansible.builtin.command: psql -c "SELECT 1"
register: db_check
until: db_check.rc == 0
retries: 12
delay: 5
This example restarts a database service asynchronously, then waits for it to complete before verifying connectivity.
Parallel Task Execution
One of the most powerful applications of async is running multiple tasks in parallel:
- name: Start multiple operations in parallel
hosts: webservers
tasks:
- name: Backup database
ansible.builtin.command: /usr/local/bin/backup_database.sh
async: 3600
poll: 0
register: backup_job
- name: Run security updates
ansible.builtin.command: /usr/local/bin/security_updates.sh
async: 3600
poll: 0
register: security_job
- name: Generate reports
ansible.builtin.command: /usr/local/bin/generate_reports.sh
async: 3600
poll: 0
register: reports_job
- name: Wait for all tasks to complete
ansible.builtin.async_status:
jid: "{{ item.ansible_job_id }}"
register: job_results
until: job_results.finished
retries: 60
delay: 30
loop:
- "{{ backup_job }}"
- "{{ security_job }}"
- "{{ reports_job }}"
This playbook runs three operations in parallel and then waits for all of them to complete.
Common Use Cases for Async Actions
- Long-running system operations: Package installations, system updates, backups
- Service restarts: Starting services that take time to initialize
- Batch processing: Running multiple independent operations in parallel
- Preventing SSH timeouts: For tasks that exceed your SSH connection timeout
- Resource-intensive operations: CPU or I/O intensive tasks that you want to run in parallel
Best Practices and Limitations
Best Practices
- Set appropriate async timeout values: Set the
async
value higher than you expect the task to take - Keep track of job IDs: Always
register
async tasks if you need to check their status later - Use reasonable polling intervals: Don't poll too frequently to avoid overloading the control node
- Consider failure scenarios: What happens if an async task fails? Plan for proper error handling
Limitations
- Not all modules support async: Some Ansible modules may not work correctly with async
- No synchronization between hosts: Async tasks run independently on each host
- Potential resource constraints: Running too many tasks in parallel might overload your systems
- Task dependencies: If tasks depend on each other, you must explicitly manage the dependencies
Example: Comprehensive Server Setup with Async
Here's a more complex example combining multiple async patterns:
---
- name: Setup web application servers
hosts: app_servers
tasks:
# Start long-running tasks in parallel
- name: Update system packages
ansible.builtin.yum:
name: '*'
state: latest
async: 1800
poll: 0
register: update_task
- name: Pull large application files
ansible.builtin.git:
repo: https://github.com/company/large-app.git
dest: /var/www/application
async: 600
poll: 0
register: git_task
# Do quick tasks while long tasks run
- name: Configure firewall
ansible.builtin.firewalld:
port: "{{ item }}/tcp"
permanent: yes
state: enabled
loop:
- 80
- 443
- 8080
- name: Create application user
ansible.builtin.user:
name: app_user
shell: /bin/bash
home: /home/app_user
create_home: yes
# Wait for long-running tasks to complete
- name: Wait for system updates
ansible.builtin.async_status:
jid: "{{ update_task.ansible_job_id }}"
register: job_result
until: job_result.finished
retries: 90
delay: 20
- name: Wait for git repository
ansible.builtin.async_status:
jid: "{{ git_task.ansible_job_id }}"
register: job_result
until: job_result.finished
retries: 30
delay: 10
# Run dependent tasks after async tasks complete
- name: Start application service
ansible.builtin.systemd:
name: application
state: started
enabled: yes
This playbook demonstrates how to optimize server setup by running long tasks in the background while performing other quick configuration tasks, then waiting for all tasks to complete before starting dependent services.
Summary
Ansible's async actions feature provides a powerful way to handle long-running operations and improve playbook efficiency. By running tasks in the background, you can:
- Prevent SSH timeout issues
- Run multiple tasks in parallel
- Improve overall playbook execution time
- Handle long-running operations more gracefully
When implementing async actions, remember to:
- Set appropriate async timeout values
- Register async tasks if you need to check their status later
- Consider dependencies between tasks
- Test thoroughly to ensure proper execution
Additional Resources
Exercises
- Modify an existing playbook to run system updates asynchronously across all hosts.
- Create a playbook that performs three different operations in parallel and waits for all to complete.
- Implement error handling for an async task that might fail (hint: check the
failed
property in the async_status result). - Write a playbook that restarts a service and polls until it's responsive again.
- Compare the execution time of a playbook with and without async tasks to measure the performance improvement.
If you spot any mistakes on this website, please let me know at feedback@compilenrun.com. I’d greatly appreciate your feedback! :)