Every task in Ansible tells the target system what state it should be in rather than just giving commands to get it there. This distinction makes Ansible’s approach idempotent which means running the same task multiple times won’t have different results.
However, real-world scenarios sometimes lead to unexpected issues. A task might fail due to an unforeseen condition or a temporary issue.
This is where Ansible block and rescue directives come into play. They provide a structured way to anticipate and handle the errors that may occur gracefully.
The essence of this tutorial is to discuss and learn about the Ansible block and rescue features to group and handle the errors in Ansible playbooks.
What Is an Ansible Block?
In Ansible, a block is a grouping of tasks that is executed as a unit. You can think of it as bundling multiple tasks together so they are treated as one logical unit.
There are three main advantages of Ansible blocks:
- Group tasks logically
- Apply the same attributes (like become or tags) to multiple tasks
- Handle the errors for a group of tasks using the “rescue” and “always” sections
Define an Ansible Block
We can define an Ansible block by using the “block” keyword. Inside the block, we can define any number of tasks, each with individual properties and requirements.
The following playbook demonstrates how we can use the Ansible block feature to group the tasks together:
hosts: webservers
tasks:
- block:
- name: Install apache
apt:
name: apache2
state: present
- name: Start apache service
service:
name: apache2
state: started
In this case, we use an Ansible block to install and start the Apache server as a single unit.
Using Ansible Blocks and Rescue
The power of blocks becomes apparent when we combine it with the “rescue” and “always” clauses.
These allow us to specify the tasks that should run if there’s an error within the block and the tasks that should run regardless of success or failure.
We can express the syntax of these blocks as demonstrated in the following:
Here’s a breakdown:
- Block – The block contains the main tasks that we wish to execute.
- Rescue – Inside the rescue block, we define the tasks that will be performed if any of the tasks within the block fails.
- Always – Finally, we have the “always” block. This defines a set of tasks that should run after the block and rescue options, irrespective of their outcomes.
To better illustrate how these blocks work, take the previous example that installs and starts the Apache service.
hosts: webservers
tasks:
- block:
- name: Install apache
apt:
name: apache2
state: present
- name: Start apache service
service:
name: apache2
state: started
rescue:
- name: Send failure notification
mail:
to: admin@example.com
subject: "Apache setup failed!"
body: "Error setting up Apache on {{ ansible_hostname }}."
always:
- name: Write log for playbook run
local_action:
module: copy
content: "Playbook run at {{ ansible_date_time.iso8601 }}"
dest: "/var/log/ansible_run.log"
The previous example playbook introduces the basic error handling when installing and starting the Apache server. If either the installation or service start fails, we send an email notification to the admin with the error message.
Similarly, we also write the log of the playbook to a file, whether we encountered an error or not, as defined by the “always” section.
Conclusion
You now learned how to configure and set up the error-handling techniques in your Ansible playbooks by leveraging the power of block, rescue, and always directives. Using these features, you can tackle the unexpected with grace.