Managing Cluster With Ansible Playbooks

While ad hoc commands are indispensable for learning Ansible and executing one time tasks, playbooks are real workhorses. In this introductory post I will explain the essence of a playbook on example.

So what is a playbook?

At the first glance, it’s a file written in YAML that contains a list of plays. If we look closer, we’ll see that a playbook file is a mixture of configuration and script concepts. On one hand, it specifies the desired configuration of systems. On the other hand, like in a script, it defines in which order tasks must be executed in a play.

It’s better to see once than to hear 100 times. This is a playbook we’ll use as an example:

---
- hosts: frontend,backend
  remote_user: yury
  become: True
  tasks:
  - name: ensure golang is at the latest version
    apt: name=golang state=latest

- hosts: frontend
  remote_user: yury
  become: True
  tasks:
  - name: ensure apache is at the latest version
    apt: name=apache2 state=latest
  - name: write the apache config file
    template:
      src: conf/apache2.j2
      dest: /etc/apache2/apache2.conf
    notify:
    - restart apache
  - name: ensure apache is running
    service:
      name: apache2
      state: started
  handlers:
    - name: restart apache
      service:
        name: apache2
        state: restarted

This playbook has two plays. Both plays start with unpadded - followed by hosts parameter.

Playbook structure

As I mention earlier, a playbook is a list of plays. But what is a play? One of the ways to define it is a YAML structure which conveniently defines a list of tasks that have to be executed on a group of hosts. Each play must include the following mandatory attributes:

  • hosts - usually an Ansible group, but can be a pattern
  • remote_user - an ssh user Ansible will use to connect to remote systems specified in hosts
  • tasks - tasks (steps) that have to be executed on the systems

In our example, the first play is applied to two groups (frontend and backend). It will connect as yury to remote hosts and will insure that the latest version of golang is installed.

Plays have other optional attributes. In our example we use:

  • become - tells Ansible to raise privilege to super user
  • handlers - list of special tasks listenning for events. A handler is executed only when the event it listens to occurs.
Tasks

The real job is done in tasks. Tasks are executed in order, one at a time. So a task will never run until the previous is complete on all hosts.

The only mandatory part of any task is an Ansible module with its parameters. For example the following task runs apt module and ensures that the latest version of apache2 package is installed:

  - name: ensure apache is at the latest version
    apt: name=apache2 state=latest

It’s a good practice for each task to have name attribute, which describes what the task is doing. Every time a task is executed, its name is written to the standard output, so that the user running the play can see what is going on.

Handlers

A task may have an optional notify attribute, which issues an event when the task changes state. If a play has a handler listening to the event, the handler will be triggered after block of tasks completes. Regardless of how many tasks notify a handler, it will be executed only once.

Handlers are often used to restart services (as in our example) and trigger reboots.

Running playbook

Now when we went thoroughly through our example playbook, let’s run it:

ansible-playbook -K node.yml

Note that the playbook requires privilege escalation, so I’ve added -K parameter. It will tell Ansible to ask a password for sudo, which Ansible will run under the hood.

The output depends on what has been previously installed on systems and may look like below:

SUDO password:

PLAY [frontend,backend] ********************************************************

TASK [setup] *******************************************************************
ok: [node202]
ok: [node101]
ok: [node102]
ok: [node201]

TASK [ensure golang is at the latest version] **********************************
ok: [node202]
ok: [node201]
changed: [node102]
changed: [node101]

PLAY [frontend] ****************************************************************

TASK [setup] *******************************************************************
ok: [node201]
ok: [node101]
ok: [node102]

TASK [ensure apache is at the latest version] **********************************
ok: [node101]
ok: [node201]
ok: [node102]

TASK [write the apache config file] ********************************************
ok: [node101]
ok: [node201]
ok: [node102]

TASK [ensure apache is running] ************************************************
ok: [node102]
ok: [node201]
ok: [node101]

PLAY RECAP *********************************************************************
node101                    : ok=6    changed=1    unreachable=0    failed=0
node102                    : ok=6    changed=1    unreachable=0    failed=0
node201                    : ok=6    changed=0    unreachable=0    failed=0
node202                    : ok=2    changed=0    unreachable=0    failed=0

The most interesting part of the output is PLAY RECAP at the end. It outlines stats for each node, highlighting if any changes or fails happened.