Task Restarting HTTPD Service is not idempotence in nature and also consume more resources suggest a way to rectify this challenge in Ansible playbook.
To solve this challenge there can be multiple solutions. For example- handlers which will notify tasks in the same play depending upon what happened to the notifying task [click here to learn more], creating your own self tailored module, or using conditions. So today we we’ll see how this use case can be solved using conditions in Ansible PlayBook.
Conditions in an Ansible PlayBook can be manipulated just like any other language, this can be done with the help of templating through Jinja2 where we can even use python to make the best fit for our use-case.
So let’s begin with the task by first understanding the requirement; as we all know most of the tasks in Ansible follow idempotency i.e. an operation, action, or request that can be applied multiple times without changing the result, and hence it helps us reduce our time complexity as we do not require to do tasks which are already in their required state. But some modules or some parts of that module are not idempotent and hence requires same computing every time the playbook is executed, but we can overcome them to suit our requirements as and when we require to.
Restarting a service is one such operation. Here if we are required to restart a service (httpd in our case) when we would require to either manually restart HTTPD with systemctl or either write it in playbook and solve the issue of non-idempotency by ourselves.
Now that our goal is clear let’s see what must be such situation where we are required to do such operation. In case of HTTPD if we change the configurations of the server we would require to restart the service as it is better than first stopping and than starting the server again i.e. restart plays in like a short-hand for the long process of stop and start. So I will demonstrate this by changing the webpage file, though such case may not require a restart nut this is just for demonstration purposes.
So the step wise process for our purpose will be as follows:
But this plan will has issue, as the restart will happen on every run as it’s not idempotent. So let’s increase some steps to first check if the task of copying the content in some file changed something or not:
So now we created a variable to check if there were any changes made to the document root (/var/www/html/index.html). Now ideally this is all we are required to do to solve our use case. However if we try to do so, we will encounter a big bug, as though this will check if there were any changes made to our file, it is incompetent to start our service if the service is not previously running. Which means if there were no changes the file, the task of restarting will be skipped independent of if the service was previously running or not, i.e. our stopped service can’t be started in case of no change.
Again we will have to increase the number of tasks and operations to achieve this:
Ohkay so now our problem of idempotency is solved, as well as we achieved to first check if our service is down and if so then we are able to bring it up again. But adding the new step makes our PlayBook a little less idempotent, how…? Well say if we induced some new changes in the document root, and if our service was not already up, our playbook will run both the tasks of starting and then restarting the service which will again induce the non-idempotent nature. Hence we must be able to create a check such that if the task of copying changes something and only if the service was already running i.e. the service starting task did not change anything, will lead to run the restart task.
If we try to understand the logic here, we want our restart task to not run when:-
- There is no changes in the document root. (Independent of the start service task). This can be checked from the return value of the task, in
changedwhich is a Boolean
- When there is a change in the document root but the start service task did not change anything (i.e. the service was already up and running). Again by checking the return value of the tasks,
changedwhich again is a Boolean
And run the restart task when: The start service task returns
changed and copy task returns
Now the logic of this can be achieved by simply using a not operator for the value of start service task for
changed which I did using the
== operator with ‘false’ (there are other better ways to do this) and then performing a logical ‘and’ with the value of copy task for
changed . And to store the values of these operations and comparisons I used
register which helps us to store Ansible facts.
Now the playbook for all this will look something as follows:
(Click here to learn more about writing playbooks)Here I used
content instead of a template or a file to do the copy tasks. Step 3 according to our plan is achieved by simply using a register in task 2. Step 4 is done via task 3. Task 4 and task 5 are used to create the final condition and to print the condition(as a check). And task 6 uses the final condition
condition to check if to run or not. To do a proper check for this there are 4 combinations of situation where the playbook can be used and tested:
- When the service is not running and there is a change in the document root:
The playbook for this condition for such situation will run as follows:
2. When the service is running and there is no changes to the file to be made:
In this condition the task will run as follows:
3. When the service is not running but there is no change in the file:
The playbook for such a condition will run something as follows:
4. When the service is running but the content has been changed:
In this case and only in this case will we want to run our ‘restarting service’ task…
For this case our playbook runs something as follows:
The last task is yellow which means it ran successfully… Hence we accomplished our task successfully(⌐■_■).
Task Completed Successfully \(@^0^@)/!!!🎉🐱🏍✔
Thank you for reading!!!😁 I hope you enjoyed it… You are amazing!!!