Grouping tasks using blocks

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 tasksin this example, you could apply an identical when clause to each of the tasks, but this is cumbersome and inefficientfar better to place all of the tasks in a block and apply the conditional logic to the block itself. In this way, the logic only needs to be declared once. Blocks are also valuable when it comes to error handling and especially when it comes to recovering from an error condition. We shall explore both of these through simple practical examples in this chapter to get you up to speed with blocks in Ansible.

As ever, let's ensure we have an inventory to work from:

[frontends]
frt01.example.com https_port=8443
frt02.example.com http_proxy=proxy.example.com

[frontends:vars]
ntp_server=ntp.frt.example.com
proxy=proxy.frt.example.com

[apps]
app01.example.com
app02.example.com

[webapp:children]
frontends
apps

[webapp:vars]
proxy_server=proxy.webapp.example.com
health_check_retry=3
health_check_interal=60

Now, let's dive straight in and look at an example of how you would use blocks to apply conditional logic to a set of tasks. At a high level, suppose we want to perform the following actions on all of our Fedora Linux hosts:

  • Install the package for the Apache web server.
  • Install a templated configuration.
  • Start the appropriate service.

We could achieve this with three individual tasks, all with a when clause associated with them, but blocks provide us with a better way. The following example playbook shows the three tasks discussed contained in a block (notice the additional level of indentation required to denote their presence in the block):

---
- name: Conditional block play
hosts: all
become: true

tasks:
- name: Install and configure Apache
block:
- name: Install the Apache package
dnf:
name: httpd
state: installed
- name: Install the templated configuration to a dummy location
template:
src: templates/src.j2
dest: /tmp/my.conf
- name: Start the httpd service
service:
name: httpd
state: started
enabled: True
when: ansible_facts['distribution'] == 'Fedora'

When you run this playbook, you should find that the Apache-related tasks are only run on any Fedora hosts you might have in your inventory; you should see that either all three tasks are run or are skippeddepending on the makeup and contents of your inventory, it might look something like this:

$ ansible-playbook -i hosts blocks.yml

PLAY [Conditional block play] **************************************************

TASK [Gathering Facts] *********************************************************
ok: [app02.example.com]
ok: [frt01.example.com]
ok: [app01.example.com]
ok: [frt02.example.com]

TASK [Install the Apache package] **********************************************
changed: [frt01.example.com]
changed: [frt02.example.com]
skipping: [app01.example.com]
skipping: [app02.example.com]

TASK [Install the templated configuration to a dummy location] *****************
changed: [frt01.example.com]
changed: [frt02.example.com]
skipping: [app01.example.com]
skipping: [app02.example.com]

TASK [Start the httpd service] *************************************************
changed: [frt01.example.com]
changed: [frt02.example.com]
skipping: [app01.example.com]
skipping: [app02.example.com]

PLAY RECAP *********************************************************************
app01.example.com : ok=1 changed=0 unreachable=0 failed=0 skipped=3 rescued=0 ignored=0
app02.example.com : ok=1 changed=0 unreachable=0 failed=0 skipped=3 rescued=0 ignored=0
frt01.example.com : ok=4 changed=3 unreachable=0 failed=0 skipped=3 rescued=0 ignored=0
frt02.example.com : ok=4 changed=3 unreachable=0 failed=0 skipped=3 rescued=0 ignored=0

This is very simple to construct, but very powerful in terms of the effect it has on your ability to control the flow over large sets of tasks. 

This time, let's build a different example to demonstrate how blocks can be utilized to help Ansible to handle error conditions gracefully. So far, you should have experienced that if your playbooks encounter any errors, they are likely to stop executing at the point of failure. This is in some situations far from ideal, and you might want to perform some kind of recovery actions in this event rather than simply halting the playbook.

Let's create a new playbook, this time with the following contents:

---
- name: Play to demonstrate block error handling
hosts: frontends

tasks:
- name: block to handle errors
block:
- name: Perform a successful task
debug:
msg: 'Normally executing....'
- name: Deliberately create an error
command: /bin/whatever
- name: This task should not run if the previous one results in an error
debug:
msg: 'Never print this message if the above command fails!!!!'
rescue:
- name: Catch the error (and perform recovery actions)
debug:
msg: 'Caught the error'
- name: Deliberately create another error
command: /bin/whatever
- name: This task should not run if the previous one results in an error
debug:
msg: 'Do not print this message if the above command fails!!!!'
always:
- name: This task always runs!
debug:
msg: "Tasks in this part of the play will be ALWAYS executed!!!!"

Notice that in the preceding play, we now have additional sections to blockas well as the tasks in block itself, we have two new parts labeled rescue and always. The flow of execution is as follows:

  1. All tasks in the block section are executed normally, in the sequence in which they are listed.
  2. If a task in the block results in an error, no further tasks in the block are run:
    • Tasks in the rescue section start to run in the order they are listed.
    • Tasks in the rescue section do not run if no errors result from the block tasks.
  3. If an error results from a task being run in the rescue section, no further rescue tasks are executed and execution moves on to the always section.
  4. Tasks in the always section are always run, regardless of any errors in either the block or rescue sections. They even run when no errors are encountered.

With this flow of execution in mind, you should see output similar to the following when you execute this playbook, noting that we have deliberately created two error conditions to demonstrate the flow:

$ ansible-playbook -i hosts blocks-error.yml

PLAY [Play to demonstrate block error handling] ********************************

TASK [Gathering Facts] *********************************************************
ok: [frt02.example.com]
ok: [frt01.example.com]

TASK [Perform a successful task] ***********************************************
ok: [frt01.example.com] => {
"msg": "Normally executing...."
}
ok: [frt02.example.com] => {
"msg": "Normally executing...."
}

TASK [Deliberately create an error] ********************************************
fatal: [frt01.example.com]: FAILED! => {"changed": false, "cmd": "/bin/whatever", "msg": "[Errno 2] No such file or directory", "rc": 2}
fatal: [frt02.example.com]: FAILED! => {"changed": false, "cmd": "/bin/whatever", "msg": "[Errno 2] No such file or directory", "rc": 2}

TASK [Catch the error (and perform recovery actions)] **************************
ok: [frt01.example.com] => {
"msg": "Caught the error"
}
ok: [frt02.example.com] => {
"msg": "Caught the error"
}

TASK [Deliberately create another error] ***************************************
fatal: [frt01.example.com]: FAILED! => {"changed": false, "cmd": "/bin/whatever", "msg": "[Errno 2] No such file or directory", "rc": 2}
fatal: [frt02.example.com]: FAILED! => {"changed": false, "cmd": "/bin/whatever", "msg": "[Errno 2] No such file or directory", "rc": 2}

TASK [This task always runs!] **************************************************
ok: [frt01.example.com] => {
"msg": "Tasks in this part of the play will be ALWAYS executed!!!!"
}
ok: [frt02.example.com] => {
"msg": "Tasks in this part of the play will be ALWAYS executed!!!!"
}

PLAY RECAP *********************************************************************
frt01.example.com : ok=4 changed=0 unreachable=0 failed=1 skipped=0 rescued=1 ignored=0
frt02.example.com : ok=4 changed=0 unreachable=0 failed=1 skipped=0 rescued=1 ignored=0

Ansible has two special variables, which contain information you might find useful in the rescue block to perform your recovery actions:

  • ansible_failed_task: This is a dictionary containing details of the task from block that failed, causing us to enter the rescue section. You can explore this by displaying its contents using debug, but for example, the name of the failing task can be obtained from ansible_failed_task.name.
  • ansible_failed_result: This is the result of the failed task and behaves the same as if you had added the register keyword to the failing task. This saves you having to add register to every single task in the block in case it fails.

As your playbooks get more complex and error handling gets more and more important (or indeed conditional logic becomes more vital), blocks will become an important part of your arsenal in writing good, robust playbooks. Let's proceed in the next section to explore execution strategies to gain further control of your playbook runs.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.141.193.158