This is in continuation to the articles of the series “Learning Ansible”. In the previous article Getting started with Ansible – Part 9 we have seen how we can set up the local NTP configuration file and how we can deploy it to the servers to get the benefit of setting up the configuration at one place and not doing this again and again in case we have 100s of servers.
But If we look closely at the playbook that we wrote we can see that if we again run the same playbook then the NTP service will again get restarted, and this should not happen as we have already deployed the NTP config file and if we have not done any change in that config then the NTP service should not get restarted.
So, we should devise a way such that NTP service will only get restarted if we have made some change in NTP configuration files. To resolve this, we can make use of handlers in Ansible. Thus, in this article we will be covering Handlers in Ansible.

Handlers in Ansible

Handlers are just like regular tasks in an Ansible playbook (see Tasks) but are only run if the Task contains a “notify” directive and also indicates that it changed something. For example, if a config file is changed then the task referencing the config file templating operation may notify a service restart handler.
Handlers are the same as tasks, but they get executed only when they are notified but tasks get executed every time we run the playbook. Handlers should be in the same line as that of tasks inside the playbook.
So below is the updated version of the playbook that we wrote in the previous article Getting started with Ansible – Part 9.
  1. ---
  2. - name: Deploying NTP Service
  3. hosts: all
  4. become: yes
  5. tasks:
  6. - name: Install packages on RedHat OS
  7. yum:
  8. name: "{{item}}"
  9. state: present
  10. loop:
  11. - ntp
  12. - unzip
  13. - git
  14. - wget
  15. - zip
  16. when: ansible_os_family == "RedHat"
  17. - name: Install packages on Debian OS
  18. apt:
  19. name: "{{item}}"
  20. state: present
  21. loop:
  22. - ntp
  23. - unzip
  24. - git
  25. - wget
  26. - zip
  27. when: ansible_os_family == "Debian"
  28. - name: Start and Enable NTP service in RedHat OS
  29. service:
  30. name: ntpd
  31. state: started
  32. enabled: yes
  33. when: ansible_os_family == "RedHat"
  34. - name: Start and Enable NTP service in Debian OS
  35. service:
  36. name: ntp
  37. state: started
  38. enabled: yes
  39. when: ansible_os_family == "Debian"
  40. - name: Deploy the NTP configuration file for Debian OS
  41. copy:
  42. src: files/ntp_debian.conf
  43. dest: /etc/ntp.conf
  44. backup: yes
  45. when: ansible_os_family == "Debian"
  46. notify:
  47. - Restart NTP service in Debian OS
  48. - name: Deploy the NTP configuration file for RedHat OS
  49. copy:
  50. src: files/ntp_redhat.conf
  51. dest: /etc/ntp.conf
  52. backup: yes
  53. when: ansible_os_family == "RedHat"
  54. notify:
  55. - Restart NTP service in RedHat OS
  56. handlers:
  57. - name: Restart NTP service in RedHat OS
  58. service:
  59. name: ntpd
  60. state: restarted
  61. when: ansible_os_family == "RedHat"
  62. - name: Restart NTP service in Debian OS
  63. service:
  64. name: ntp
  65. state: restarted
  66. when: ansible_os_family == "Debian"
We can syntax check our playbook with the below command.
ansible-playbook ntp_playbook.yml --syntax-check
and then can run the playbook with the below command.
ansible-playbook ntp_playbook.yml
OUTPUT
Ansible
Here we can see that as we have not changed anything inside the RedHat and Debian NTP configuration files, our handlers didn’t execute.
Now let’s try to add some dummy values inside the RedHat and Debian NTP configuration files and see if our handlers get executed or not.
Ansible
Now ansible knows that the source NTP configuration files and the destination NTP configuration files are different so will execute the tasks and will notify the handlers.
I am using the debug module to print the output for one of the tasks that deploy NTP configuration files for suppose RedHat OS based servers.
I have registered redhat_deployment_output variables that will store the JSON output of our task “Deploy the NTP configuration file for RedHat OS” and will get it printed using the debug module.
Below is the updated playbook.
  1. ---
  2. - name: Deploying NTP Service
  3. hosts: all
  4. become: yes
  5. tasks:
  6. - name: Install packages on RedHat OS
  7. yum:
  8. name: "{{item}}"
  9. state: present
  10. loop:
  11. - ntp
  12. - unzip
  13. - git
  14. - wget
  15. - zip
  16. when: ansible_os_family == "RedHat"
  17. - name: Install packages on Debian OS
  18. apt:
  19. name: "{{item}}"
  20. state: present
  21. loop:
  22. - ntp
  23. - unzip
  24. - git
  25. - wget
  26. - zip
  27. when: ansible_os_family == "Debian"
  28. - name: Start and Enable NTP service in RedHat OS
  29. service:
  30. name: ntpd
  31. state: started
  32. enabled: yes
  33. when: ansible_os_family == "RedHat"
  34. - name: Start and Enable NTP service in Debian OS
  35. service:
  36. name: ntp
  37. state: started
  38. enabled: yes
  39. when: ansible_os_family == "Debian"
  40. - name: Deploy the NTP configuration file for Debian OS
  41. copy:
  42. src: files/ntp_debian.conf
  43. dest: /etc/ntp.conf
  44. backup: yes
  45. when: ansible_os_family == "Debian"
  46. notify:
  47. - Restart NTP service in Debian OS
  48. - name: Deploy the NTP configuration file for RedHat OS
  49. copy:
  50. src: files/ntp_redhat.conf
  51. dest: /etc/ntp.conf
  52. backup: yes
  53. when: ansible_os_family == "RedHat"
  54. notify:
  55. - Restart NTP service in RedHat OS
  56. register:
  57. redhat_deployment_output
  58. - debug:
  59. var: redhat_deployment_output
  60. handlers:
  61. - name: Restart NTP service in RedHat OS
  62. service:
  63. name: ntpd
  64. state: restarted
  65. when: ansible_os_family == "RedHat"
  66. - name: Restart NTP service in Debian OS
  67. service:
  68. name: ntp
  69. state: restarted
  70. when: ansible_os_family == "Debian"
OUTPUT
Ansible
We can see that the changed value is true for dbsrv01, similarly it is true for webserver 01 and 02 except web server 03 as web server 03 is a Debian based server.
We can also see from the output that our handlers also got triggered and executed successfully.
Handlers are lists of tasks, not really any different from regular tasks, that are referenced by a globally unique name, and are notified by notifiers. If nothing notifies a handler, it will not run. Regardless of how many tasks notify a handler, it will run only once, after all of the tasks complete in a particular play.
IMPORTANT NOTE
Handlers gets executed in the end of the playbook, so if you want to execute some task immediately as soon as the configuration gets changes then handlers won’t help and that can be handled via Conditions.

Summary

In this article we have learned about handlers and their use case and how they can be implemented in a real-life project scenario.
I hope you find this article helpful. Stay tuned for more … Cheers!!
You can also check out some of my previous articles of the series “Learning Ansible” here,