Ansible’s block module is a powerful feature that allows you to group a set of tasks together. Blocks can be used for error handling, running a series of tasks under certain conditions, or organizing related tasks for better readability.
In this guide, we’ll explore the block module in depth, focusing on real-world practical use cases to illustrate its utility.
Table of Contents
Introduction to Ansible Block Module
The block module in Ansible enables you to group multiple tasks into a single logical block. This grouping can be beneficial in various scenarios, such as:
- Error Handling: Use block with rescue and always, to define actions for failure and cleanup.
- Conditional Execution: Run a group of tasks based on certain conditions.
- Organizational Clarity: Keep related tasks together, improving the playbook’s readability and maintainability.
Basic Structure of a Block
A block can contain multiple tasks, rescue, and always sections:
- block: The main section containing the tasks.
- rescue: Tasks that run if any task in the block fails.
- always: Ensures that tasks run after the block are completes, no matter if it was successful or not.
Syntax and Basic Usage
Here’s a basic example of the block module:
- name: Basic usage of block
hosts: all
tasks:
- block:
- name: Task 1
debug:
msg: "This is task 1"
- name: Task 2
debug:
msg: "This is task 2"
rescue:
- name: Handle failure
debug:
msg: "Something went wrong!"
always:
- name: Always run this
debug:
msg: "This task runs always"
In this example playbook:
- block contains tasks that will be executed together.
- rescue section contains tasks that will be executed if any task within the block fails.
- always section contains tasks that will always be executed, regardless of success or failure.
Example 1: Database Setup
Let’s consider a scenario where you need to set up a database server. This involves installing the database software, configuring it, and ensuring it’s running. If any part of this process fails, you might want to clean up partial installations.
- name: Database setup with block
hosts: database_servers
tasks:
- block:
- name: Install PostgreSQL
apt:
name: postgresql
state: present
- name: Configure PostgreSQL
template:
src: templates/postgresql.conf.j2
dest: /etc/postgresql/13/main/postgresql.conf
- name: Start PostgreSQL service
systemd:
name: postgresql
state: started
enabled: yes
rescue:
- name: Remove partially installed PostgreSQL
apt:
name: postgresql
state: absent
always:
- name: Ensure system is updated
apt:
update_cache: yes
In this example:
- block ensures that PostgreSQL is installed, configured, and started.
- rescue section ensures that if any task fails, PostgreSQL is removed to clean up the system.
- always section updates the package cache.
Example 2: Application Deployment
Consider deploying a web application where you must pull the latest code, install dependencies, and restart the web service.
- name: Web application deployment
hosts: web_servers
tasks:
- block:
- name: Pull latest code from Git
git:
repo: 'https://github.com/example/app.git'
dest: /var/www/app
version: master
- name: Install dependencies
pip:
requirements: /var/www/app/requirements.txt
- name: Restart web service
systemd:
name: my_web_service
state: restarted
rescue:
- name: Roll back to previous release
git:
repo: 'https://github.com/example/app.git'
dest: /var/www/app
version: previous_release
always:
- name: Ensure web service is running
systemd:
name: my_web_service
state: started
enabled: yes
In this example:
- block includes tasks to pull the latest code, install dependencies, and restart the web service.
- rescue section rolls back to the previous release if any task fails.
- always section ensures that the web service is running.
Example 3: System Configuration
Imagine you want to configure NTP service on multiple hosts and ensure they are running. If any configuration fails, you want to revert changes.
The following playbook configures and starts the NTP service on all remote hosts.
- name: System configuration with block
hosts: all
tasks:
- block:
- name: Configure NTP
template:
src: templates/ntp.conf.j2
dest: /etc/ntp.conf
- name: Start NTP service
systemd:
name: ntp
state: started
enabled: yes
- name: Configure Firewall
ufw:
rule: allow
name: 'NTP'
port: 123
proto: udp
rescue:
- name: Revert NTP configuration
command: mv /etc/ntp.conf.bak /etc/ntp.conf
always:
- name: Ensure firewall is enabled
ufw:
state: enabled
In this example:
- block contains tasks to configure NTP, start the NTP service using systemd module, and configure the firewall.
- rescue section reverts the NTP configuration if any task fails.
- always section ensures that the firewall is enabled.
Example 4: Conditional Package Installation Based on OS Type
In a scenario where you manage multiple different Linux servers and want to install packages conditionally based on the operating system type,
Here is an example playbook that uses the when conditionals to install packages based on operating system type.
- name: Install packages conditionally based on OS
hosts: all
tasks:
- block:
- name: Install Apache on Debian-based systems
apt:
name: apache2
state: present
when: ansible_os_family == "Debian"
- name: Install Apache on RedHat-based systems
yum:
name: httpd
state: present
when: ansible_os_family == "RedHat"
- name: Start Apache service
service:
name: "{{ 'apache2' if ansible_os_family == 'Debian' else 'httpd' }}"
state: started
enabled: yes
rescue:
- name: Handle failure in package installation
debug:
msg: "Failed to install Apache. Please check the logs."
always:
- name: Ensure that the package manager cache is up-to-date
shell: "{{ 'apt-get update' if ansible_os_family == 'Debian' else 'yum update -y' }}"
ignore_errors: yes
In this example:
- block installs and starts Apache based on the operating system type.
- rescue section logs an error message if the installation fails.
- always section updates the package manager cache regardless of the outcome.
Example 5: Looping Through Users to Configure SSH Access
Suppose you need to ensure that a list of users has the correct SSH configuration on a set of servers. You can use loops within a block to iterate over each user and apply the necessary configurations.
- name: Configure SSH access for multiple users
hosts: all
vars:
users:
- { name: 'alice', ssh_key: 'alice_public_key' }
- { name: 'bob', ssh_key: 'bob_public_key' }
- { name: 'charlie', ssh_key: 'charlie_public_key' }
tasks:
- block:
- name: Create users
user:
name: "{{ item.name }}"
state: present
loop: "{{ users }}"
- name: Set up SSH key for users
authorized_key:
user: "{{ item.name }}"
key: "{{ lookup('file', item.ssh_key) }}"
state: present
loop: "{{ users }}"
rescue:
- name: Log user creation failure
debug:
msg: "Failed to create or configure SSH for some users."
always:
- name: Ensure SSH service is running
service:
name: ssh
state: started
enabled: yes
In this example:
- block creates user accounts and sets up SSH keys for each user in the list.
- rescue section logs an error if user creation or SSH key setup fails.
- always section ensures the SSH service is running.
Conclusion
Ansible’s block module is the best tool for grouping tasks and applying common error handling and properties. By using blocks, you can make your playbooks more robust and easier to maintain. This guide has provided an overview of the block module and demonstrated its usage with practical examples.
Start optimizing your playbooks today by leveraging tags with the Ansible block module to streamline your tasks and improve efficiency!
FAQs
1. Can I apply error handling to a block of tasks?
Yes, you can use rescue and always clauses with blocks to handle errors and ensure certain tasks run regardless of success or failure.
2. Is it possible to use conditionals with a block?
Yes, you can use the when condition to execute a block only when specified conditions are met.
3. What happens if a task within a block fails?
If a task fails and you have defined a rescue clause, Ansible will execute the tasks in that clause, allowing for recovery or alternative actions.
4. Can I apply tags to a block of tasks?
Yes, you can assign tags to a block, allowing you to run only specific blocks when executing the playbook.