This is the fourth article in the Automation with Ansible series. For the third article, please refer to this link.
In this series, we will be looking at different ways in which Ansible can be used to implement automation in the IT industry
Through the previous articles, we have looked at some of the basics about Ansible, and written Ansible Playbooks to set up different tools like Docker, and HDFS cluster. In this article, we will look at an important property of Ansible, and how it can be used to write better Plays in the Ansible Playbook.
Ansible’s Nature of Idempotence
Idempotence is originally a concept from Mathematics. As defined by Wikipedia,
Idempotence is the property of certain operations in mathematics and computer science whereby they can be applied multiple times without changing the result beyond the initial application.
In Ansible, idempotence is offered as a built-in feature of many of the Ansible modules. This relates directly to the working of Ansible based on the state of the target node. In simple terms, this means when we run any task, the required changes in the state of the target node are made and any more repetitions of this task on the target node do not bring about any change in the state of the target node.
Idempotence is present in almost all modules of Ansible; However, some modules like command and shell do not have this nature. Thus, when working with such modules, we need to write the tasks in such a way that we can introduce a custom nature of idempotence.
When does Idempotence fail?
Even if a module has the nature of Idempotence, certain attributes and their values can prevent it from being used in the way it is meant to be. For example, let’s consider the case where we try to install the HTTPD web server on an RHEL8 OS (or VM). The Ansible Playbook for this is given below.
In the Playbook seen above, we are trying to install the HTTPD web server using the package module. Next, we copy a custom configuration file from the target node to the target node using the template module. Then, we allow network traffic through Port 80 of the target node to allow other systems to access the pages on the target node through the web server, and finally, we start the HTTPD service.
The key part is the task where we start the HTTPD service. The service’s
state attribute is set to
restarted. This means, every time this task is executed, the HTTPD service would be restarted. This goes against Ansible’s nature of Idempotence and also results in unnecessary operations being performed.
A possible solution is to set the state to
started, instead of
restarted. This means, if the service is already running, then the task will not make any changes in the state of the target node. However, this also poses a challenge in situations where we change the custom configuration file. If the configuration file is changed, then the service must be restarted to apply the new changes on the service, which will not be possible unless the task can restart the service on its own.
So, how do we introduce a customized nature of Idempotence in our current task?
Introducing Idempotence using Ansible Handlers
One way of making our task’s working idempotent is to ensure it runs only based on a certain condition. In our case, we can consider restarting HTTPD only if the configuration file of the target node is being changed in any way. In other words, we can restart the HTTPD service, only if there are any changes made by the task copying the custom configuration file to the target node. To execute tasks based on conditions, we can use Ansible Handlers.
Handlers are tasks that only run when notified. Each handler should have a globally unique name. Handlers are defined under a different section from the tasks in a Playbook. They are executed only if one or more tasks call for a particular handler using the
Now, the Playbook can be modified as shown below. When the Playbook is run for the first time (when setting up httpd), all the tasks are executed, and the notification of the task copying the custom configuration file leads to the execution of the handler conf_changed. But, if the Playbook is executed repeatedly with no changes in the configuration file, then Ansible’s idempotence ensures that no task is executed.
When the Playbook is executed with changes in the custom configuration file, then the Copying conf file task is executed and changes are made. This triggers the execution of the conf_changed handler, which restarts the HTTPD service, and applies the changes in the configuration file.
In this article, we have seen how Ansible’s Idempotence works and the situations where it can fail. We also looked into how we can introduce a customized nature of Idempotence in our Playbooks using Handlers.
In the next article, we will look at how we can set up a Load Balancer on RHEL8 VMs using Haproxy and Ansible