Going to AWS re:Invent 2024?

➡️ Book a meeting with Spacelift

Ansible

How to Use Blocks in Ansible Playbooks

ansible playbook blocks

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:

  1. What are blocks in Ansible?
  2. How to use blocks in Ansible playbooks?
  3. Ansible blocks use case examples

What are blocks in Ansible?

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 as become
  • 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. 

How to use blocks in Ansible playbooks?

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 the block or rescue sections succeeded or failed. It can be used for cleanup or logging purposes. (Optional)

Ansible blocks use case examples

In this section, we will look into use cases for Ansible blocks and how to effectively use them in your playbooks.

Example 1: Group tasks in Ansible blocks

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.

Example 2: Using rescue blocks to handle exceptions

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"

Example 3: Cleanup operations

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

Example 4: Conditional execution with Ansible blocks

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.

Example 5: Nested blocks in Ansible playbooks

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.

How can Spacelift help you with Ansible projects?

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.

Key points

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.

Learn more

The Practitioner’s Guide to Scaling Infrastructure as Code

Transform your IaC management to scale

securely, efficiently, and productively

into the future.

ebook global banner
Share your data and download the guide