Chapter 3: Implementing an Ansible Playbook

Writing and running playbooks

A playbook turns a long, fiddly manual procedure into a short text file you can run again and again with the same result. This section covers how playbooks are laid out in YAML, how tasks call modules, and how to check, preview and run a playbook with ansible-navigator.

Tasks, plays and playbooks

Because a play is written in readable YAML, it is also documentation: the tasks describe exactly what is needed to deploy your application or infrastructure.

How a playbook is structured: A playbook is a YAML list of plays. Each play is a dictionary whose tasks key holds a list of tasks. Each task names one module and its arguments.

Anatomy of a playbook

Here is a complete playbook with one play and one task. Click through the lines to see what each part does.

Two indentation rules

YAML uses spaces to show structure. It does not care how many spaces you use, but it does insist on two rules:

  1. Items at the same level of the hierarchy, such as the keys of one play or the items in one list, must have the same indentation.
  2. Items that are children of another item must be indented more than their parent.

Blank lines are allowed anywhere and help readability.

A play is a dictionary; tasks is a list

A YAML list is one item per line, each starting with a dash and a space:

- apple
- orange
- grape

A play is a collection of key-value pairs (a dictionary). Its keys all share the same indentation. Here the first two keys have simple values and the third has a list as its value:

name: just an example
hosts: webservers
tasks:
  - first
  - second
  - third

A tasks list with several tasks looks like this. Each task is its own dictionary with a name and a module:

tasks:
  - name: Web server is enabled
    ansible.builtin.service:
      name: httpd
      enabled: true

  - name: NTP server is enabled
    ansible.builtin.service:
      name: chronyd
      enabled: true

  - name: Postfix is enabled
    ansible.builtin.service:
      name: postfix
      enabled: true

Modules, collections and FQCNs

Modules are the tools that tasks use. Since Ansible Core 2.11, modules are packaged in Ansible Content Collections, each containing a set of related modules and their documentation.

Browse what your EE contains with ansible-navigator collections. Type : and a line number to open a collection and see its modules; do the same on a module to read its documentation. Esc goes back.

Modules are named with a fully qualified collection name (FQCN): collection_namespace.collection_name.module. For example, the copy module in ansible.builtin is ansible.builtin.copy. That lets two collections each have a module called copy without conflict.

Running a playbook

Run a playbook from the control node with ansible-navigator run, passing the playbook file name. Add -m stdout to print the output to your terminal; without it, navigator opens its interactive mode.

[user@controlnode playdemo]$ cat webserver.yml
---
- name: Play to set up web server
  hosts: servera.lab.example.com
  tasks:
  - name: Latest httpd version installed
    ansible.builtin.dnf:
      name: httpd
      state: latest
[user@controlnode playdemo]$ ansible-navigator run -m stdout webserver.yml

PLAY [Play to set up web server] ***********************************************

TASK [Gathering Facts] *********************************************************
ok: [servera.lab.example.com]

TASK [Latest httpd version installed] ******************************************
changed: [servera.lab.example.com]

PLAY RECAP *********************************************************************
servera.lab.example.com    : ok=2    changed=1    unreachable=0    failed=0
skipped=0    rescued=0    ignored=0

Things to notice:

Run the same playbook again and every task reports ok with changed=0, because httpd is already at the latest version. That is idempotency in action.

More output with -v

The default output does not show task details. Add -v for more, up to four levels:

OptionAdds
-vTask results.
-vvTask results and task configuration.
-vvvInformation about connections to managed hosts.
-vvvvExtra verbosity for connection plug-ins, including the users used on managed hosts and what scripts were run.

Check before you run

Syntax check

--syntax-check parses the playbook without running anything. Silence plus the playbook path means it is valid:

[user@controlnode playdemo]$ ansible-navigator run \
> -m stdout webserver.yml --syntax-check
playbook: /home/user/playdemo/webserver.yml

When it fails, it tells you roughly where. Here the space after name: is missing:

[user@controlnode playdemo]$ ansible-navigator run \
> -m stdout webserver.yml --syntax-check
ERROR! Syntax Error while loading YAML.
  mapping values are not allowed in this context

The error appears to be in ...output omitted... line 3, column 8, but may
be elsewhere in the file depending on the exact syntax problem.

The offending line appears to be:

- name:Play to set up web server
  hosts: servera.lab.example.com
       ^ here

Dry run with --check

--check runs the playbook in check mode: Ansible connects and reports what would change, but changes nothing on the hosts.

[user@controlnode playdemo]$ ansible-navigator run \
> -m stdout webserver.yml --check
...output omitted...
TASK [Latest httpd version installed] ******************************************
changed: [servera.lab.example.com]

PLAY RECAP *********************************************************************
servera.lab.example.com    : ok=2    changed=1    unreachable=0    failed=0  ...

Here the dry run predicts that the httpd task would make a change.

A safe playbook workflow: Catch YAML mistakes first, preview changes second, apply third, and prove idempotency last.

Takeaway

Write a correctly indented playbook with a play, hosts and tasks. Use the chapter lab to check this on a real host.