Ansible has become one of the most popular tools for managing cloud and on-premises infrastructure. With Ansible, IT professionals can automate processes and perform configuration management tasks to ensure consistency and best practices in their systems.
This blog explores Ansible blocks, a way to group tasks in Ansible playbooks.
What we will cover:
Ansible blocks are a way to logically group and split tasks in Ansible playbooks. By grouping multiple tasks together using blocks, you can apply common attributes, error handling, or conditional statements and can set data or directives to various tasks simultaneously.
When to use Ansible blocks:
- Organization – Blocks help with playbook organization and ensure code duplication and standard behavior across tasks.
- Conditional execution – Attributes or directives set at the Ansible block level aren’t applied directly to the block itself but are inherited by and applied to each task in the block. Examples include conditionals such as
when
, block variables, or privilege escalation such asbecome
. - Debugging – Blocks also help with error handling with the
rescue
keyword. You can define a rescue section to run specific tasks when an error occurs within the block. - Error recovery – Another handy use case is cleanup situations using the
always
keyword to execute generic tasks regardless of what happens inside the main block of tasks.
Here’s an example of how to define an Ansible block and apply some common attributes to the block’s tasks:
tasks:
- name: The code below defines an example Ansible Block
block:
- name: Install Apache
ansible.builtin.yum:
name: httpd
state: present
- name: Start Apache service
ansible.builtin.service:
name: httpd
state: started
rescue:
- name: Task to run if there is an error
ansible.builtin.command: echo "Error occurred"
always:
- name: Task that always runs
ansible.builtin.command: echo "This always runs"
- block: Contains a list of tasks that are grouped together. All tasks inside the block will be executed in sequence.
- rescue: If any task within the block fails, the tasks inside rescue will be executed to handle the failure. (Optional)
- always: Tasks inside
always
are executed no matter whether theblock
orrescue
sections succeeded or failed. It can be used for cleanup or logging purposes. (Optional)
In this section, we will look into use cases for Ansible blocks and how to effectively use them in your playbooks.
Ansible block’s main use case is better for organizing multiple tasks in a common set.
Grouping similar tasks with blocks becomes especially useful when applying common logic to many tasks.
tasks:
- block:
- name: Install Apache
ansible.builtin.yum:
name: “{{ apache_version }}”
state: present
- name: Start and enable Apache service
ansible.builtin.service:
name: httpd
state: started
enabled: true
when: ansible_os_family == "RedHat"
become: true
vars:
apache_version: httpd-2.4.6-6.fc20
In this example, we group all the tasks that install Apache under a common block. We apply root privileges to every task using become
and execute each task conditionally only if the ansible_os_family
value is RedHat
.
The variable apache_version
is only available to the tasks of this block and cannot be reached by other tasks in the same Ansible play.
In Ansible, blocks allow you to handle errors or failures in tasks using the rescue
keyword, which is similar to try/catch
blocks in other programming languages. When a task within a block fails, the rescue
section enables you to handle the failure gracefully, perform alternative actions, or recover from the failure, such as by retrying the task or executing a different fallback procedure.
Error handling with blocks is useful when you want to define alternative behaviors when an error occurs, allowing your playbook to handle exceptions without immediately terminating execution.
Let’s check an example of handling exceptions with rescue
:
- block:
- name: Install Nginx
ansible.builtin.yum:
name: nginx
state: present
- name: Start and enable Nginx service
ansible.builtin.service:
name: nginx
state: started
enabled: true
rescue: # These tasks run only if there is a failure on the block
- name: Show which task failed
ansible.builtin.debug:
msg: "{{ ansible_failed_task }}"
You can get information on the failed task with the ansible_failed_task
variable, as shown above. Common examples of rescue blocks in practice include logging, triggering alerts, restoring backups, or retry mechanisms.
When a task on the block fails, and the rescue
code is executed, the overall block’s result is success
. To force a failure of the overall block, use the fail
module:
- block:
- name: Task 1
ansible.builtin.debug:
msg: "A task"
- name: Task 2
ansible.builtin.debug:
cmd: "A second task"
rescue:
- name: A rescue task
ansible.builtin.debug:
msg: "Rescued"
- name: Fail the block when rescued
ansible.builtin.fail:
msg: "A task failed, so failing the whole block"
Blocks make it easy to specify cleanup actions using the always
keyword, ensuring that certain tasks are executed regardless of the previous tasks’ success or failure. Leveraging always
can help release resources, reset configurations, or perform housekeeping tasks.
- block:
- name: Install Nginx
ansible.builtin.yum:
name: nginx
state: present
- name: Start and enable Nginx service
ansible.builtin.service:
name: nginx
state: started
enabled: true
always:
- name: Always remove temp directory
ansible.builtin.file:
path: /tmp/to_delete
state: absent
You can apply conditions to entire blocks, making them useful when a common condition dictates whether or not several related tasks need to be executed. A typical example of conditional block execution is executing specific blocks of tasks for different operating systems:
```
- block:
- name: Install Nginx Web Server
ansible.builtin.apt
name: nginx
state: present
update_cache: true
- name: Start PostgreSQL service
ansible.builtin.service:
name: postgresql
state: started
when: ansible_os_family == "Debian"
become: true
Here, the entire block is executed only if the system is running a Debian-based OS.
You can use many blocks in plays and nest them inside each other to handle more complex workflows. You can opt for nested blocks to apply specific attributes to different levels of the hierarchy in an Ansible play.
- block:
- block: nested block 1
- name: Install Nginx
ansible.builtin.apt:
update_cache: true
name: nginx
state: present
- name: Configure Nginx
ansible.builtin.copy:
dest: "/var/www/html/index.html"
content: |
<!DOCTYPE html>
<html>
<head><title>Server Details</title></head>
<body>
<h1>Served from {{ ansible_hostname }}</h1>
</body>
</html>
mode: '0644'
rescue:
- name: Handle error during Nginx installation/configuration
ansible.builtin.debug:
msg: "Error during package setup"
- block:
- name: Start the service
ansible.builtin.service:
name: nginx
state: started
enabled: true
rescue:
- name: Handle service start failure
debug:
msg: "Failed to start service"`
In the above example, we have two separate blocks of tasks for handling separate flows—one block and its respective rescue for the package installation phase and a separate block with its rescue code for starting the service.
Although nested blocks can be handy, they usually make plays more complex and challenging to read, so use them carefully.
Spacelift’s vibrant ecosystem and excellent GitOps flow can greatly assist you in managing and orchestrating Ansible. By introducing Spacelift on top of Ansible, you can then easily create custom workflows based on pull requests and apply any necessary compliance checks for your organization.
Another great advantage of using Spacelift is that you can manage different infrastructure tools like Ansible, Terraform, Pulumi, AWS CloudFormation, and even Kubernetes from the same place and combine their Stacks with building workflows across tools.
Our latest Ansible enhancements solve three of the biggest challenges engineers face when they are using Ansible:
- Having a centralized place in which you can run your playbooks
- Combining IaC with configuration management to create a single workflow
- Getting insights into what ran and where
Provisioning, configuring, governing, and even orchestrating your containers can be performed with a single workflow, separating the elements into smaller chunks to identify issues more easily.
Would you like to see this in action – or just want a tl;dr? Check out this video I put together showing you Spacelift’s new Ansible functionality:
If you want to learn more about using Spacelift with Ansible, check our documentation, read our Ansible guide, or book a demo with one of our engineers.
In this blog post, we explored Ansible blocks and how you can effectively use them in your playbooks. We also explored various use cases and saw examples of Ansible blocks in action, such as grouping, error handling tasks, block nesting, and conditional execution.
Manage Ansible Better with Spacelift
Managing large-scale playbook execution is hard. Spacelift enables you to automate Ansible playbook execution with visibility and control over resources, and seamlessly link provisioning and configuration workflows.