Everything about Ansible Handlers

Everything about Ansible Handlers

Subscribe to our newsletter and never miss any upcoming articles

Listen to this article

It's an important concept as we learn more about Ansible playbooks and march towards Ansible roles. Ansible handlers are very useful in the scenarios where we need to run some task when a change takes place on the remote node.

For example we want to restart or reload a service immediately after a change has been made to its configuration file.

Handlers must have a unique name globally. The tasks which requires to run handler contains notify directive. If nothing notifies a handler, it will not run.

Regardless of how many tasks notifies a handler, it will only run once, after all of the tasks complete in a particular play.

Handlers are mostly used when we need to restart services or trigger reboots.

Implementing Handlers in a Playbook

Here is my playbook. This will go to our worker nodes install epel repository on them and then install the nginx web server.

Once the nginx package is installed it will notify the handler task and handler task (restarting nginx service) will then start execution.

---
 - name: Handlers implementation example-1
   hosts: workers
   tasks:
     - name: Enable EPEL Repository on CentOS 7
       yum:
         name: https://dl.fedoraproject.org/pub/epel/epel-release-latest-{{ ansible_distribution_major_version }}.noarch.rpm
         state: present
       become: True

     - name: Import EPEL GPG key.
       rpm_key:
         key: /etc/pki/rpm-gpg/RPM-GPG-KEY-EPEL-{{ ansible_distribution_major_version }}
         state: present
       become: True

     - name: Install nginx latest version
       yum:
         name: nginx
         state: present
       become: true
       notify: restart_nginx

   handlers:
     - name: restart_nginx
       become: true
       service:
         name: nginx
         state: restarted

Let's execute our playbook.

handler_tags $ ansible-playbook handler-1.yml -i myinventory -kK
SSH password:
BECOME password[defaults to SSH password]:

PLAY [Handlers implementation example-1] ***********************************************************************

TASK [Gathering Facts] *****************************************************************************************
ok: [worker1]
ok: [worker2]

TASK [Enable EPEL Repository on CentOS 7] **********************************************************************
changed: [worker1]
changed: [worker2]

TASK [Import EPEL GPG key.] ************************************************************************************
changed: [worker1]
changed: [worker2]

TASK [Install nginx latest version] ****************************************************************************
changed: [worker1]
changed: [worker2]

RUNNING HANDLER [restart_nginx] ********************************************************************************
changed: [worker1]
changed: [worker2]

PLAY RECAP *****************************************************************************************************
worker1                    : ok=5    changed=3    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
worker2                    : ok=5    changed=3    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

By looking at the above output you can clearly see that once package installation was completed successfully Ansible triggered the handler task to run.

handlers keyword in the playbook should be at the same level as "tasks".

Here you have learned "How to enable EPEL repository on RHEL/CentOS through Ansible" as well.

Grouping of multiple handlers

Multiple handlers can be grouped as well. Handlers “listen” to generic topics, and tasks can notify those topics.

For example:

---
- name: Handlers grouping example
  hosts: workers

  tasks:
    - name: restart everything
      command: echo "this task will restart web and ftp services"
      notify: "restart web and ftp services"
      become: true

  handlers:
    - name: restart vsftpd
      service: name=vsftpd state=restarted
      listen: "restart web and ftp services"
      become: true
    - name: restart nginx
      service: name=nginx state=restarted
      listen: "restart web and ftp services"
      become: true

listen directive makes it easier for us to trigger multiple handlers. It also decouples handlers from their names, making it easier to share handlers among playbooks and roles.

Controlling handler run

As designed all the handlers run once and after all the tasks in a particular play have been completed regardless of how many tasks notify it.

By using meta module the handlers can be run before the end of the play. If you want to force the handler to run in between the two tasks instead of at the end of the play, you need to put this between the two tasks

Here is example playbook:

     - name: Flush handlers
       meta: flush_handlers

The meta: flush_handlers task triggers any handlers that have been notified at that point in the play.

Using variables with handlers

Using variables within handlers is also possible and important when we have different OS distributions and different service names, and you want your output to show the exact name of the restarted service for each target machine. We should avoid placing variables in the name of the handler.

handlers:
# This handler name may cause your play to fail!
- name: Restart "{{ web_service_name }}"

If the variable used in the handler name is not available, the entire play fails.

We should instead place variables in the task parameters of your handler.

---
- name: Handlers grouping example
  hosts: workers
  vars:
    ftp_service_name: vsftpd
    web_service_name: nginx
  tasks:
    - name: restart everything
      command: echo "this task will restart web and ftp services"
      notify: "restart web and ftp services"
      become: true

  handlers:
    - name: restart vsftpd
      service: name="{{ ftp_service_name | default('vsftpd')}}" state=restarted
      listen: "restart web and ftp services"
      become: true
    - name: restart nginx
      service: name="{{ web_service_name | default('httpd')}}" state=restarted
      listen: "restart web and ftp services"
      become: true

In the above playbook we are using variables as part of handler task which can be either declared in a separate file or within the playbook itself.

You can provide default values for variables directly in your templates using the Jinja2 ‘default’ filter. This is often a better approach than failing if a variable is not defined.

That's all for this article. I am sure you will have a very good idea now about Ansible handlers and their use cases.

Hope you like the article. Stay Tuned for more.

Thank you. Happy learning!

 
Share this