Ansible Block And Handlers

Last updated: June 30, 2025

My Journey with Blocks and Handlers in Ansible

When I first started writing Ansible playbooks, I created simple, sequential tasks that ran one after another. As my infrastructure grew more complex, I found myself repeating error-handling patterns and creating redundant notification systems for service restarts. That's when I discovered the power of Ansible blocks and handlers.

Learning to use blocks transformed how I structure my playbooks. Instead of scattering error handling across tasks, I could group related tasks and handle exceptions in a cleaner, more Python-like way. And with handlers, I could eliminate redundant service restarts, ensuring that services only restarted once, no matter how many configuration changes were made.

In this blog post, I'll share my experiences with Ansible blocks and handlers, providing practical examples for both Linux and Windows environments. These features have significantly improved the reliability and efficiency of my automation code, and I believe they can do the same for yours.

Understanding Blocks in Ansible

Blocks in Ansible allow you to group related tasks together and apply common parameters to them. They're especially useful for error handling and applying conditional logic to multiple tasks at once.

Think of blocks as similar to the try/except/finally structure in Python:

  • block section = try section (main tasks to execute)

  • rescue section = except section (tasks to run if the block fails)

  • always section = finally section (tasks to run regardless of success or failure)

Here's a comparison table to visualize the relationship:

Python
Ansible Block

try

block

except

rescue

finally

always

Basic Block Usage

Let's start with a simple example. Here's how you can group related tasks in a block:

In this example, all tasks within the block share common variables that determine the appropriate package, configuration path, and service name based on the operating system.

Blocks with Conditionals

One of the most powerful features of blocks is the ability to apply conditionals to an entire group of tasks:

This playbook applies different configurations based on whether the target system is running Linux or Windows, using blocks to group the OS-specific tasks.

Error Handling with Blocks

Blocks really shine when it comes to error handling. Here's an example that demonstrates how to use the rescue and always sections:

In this example:

  1. The block section attempts to stop the database, create a backup, and restart the database.

  2. If any task in the block fails, the rescue section ensures the database is running again and sends a notification.

  3. The always section runs regardless of success or failure, cleaning up temporary files and logging the attempt.

Windows Error Handling Example

Here's a similar example for Windows environments:

Accessing Error Information

When using the rescue section, you can access information about the failed task using special variables:

Understanding Handlers in Ansible

Handlers are special tasks that only run when notified by other tasks. They are particularly useful for operations that should only occur once, regardless of how many tasks might trigger them, such as restarting a service after multiple configuration changes.

Handlers have two key characteristics:

  1. They only run when explicitly notified by a task using the notify directive

  2. They run only once at the end of a play, even if notified multiple times

Basic Handler Usage

Here's a simple example showing how handlers work:

In this playbook:

  1. We have two tasks that modify Apache configuration files

  2. Both tasks notify the "Restart Apache" handler if they make changes

  3. The handler will only run once at the end of the play, even though it might be notified twice

Windows Handlers Example

Handlers work the same way on Windows:

Notifying Multiple Handlers

A single task can notify multiple handlers:

Handlers with Listen Directive

The listen directive provides a more flexible way to organize handlers. Multiple handlers can listen to the same notification topic:

By using the listen directive, you can notify multiple handlers with a single topic name, making your playbooks more maintainable and decoupled.

Controlling When Handlers Run

By default, handlers run at the end of a play. However, sometimes you might want handlers to run earlier. You can use the meta: flush_handlers task to force notified handlers to run immediately:

In this example, we want to ensure that the schema version log is updated before we start the application servers, so we use meta: flush_handlers to run any notified handlers immediately.

Combining Blocks and Handlers

Blocks and handlers can be used together to create robust playbooks with elegant error handling:

Sequence Diagram: Blocks and Handlers Workflow

Here's a sequence diagram illustrating how blocks and handlers work in Ansible:

spinner

This diagram shows how:

  1. Tasks are executed sequentially within a block

  2. If a task fails, execution jumps to the rescue section

  3. The always section runs regardless of success or failure

  4. Handlers are queued when notified but only run at the end of the play

Best Practices for Blocks and Handlers

Based on my experience, here are some best practices for using blocks and handlers effectively:

For Blocks:

  1. Group related tasks: Use blocks to group tasks that are logically related or share common properties (like conditionals or privilege escalation).

  2. Plan error handling carefully: Design your rescue sections to properly handle failures and maintain system stability.

  3. Use meaningful names: Give your blocks descriptive names to improve readability.

  4. Avoid deep nesting: While blocks can be nested, deep nesting makes playbooks harder to read and maintain.

For Handlers:

  1. Use unique names: Ensure handler names are unique across your playbook and included roles.

  2. Idempotent operations: Make sure handlers are idempotent (can run multiple times without negative effects).

  3. Consider using 'listen': For complex playbooks, the listen directive can make your code more maintainable.

  4. Be cautious with variables in handler names: Variables in handler names can cause issues if the values change mid-play.

Common Pitfalls and Solutions

Mistake: Using variables in handler names

Solution: Place variables in the task parameters, not in the handler name:

Mistake: Not handling errors appropriately in blocks

Solution: Always include proper error handling:

Conclusion

Blocks and handlers are powerful features in Ansible that significantly improve how you organize and structure your playbooks. Blocks provide clean error handling and task grouping, while handlers ensure that operations like service restarts happen at the right time and only when necessary.

In my automation journey, these features have been invaluable for creating more robust and maintainable playbooks. They've helped me reduce code duplication, handle errors gracefully, and ensure that services are only restarted when needed.

As you build your own automation, I encourage you to incorporate blocks and handlers into your playbooks. Start with simple implementations and gradually explore more complex patterns as you become comfortable with the concepts.

Last updated