Ansible playbook
Now things are starting to get interesting. Using Ansible playbooks, we will be able to achieve configuration management, orchestration, provisioning, and deployment. Playbook scripting uses the Ansible ad hoc commands in a more organized way, similar to the way in which shell scripting arranges shell commands to execute a task on a system, but more advanced than that. Ansible playbooks can set up and configure complex environments on bare metal, virtually, or on the cloud. It can sequence multi-tier machine roll-outs; apply systems, devices, and application patches and fixes; gather data from hosts or monitoring services; and act accordingly to send immediate actions to servers, network devices, and load balancers. All of these tasks can be delegated to other servers.
Playbooks are coded in a YAML data serialization format. This is a human-readable formatting, allowing the developer an easier sharing of their code and better organization as part of team projects. YAML is a very simple language compared to a traditional coding/scripting language.
Playbooks cannot do much on their own without their Ansible modules, which you can either get from Ansible Galaxy or build yourself. Modules will be explained in more detail in the next chapter. A playbook script runs multiple plays. Each one executes a number of tasks, which are composed of a number of modules on selected hosts from the Ansible inventory or from an external inventory, if this option is selected. These modules apply certain configuration changes, updates, or fixes to the selected hosts, depending on the nature of the module. A simple playbook running one play with one module to update the package manager cache is shown as follows:
nano ./playbook/apt_cache.yml
Then, we fill it in with the following code:
---
- name: playbook to update Debian Linux package cache
hosts: servers
tasks:
- name: use apt to update its cache
become: yes
apt:
update_cache: yes
We save the file and then run the ansible-playbook command as follows:
ansible-playbook playbooks/apt-cache.yml
The following output from the playbook's execution shows if the playbook's has made a change to the hosts:
As you can see, a task called gathering facts has been executed within our simple playbook. This is a task that runs the module setup, which collects all of the useful information about the host or hosts in question.
Let's try to break down the structure of a playbook script. First, let's explain the name option. This is an optional parameter, but it is highly recommended. When a simple and meaningful sentence is written as input to the name option, it helps provide a useful description of the play for improved user communication. It is also helpful when running the playbook, in order to see which plays have finished and which are still processing. A playbook output without the use of the name option looks as follows:
---
- hosts: servers
gather_facts: False
tasks:
- apt:
update_cache: yes
become: yes
We then have the hosts parameter or line. This is used to point to the inventory that the play should be run on, either to specify a certain group or host, or both of these combined. At the same level within the playbook, we can fill in other parameters underneath it. Those parameters can be host or group variables, used to enforce the parameters that are configured in their inventory files. These variables can be play-specified when we define them underneath the line hosts:
---
- name: playbook to update Debian Linux package cache
hosts: servers
remote_user: setup
become: yes
tasks:
They can also be task-specific when we define them within the task:
---
- name: playbook to update Debian Linux package cache
hosts: servers
tasks:
- name: use apt to update its cache
apt:
update_cache: yes
become: yes
become_user: setup
We then move to the tasks list, which is basically a list module to be executed in a series. Similarly to a playbook, each task can be named using the name: parameter. This is highly recommended for both documentation and to follow upon the status of tasks:
tasks:
- name: use apt to update its cache
apt: update_cache=yes
If a task fails, the playbook execution stops with the failure. To bypass this when running a non-critical task, we can always add the ignore_errors: True parameter:
tasks:
- name: use apt to update its cache
apt:
update_cache: yes
ignore_errors: True
Finally, handlers are a major factor in making playbooks independent and automated, with less interaction for the user. They have the capacity to recognize changes and act accordingly. They are a way of controlling the system's behaviors and running actions that respond to the needs of those behaviors:
tasks:
- name: use apt to update its cache
apt:
update_cache: yes
become: yes
notify: pkg_installable
handlers:
- name: pkg_installable
apt:
name: htop
state: latest
become: yes
When executing the preceding playbook, the output should look as follows:
Advanced Ansible playbook scripting includes conditional and loop statements to give the developer various logic and patterns to play within their playbooks.
For example, the when parameter is a way of implementing task control with conditions. Consider the following example, which only runs application updates when it is running on the right family of Linux:
tasks:
- name: use apt to update all apps for Debian family
apt:
name: "*"
state: latest
update_cache: yes
become: yes
when: ansible_os_family == "Debian"
- name: use yum to update all apps for Red Hat family
yum:
name: '*'
state: latest
become: yes
when: ansible_os_family == "Red Hat"
The when parameter condition is not limited to values collected from the host system but also from the task's execution status, which can be one of the following:
- Result has failed
- Result has succeeded
- Result has been skipped
There are various other ways to use the playbook conditions. We will look at these in later chapters.
The loop statement can also be used. For this, we are going to use the loop parameter. In some cases, when we want to apply an action on multiple entries, we use the vars: parameter, as shown in the following example:
tasks:
- name: use apt to install multiple apps
apt:
name: '{{ app }}'
state: latest
update_cache: yes
vars:
app:
- htop
- mc
- nload
become: yes
This can also be done using the loop parameter:
tasks:
- name: use apt to install multiple apps
apt:
name: '{{ item }}'
state: latest
update_cache: yes
loop:
- htop
- mc
- nload
become: yes