Table of Contents
Blocks in Ansible allow you to logically group a set of tasks together, primarily for one of two purposes. One might be to apply conditional logic to an entire set of tasks. We can use ansible block, rescue and always to perform different kinds of error handling where with the rescue block we can perform cleanup action. We will learn more about this in detail with examples in this section.
Example-1: Why we should use blocks
I have written a small playbook ansible-blocks-1.yml
which will help you understand how ansible blocks can be useful and why it should be used. In this playbook we have 4 tasks wherein 3 tasks requires root level privilege while one of the task can be executed without root level privilege.
Now I have used become: yes
3 times which is just increasing the number of lines in the playbook.
[ansible@controller ~]$ vim ansible-blocks-1.yml --- - name: Ansible Blocks hosts: server1 gather_facts: false tasks: - name: List usr directory content command: "ls -l /usr/" become: yes - name: List root partition content command: "ls -l /root/" become: yes - name: List ansible user's home directory content command: "ls -l ~/" - name: List bin diretcory content command: "ls -l /bin/" become: yes
We could have added become: yes in the starting of the play but then that variable will be applied to the entire play wherein we don't want this permission for task 3. To overcome this we have block level solution wherein we can place all the common tasks under single block.
This is how the playbook would look with blocks
. Here we have combined all the tasks which requires root level privilege into single block and instead of using become 3 times, we have applied this to the entire block.
--- - name: Ansible Blocks hosts: server1 gather_facts: false tasks: - block: - name: List usr directory content command: "ls -l /usr/" - name: List root partition content command: "ls -l /root/" - name: List bin directory content command: "ls -l /bin/" become: yes - name: List ansible user's home directory content command: "ls -l ~/"
NOTE:
become
key has same number of whitespace compared to block
so the become
value is applicable for the entire block.Now we can execute the playbook:
[ansible@controller ~]$ ansible-playbook ansible-blocks-1.yml PLAY [Ansible Blocks] ************************************************************************************************ TASK [List usr directory content] ************************************************************************************ changed: [server1] TASK [List root partition content] *********************************************************************************** changed: [server1] TASK [List bin directory content] ************************************************************************************ changed: [server1] TASK [List ansible user's home directory content] ******************************************************************** changed: [server1] PLAY RECAP *********************************************************************************************************** server1 : ok=4 changed=4 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Example-2: Perform error recovery with rescue block
We have additional benefit while using ansible blocks to perform recovery operation i.e. if any of the tasks within a block fails then in a ideal scenario the playbook would exit (unless you are using ignore_errors: yes
). But with blocks we can assign a rescue
block which can again contain a bunch of tasks. So if any of the tasks within block fails then automatically the tasks from recovery block will be executed which can perform any clean up activity which you would want to do in case of failure.
Lastly we also have an always
block which will be called independent of the task execution status which can be used to give something like SUMMARY or anything as per your requirement.
In this sample playbook ansible-blocks-2.yml we have created two tasks inside the block
wherein one task will pass while the other is expected to fail. In which case the rescue
block will be executed followed by the always
block.
--- - name: Ansible Blocks hosts: server1 gather_facts: false tasks: - block: - name: List home directory content command: "ls -l ~/" - name: Failing intentionally command: "ls -l /tmp/does-not-exist" rescue: - name: Rescue block (perform recovery) debug: msg: "Something went wrong, cleaning up.." always: - name: This will execute always debug: msg: "I will execute even in failure scenario"
Let us execute this playbook:
[ansible@controller ~]$ ansible-playbook ansible-blocks-2.yml PLAY [Ansible Blocks] ************************************************************************************************ TASK [List home directory content] *********************************************************************************** changed: [server1] TASK [Failing intentionally] ***************************************************************************************** fatal: [server1]: FAILED! => {"changed": true, "cmd": ["ls", "-l", "/tmp/does-not-exist"], "delta": "0:00:00.003050", "end": "2020-09-26 10:29:13.864820", "msg": "non-zero return code", "rc": 2, "start": "2020-09-26 10:29:13.861770", "stderr": "ls: cannot access '/tmp/does-not-exist': No such file or directory", "stderr_lines": ["ls: cannot access '/tmp/does-not-exist': No such file or directory"], "stdout": "", "stdout_lines": []} TASK [Rescue block (perform recovery)] ******************************************************************************* ok: [server1] => { "msg": "Something went wrong, cleaning up.." } TASK [This will execute always] ************************************************************************************** ok: [server1] => { "msg": "I will execute even in failure scenario" } PLAY RECAP *********************************************************************************************************** server1 : ok=3 changed=1 unreachable=0 failed=0 skipped=0 rescued=1 ignored=0
As you can see, our task1 was successful but task2 has failed after which the recovery block was executed followed by always block.
Now assuming none of the tasks failed in the block
section in which case the rescue
block will not be executed, but always
block will be executed independent of the task status.
I have updated my playbook to list /tmp
directory content so that the second task does not fail, let's observe the output after execution:
[ansible@controller ~]$ ansible-playbook ansible-blocks-2.yml PLAY [Ansible Blocks] ************************************************************************************************ TASK [List home directory content] *********************************************************************************** changed: [server1] TASK [List tmp directory content] ************************************************************************************ changed: [server1] TASK [This will execute always] ************************************************************************************** ok: [server1] => { "msg": "I will execute even in failure scenario" } PLAY RECAP *********************************************************************************************************** server1 : ok=3 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
So this time the rescue
block has not been called but always
block was executed as expected.
Example-3: Practical example of error and recovery with blocks
Now that we know the concept of block and rescue let's use this in practical use case. I will create one playbook which will perform the following task under our block
- Install
vsftpd
package - Take a backup of
/etc/vsftpd/vsftpd.conf
on the same managed node - Copy
vsftpd.j2
jinja template from controller node to the managed node and replace/etc/vsftpd/vsftpd.conf
- Intentionally fail the block by trying to access incorrect location
Next I will have a rescue
block so that if something goes wrong we will perform some clean up/fallback. In this I have two tasks:
- Print a message on the console for the recovery
- Restore
vsftpd.conf
using the backup filevsftpd.conf.bkp
on the managed node
Lastly we will have always block will restart the vsftpd
service independent of the task execution status
This is our playbook ansible-blocks-3.yml
:
--- - name: Install vsftpd hosts: server1 become: yes vars: anonymous_enable: yes local_enable: yes write_enable: yes anon_upload_enable: yes tasks: - block: - name: install vsftp yum: name: vsftpd - name: take backup of existing config copy: src: /etc/vsftpd/vsftpd.conf dest: /etc/vsftpd/vsftpd.conf.bkp remote_src: yes - name: use Jinja2 template to configure vsftpd template: src: vsftpd.j2 dest: /etc/vsftpd/vsftpd.conf - name: This will fail command: "ls -l /tmp/does-not-exist" rescue: - name: Recovery block debug: msg: "something failed, restoring vsftpd.conf from backup" - name: Restoring vsftpd.conf copy: src: /etc/vsftpd/vsftpd.conf.bkp dest: /etc/vsftpd/vsftpd.conf remote_src: yes always: - name: Restarting vsftpd service: name: vsftpd state: restarted
This is our sample jinj2 template. We had used the same template while working with Jinja2 templates chapter earlier.
[ansible@controller ~]$ cat vsftpd.j2 anonymous_enable={{ anonymous_enable }} local_enable={{ local_enable }} write_enable={{ write_enable }} anon_upload_enable={{ anon_upload_enable }} dirmessage_enable=YES xferlog_enable=YES connect_from_port_20=YES pam_service_name=vsftpd userlist_enable=YES # MY IP Address={{ ansible_facts['default_ipv4']['address'] }}
Let us execute this playbook and verify the steps:
[ansible@controller ~]$ ansible-playbook ansible-blocks-3.yml PLAY [Install vsftpd] ************************************************************************************************ TASK [Gathering Facts] *********************************************************************************************** ok: [server1] TASK [install vsftp] ************************************************************************************************* ok: [server1] TASK [take backup of existing config] ******************************************************************************** changed: [server1] TASK [use Jinja2 template to configure vsftpd] *********************************************************************** changed: [server1] TASK [This will fail] ************************************************************************************************ fatal: [server1]: FAILED! => {"changed": true, "cmd": ["ls", "-l", "/tmp/does-not-exist"], "delta": "0:00:00.003701", "end": "2020-09-26 10:56:49.076834", "msg": "non-zero return code", "rc": 2, "start": "2020-09-26 10:56:49.073133", "s tderr": "ls: cannot access '/tmp/does-not-exist': No such file or directory", "stderr_lines": ["ls: cannot access '/tm p/does-not-exist': No such file or directory"], "stdout": "", "stdout_lines": []} TASK [Recovery block] ************************************************************************************************ ok: [server1] => { "msg": "something failed, restoring vsftpd.conf from backup" } TASK [Restoring vsftpd.conf] ***************************************************************************************** changed: [server1] TASK [Restarting vsftpd] ********************************************************************************************* changed: [server1] PLAY RECAP *********************************************************************************************************** server1 : ok=7 changed=4 unreachable=0 failed=0 skipped=0 rescued=1 ignored=0
As expected TASK 1 and TASK 2 have successfully executed while TASK 3 has failed. Since one of the task in our block has failed, the rescue
block is executed which will restore our vsftpd.conf
file. Lastly the always block is executed to restart the vsftpd
service.
We can also connect to server1 and check if vsftpd.conf.bkp
is created:
[ansible@server-1 ~]$ sudo ls -l /etc/vsftpd/vsftpd.conf*
-rw------- 1 root root 5098 May 14 2019 /etc/vsftpd/vsftpd.conf
-rw-r--r-- 1 root root 5098 May 14 2019 /etc/vsftpd/vsftpd.conf.bkp
What’s Next
Next in our Ansible Tutorial we will learn about include and import module in Ansible with mutliple examples.