Ansible Quick Start Guide
上QQ阅读APP看书,第一时间看更新

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
YAML requires a very strict file structure when writing its files. Well-aligned action parameters are very important for the success of the playbook file.

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.

When not needed, disabling the  gathering facts task can increase the performance of your playbooks. This can be done by adding gather_facts: False when defining a play.

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
When executing the preceding playbook, the output should look as follows:

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
As you have seen from the two previous examples, each task's action line can be used in two different ways: either broken down or in one line. You can choose which to use based on your needs.

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:

Handlers can also be placed before the tasks in listen mode to enable action execution whenever they are triggered by multiple tasks.

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

In this chapter, we have only covered the tip of the iceberg of Ansible playbooks. There are many more advanced customizations and parameters that we cannot cover in this book. Ansible is well-known for its neat and well-maintained documentation, so we recommend you have a look at this for more information.