Group Multiple Tasks Using Ansible Block Module

Ansible block Module Example

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.

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:

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.

About Hitesh Jethva

I am Hitesh Jethva, Founder and Author at Code2DevOps.com. With over 15 years of experience in DevOps and open source technologies, I am passionate about empowering teams through automation, continuous integration, and scalable solutions.

View all posts by Hitesh Jethva