What are Ansible handlers?

As an Ansible automation expert with years of experience in enterprise deployments, I’ve found that handlers are one of the most powerful yet often misunderstood features in Ansible. In this comprehensive guide, I’ll walk you through everything you need to know about handlers, backed by real-world examples from my experience in production environments.

What Are Ansible Handlers?

Handlers are special tasks in Ansible that only execute when they’re explicitly notified by other tasks. Think of them as event-driven tasks – they wait patiently in the background and spring into action only when needed. This makes them perfect for operations that should only happen when something changes, like restarting a service after a configuration update.

Why Use Handlers?

Let’s look at a common scenario I encountered while managing a fleet of web servers. We had multiple tasks that modified various Apache configuration files, and we needed to restart Apache only if any of these configurations changed. Without handlers, we might have ended up restarting Apache multiple times unnecessarily.

Here’s how we solved it:

- name: Configure production web servers
  hosts: webservers
  tasks:
    - name: Update main Apache configuration
      copy:
        src: files/httpd.conf
        dest: /etc/httpd/conf/httpd.conf
      notify: Restart Apache

    - name: Update SSL configuration
      template:
        src: templates/ssl.conf.j2
        dest: /etc/httpd/conf.d/ssl.conf
      notify: Restart Apache

    - name: Update virtual hosts
      template:
        src: templates/vhosts.conf.j2
        dest: /etc/httpd/conf.d/vhosts.conf
      notify: Restart Apache

  handlers:
    - name: Restart Apache
      service:
        name: httpd
        state: restarted

In this example, Apache restarts only once at the end of the play, even if all three configuration files change. This efficiency is crucial in production environments where unnecessary service restarts can impact users.

Real-world Example: Database Configuration Management

Here’s a more complex real-world example from a recent project where we managed PostgreSQL configurations across different environments:

- name: Configure PostgreSQL servers
  hosts: db_servers
  vars:
    pg_config_path: /var/lib/pgsql/data/postgresql.conf
    pg_hba_path: /var/lib/pgsql/data/pg_hba.conf

  tasks:
    - name: Update PostgreSQL main configuration
      template:
        src: templates/postgresql.conf.j2
        dest: "{{ pg_config_path }}"
        owner: postgres
        group: postgres
        mode: '0600'
      notify: Reload PostgreSQL Config

    - name: Update client authentication configuration
      template:
        src: templates/pg_hba.conf.j2
        dest: "{{ pg_hba_path }}"
        owner: postgres
        group: postgres
        mode: '0600'
      notify: Restart PostgreSQL

  handlers:
    - name: Reload PostgreSQL Config
      systemd:
        name: postgresql
        state: reloaded
      listen: "postgresql configuration change"

    - name: Restart PostgreSQL
      systemd:
        name: postgresql
        state: restarted
      listen: "postgresql configuration change"

Notice how we use different handlers for different types of changes. Configuration updates that don’t require a full restart use the “Reload” handler, while authentication changes trigger a full restart.

Advanced Handler Techniques

1. Using Handler Listen Topics

The listen directive allows multiple handlers to respond to a single notification:

tasks:
  - name: Deploy web application
    copy:
      src: app/
      dest: /var/www/html/
    notify: "web app deployment"

handlers:
  - name: Clear application cache
    command: /usr/bin/clear-cache
    listen: "web app deployment"

  - name: Restart PHP-FPM
    service:
      name: php-fpm
      state: restarted
    listen: "web app deployment"

  - name: Reload Nginx
    service:
      name: nginx
      state: reloaded
    listen: "web app deployment"

2. Conditional Handlers

Sometimes you need handlers to behave differently based on conditions:

handlers:
  - name: Restart web service
    service:
      name: "{{ web_service_name }}"
      state: restarted
    when: ansible_distribution == 'RedHat'

3. Force Handler Execution

There are times when you need handlers to run before the end of the play:

tasks:
  - name: Update application code
    git:
      repo: https://github.com/myapp
      dest: /var/www/myapp
    notify: Restart application

  - name: Force handlers to run
    meta: flush_handlers

  - name: Run database migrations
    command: /var/www/myapp/manage.py migrate

Best Practices and Tips

  1. Meaningful Names: Give your handlers descriptive names that clearly indicate their purpose. Instead of “Restart service”, use “Restart Nginx after config change”.
  2. Idempotency: Ensure your handlers are idempotent – they should be safe to run multiple times.
  3. Granular Control: Use different handlers for different types of service control (reload vs restart).
  4. Error Handling: Include proper error handling in your handlers:
handlers:
  - name: Restart Apache with verification
    block:
      - name: Restart Apache service
        service:
          name: httpd
          state: restarted

      - name: Verify Apache is running
        wait_for:
          port: 80
          timeout: 30
    rescue:
      - name: Roll back configuration
        copy:
          src: /etc/httpd/conf/httpd.conf.backup
          dest: /etc/httpd/conf/httpd.conf

Common Pitfalls to Avoid

  1. Handler Name Conflicts: Each handler name must be unique across your entire playbook, including roles.
  2. Over-notification: Don’t notify handlers unnecessarily – only notify when a change requires action.
  3. Complex Handler Logic: Keep handlers simple – they should do one thing well.

Conclusion

Handlers are a powerful feature in Ansible that, when used correctly, can make your playbooks more efficient and robust. They’re especially valuable in production environments where service restarts and system changes need to be carefully managed.

Remember: the key to effective handler usage is understanding when to use them. Not every task needs a handler – use them when you need to respond to changes in a controlled, efficient manner.

By following these practices and examples, you’ll be well-equipped to implement handlers effectively in your own Ansible automation projects. Happy automating!

Leave a Reply