Any change of state or configuration of an application requires a restart for the changes to be applied. For example, you might want to restart Apache HTTP server after configuring a virtual host file for your domain. To accomplish this, you would need to specify two tasks in your playbook - one task to configure the virtual host file and the other to restart Apache.
But here’s a challenge. The Apache service will always be restarted with each successive playbook run regardless of whether or not there is a change in the configuration file. This is because there is a task that explicitly restarts Apache and does not take into consideration the state of the Apache service or the changes made in the configuration file. Restarting the service with each subsequent run is not desirable as it often leads to resource overhead. To address this issue, Ansible handlers are used.
In Ansible, handlers are just like any regular tasks. The difference between handlers and regular tasks is that handlers only run when 'notified' using the notify
directive. Handlers are usually used to start, restart, reload and stop services on target nodes only when there is a change in the state of the task, and not when no change is made. This helps to achieve idempotency.
By default, handlers are executed last regardless of their location in the playbook. You can define and call one or more handlers in a playbook depending on the tasks to be carried out.
In this topic, you will learn how Ansible Handlers are used in playbooks.
Using Ansible Handlers
As earlier mentioned, handlers are just like other tasks in a playbook, the difference being that they are triggered using the notify
directive, and are only run when there is a change of state.
To get the most out of using handlers, here are some points to keep in mind.
- A handler should have a globally unique name within the playbook.
- If multiple handlers are defined with the same name, only the first one will be called. The remaining handlers will be ignored.
- Handlers always run in the order in which they are defined in the
handlers
section, and not in thenotify
section. - If the state of a task remains unchanged, the handler will not run. As such, handlers help in achieving idempotency. Hence a handler task only runs if there is a change in state, else it doesn’t.
To define a handler, the notify
and handlers
directives are used. The notify
directive triggers the execution of the task(s) specified in the handlers
section.
Let us now explore how to define and call Ansible handlers.
Run your deployments in a scalable and cost-effective open cloud infrastructure. Cherry Servers' secure virtual private servers offer automatic scaling, flexible pricing, and 24/7 technical support. Our pre-defined Ansible module helps you automate the installation and configuration.
A single task and a handler
Consider the following playbook that installs Apache and starts it on a target system.
---
- name: Install Apache on RHEL server
hosts: webserver
tasks:
- name: Install the latest version of Apache
dnf:
name: httpd
state: latest
notify:
- Start Apache
handlers:
- name: Start Apache
service:
name: httpd
state: started
The playbook consists of a regular task and a handler. The regular task installs the Apache HTTP server on the target system. Once installed the notify
directive calls the handler task which starts the Apache service.
During playbook runtime, the regular task is executed first followed by the handler.
If the playbook is executed again, the handler task will not run for the simple reason that both the state and configuration of the Apache service remain unchanged.
Multiple tasks and handlers
In most cases, you will be dealing with multiple tasks which might require multiple handlers.
The following playbook contains two regular tasks and two handlers broken down as follows.
Regular tasks
- Installing the latest version of Apache.
- Configuring Apache
Handlers
- Starting Apache Service
- Configuring the firewall to allow inbound HTTP traffic
---
- name: Install Apache on RHEL server
hosts: webserver
tasks:
- name: Install the latest version of Apache
dnf:
name: httpd
state: latest
- name: Configure Apache
copy:
src: /home/cherry/Documents/index.html
dest: /var/www/html
owner: apache
group: apache
mode: 0644
notify:
- Configure Firewall
- Start Apache
handlers:
- name: Start Apache
service:
name: httpd
state: started
- name: Configure Firewall
firewalld:
permanent: yes
immediate: yes
service: http
state: enabled
The first task installs Apache while the second one configures Apache by making changes to the default index.html file and applying the required group, user ownership, and file permissions.
During playbook runtime, the 'Start Apache' handler is executed first followed by the 'Configure Firewall' handler despite the latter appearing first in the notify
section. As mentioned earlier, the order of execution is determined by the handlers
section and not the notify
section.
Grouping Handlers Using the 'listen' Directive
Alternatively, you can group handlers using the listen
keyword and call them using a single notify statement. In the following playbook, the notify
directive is set to restart services which triggers all the handler tasks bearing the listen
directive.
---
- name:
hosts: webserver
tasks:
- name: Restart services on remote target
command: echo "This task restarts services"
notify: "restart services"
handlers:
- name: Restart Apache
service:
name: httpd
state: restarted
listen: "restart services"
- name: verify rsyslog running service:
service:
name: rsyslog
state: restarted
listen: "restart services"
Determine When Handlers Run
By default, handlers run at the end of the play once all the regular tasks have been executed. If you want handlers to run before the end of the play or between tasks, add a meta task to flush them using the meta module. The meta task is defined as follows.
- name: Flush handlers
meta: flush_handlers
The meta: flush_handlers
task calls any handlers that have been notified at that point in the play.
The following playbook performs the following tasks in order
- Installation of EPEL package ( Extra Package for Enterprise Linux)
- Installation of Nginx web server
- Installation of Neofetch
---
- name: Control when handlers run using meta directive
hosts: webserver
tasks:
- name: Add EPEL repository
dnf:
name: epel-release
state: latest
- name: Install Nginx web server
dnf:
name: nginx
state: latest
notify:
- Start Nginx
- name: Flush handlers
meta: flush_handlers
- name: Install Neofetch
dnf:
name: neofetch
state: latest
handlers:
- name: Start Nginx
service:
name: nginx
state: started
The meta: flush_handlers
meta task triggers the Start Nginx
handler to be executed at that point in the play. The remaining task follows suit.
From the output of the playbook runtime, you can see that the handler runs just before the last task.
Discover how Tempesta, an open-source application delivery controller (ADC), leveraged Cherry Servers' bare metal cloud to complete tests and validation of their ADC successfully, benefiting from 99.97% uptime, server customization, and 24/7 technical support.
Handling task errors
By default, when a task fails in a particular host, subsequent tasks are not executed on that host and the playbook exits with an error. Consequently, the handler tasks are also ignored and do not get executed.
Let’s take a simple playbook example to illustrate this. The following playbook contains two tasks. The first task defined by the shell module is set to fail using the /bin/false
argument. The second task, however, is configured to run successfully using the /bin/false
argument.
---
- name: Simulate a task to fail on a host
hosts: webserver
tasks:
- name: set a task to fail
shell: /bin/false
- name: set a task to run successfully
shell: /bin/true
notify: success
handlers:
- name: success
debug:
msg: "This task has been executed successfully"
As expected, the playbook executes and fails upon encountering an error in the first task.
To ignore failed tasks, use the ignore_errors: yes
property in the playbook as follows.
---
- name: Simulate a task to fail on a host
hosts: webserver
ignore_errors: yes
This time around, the failed task is ignored and the playbook proceeds to run the remaining tasks.
The ignored
flag section at the very bottom of the output displays the number of tasks that have been skipped due to failures or errors.
Wrapping up
In this tutorial, we have explored what handlers are and how they are defined and called using the notify
directive. We have also looked at how handlers can be used alongside multiple tasks and how to handle errors associated with task failure. Visit the Ansible Documentation for more information about Ansible handlers.