1 Objective

In this exercise you will be writing your first Ansible playbooks containing tasks

2 Setup

Make sure you are logged into your master ansible machine over SSH with agent forwarding enabled. You should be logged in as your normal non-root user ("sysadm").

Also make sure that from this host, ansible is able to login as root to all the other machines you are managing. To test this:

ansible all -m shell -a id

You should get a response like this:

hostXXX.ws.nsrc.org | success | rc=0 >>
uid=0(root) gid=0(root) groups=0(root)

This shows that the login was successful (return code zero = OK) and that when the command "id" was run on the remote machines, it was running as user "root".

3 Writing a playbook

A playbook comprises of a list of hosts and tasks entries, like this:

- hosts: ...
  tasks: ...
- hosts: ...
  tasks: ...
- hosts: ...
  tasks: ...

In each section, you define a set of task(s) to be run on a set of host(s). The playbook is run in sequence, although the same task can run on multiple hosts in parallel.

3.1 Creating a first playbook

You will do the following as the "sysadm" user. Check you are in your home directory:

pwd   # should show /home/sysadm

If necessary, use "cd" to change to the right directory.

Now use a text editor of your choice to create a file "first.yml"

The contents of this file should look like this:

- hosts:
    - all
  tasks:
    - action: ping

Now run the playbook like this:

ansible-playbook first.yml

Did it work? The output should have these sections:

GATHERING FACTS

This is when the "setup" module is being run to collect information about the hosts you are connecting to

TASK: ping

The task you defined in the playbook

PLAY RECAP

Summarises which tasks were run, how many were success or failure, and how many changes were made

So far your playbook is essentially doing what you did using the ansible command-line tool.

(If you get bored of the cows, you can either uninstall the cowsay package, or uncomment nocows = 1 in /etc/ansible/ansible.cfg)

4 Web server example

To make a more realistic example, let's install a webserver (apache2) on two of your hosts.

Create web.yml which looks like this:

- hosts:
    - hostXXX.ws.nsrc.org
    - hostYYY.ws.nsrc.org
  tasks:
    - action: apt update_cache=yes cache_valid_time=3600
    - action: apt pkg=apache2 state=present

Replace hostXXX and hostYYY with two of the hosts you are managing; they must exist in your inventory.

Run it (note that it may be slow the first time).

ansible-playbook web.yml

Did it work? Take a note of the "ok" and "changed" values.

Run it again:

ansible-playbook web.yml

How do the "ok" and "changed" values look now?

Explanation: there are two tasks involving the apt module. One updates the cache of available packages (like "apt-get update") and the other ensures that apache2 is installed.

At this point, a webserver should be running on both of those hosts. Test it by pointing your laptop's web browser to hostXXX.ws.nsrc.org and hostYYY.ws.nsrc.org

You should see "Apache2 Ubuntu Default Page"

4.1 Documentation

It would be helpful if the playbook could be self-documenting, so edit web.yml so it now looks like this:

- hosts:
    - hostXXX.ws.nsrc.org
    - hostYYY.ws.nsrc.org
  tasks:
    - name: ensure package cache is up to date
      action: apt update_cache=yes cache_valid_time=3600
    - name: install web server
      action: apt pkg=apache2 state=present

Run it again. You should get more helpful TASK descriptions as it runs.

4.2 Alternate syntax

As a shortcut, instead of writing

action: foo ....

you can write

foo: ....

This means you can slightly simplify that playbook to:

- hosts:
    - hostXXX.ws.nsrc.org
    - hostYYY.ws.nsrc.org
  tasks:
    - name: ensure package cache is up to date
      apt: update_cache=yes cache_valid_time=3600
    - name: install web server
      apt: pkg=apache2 state=present

Which form you prefer is just a personal choice.

From this point on, learning ansible is really just a case of getting to know the different modules that are available.

4.3 Copying a file

Let's say we want to replace that Ubuntu default web page with our own.

Still in your sysadm home directory, create a file front.html with some HTML text, e.g.

<html>
<head><title>Hello world</title></head>
<body>
This is the page I installed
</body>
</html>

Now add a new task to our playbook:

- hosts:
    - hostXXX.ws.nsrc.org
    - hostYYY.ws.nsrc.org
  tasks:
    - name: ensure package cache is up to date
      apt: update_cache=yes cache_valid_time=3600
    - name: install web server
      apt: pkg=apache2 state=present
    - name: install index page
      copy: src=front.html dest=/var/www/html/index.html backup=yes

After this has run successfully (check for "changed=1"), point your laptop's web browser at your two hosts and check you have a new index page.

What about if we wanted to keep the original file? That is what backup=yes is for. If you log in to one of those hosts and look at the contents of that directory, you'll see that the original file is still there but renamed to a name containing its timestamp.

# ls /var/www/html
index.html  index.html.2015-05-04@13:21~

4.4 Check and diff

Suppose you want to know what changes ansible will make, before it makes them? Two flags are provided for this.

It is common to use both flags. Try changing the text in front.html, and then running this command:

ansible-playbook web.yml --check --diff

It should identify that index.html is going to be updated, and show you the differences.

Run it again without the --check flag and then it will actually apply the change.

4.5 Handlers

Sometimes when you make a configuration change it's necessary to restart the service. Ansible supports this though "handlers".

Imagine that whenever the index.html page changes you need to restart apache (although that's not actually true). You add a "notify:" statement to every action which needs the restart, and a "handler:" which performs the restart.

- hosts:
    - hostXXX.ws.nsrc.org
    - hostYYY.ws.nsrc.org
  tasks:
    - name: ensure package cache is up to date
      apt: update_cache=yes cache_valid_time=3600
    - name: install web server
      apt: pkg=apache2 state=present
    - name: install index page
      copy: src=front.html dest=/var/www/html/index.html backup=yes
      notify: restart apache2
  handlers:
    - name: restart apache2
      service: name=apache2 state=restarted

Run your playbook again, firstly without changing front.html, and then after changing front.html.

In the latter case you should see

NOTIFIED: [restart apache2]

which shows that the handler was triggered.

4.6 Tags

As your playbook gets bigger, it may get slower to run, and you may wish to run only part of a playbook to bypass the earlier steps. The way to do this is using 'tags'. Example:

- hosts:
    - hostXXX.ws.nsrc.org
    - hostYYY.ws.nsrc.org
  tasks:
    - name: ensure package cache is up to date
      apt: update_cache=yes cache_valid_time=3600
      tags: install
    - name: install web server
      apt: pkg=apache2 state=present
      tags: install
    - name: install index page
      copy: src=front.html dest=/var/www/html/index.html backup=yes
      tags: configure
      notify: restart apache2
  handlers:
    - name: restart apache2
      service: name=apache2 state=restarted

Now try:

ansible-playbook web.yml -t configure

and it will run only the task which has been tagged as "configure". When writing a playbook, you can assign whatever tags make sense to you.

4.7 Limit to specific hosts

You can also tell the playbook to run against only a single host or a subset of hosts. The way to do this is with the '-l' (limit) option.

ansible-playbook web.yml -l hostYYY.ws.nsrc.org

This is particularly useful for testing and staged rollout; but note that the -l flag is only a filter against the hosts already listed in the playbook. It cannot cause the playbook to run against other hosts.

For example:

ansible-playbook web.yml -l 'hostYYY.ws.nsrc.org;hostZZZ.ws.nsrc.org'

will only run those tasks which were already defined to run on either hostYYY or hostZZZ.

4.8 Adding more "plays"

A "play" is one group of hosts and tasks, and a playbook can contain multiple instances of these.

Let's say that in your web application cluster you need another host which is a mysql server. You can include this in the same playbook by adding another play. Add a new section to the end of your web.yml file so that it looks like this:

- hosts:
    - hostXXX.ws.nsrc.org
    - hostYYY.ws.nsrc.org
  tasks:
    - name: ensure package cache is up to date
      apt: update_cache=yes cache_valid_time=3600
      tags: install
    - name: install web server
      apt: pkg=apache2 state=present
      tags: install
    - name: install index page
      copy: src=front.html dest=/var/www/html/index.html backup=yes
      tags: configure
      notify: restart apache2
  handlers:
    - name: restart apache2
      service: name=apache2 state=restarted

- hosts:
    - hostZZZ.ws.nsrc.org
  tasks:
    - name: install mysql server
      apt: pkg=mysql-server state=present

Run this playbook to confirm that it does what you expect.

4.9 Host groups

Finally, a way to make your playbook easier to maintain is to make use of host groups in the inventory.

Edit your inventory file (/etc/ansible/hosts), remembering you have to use sudo to do this. Divide it into groups by adding group headings surrounded by square brackets, so that it looks like this:

[app_web]
hostXXX.ws.nsrc.org ansible_ssh_user=root
hostYYY.ws.nsrc.org ansible_ssh_user=root

[app_db]
hostZZZ.ws.nsrc.org ansible_ssh_user=root

Then you can simplify your playbook by listing the groups instead of the individual hosts:

- hosts:
    - app_web
  tasks:
    ... as before

- hosts:
    - app_db
  tasks:
    ... as before

Now test that everything still works:

ansible-playbook web.yml

You can also use groups on the command line, e.g.

ansible app_web -m shell -a 'ls /'

4.10 Playbook storage

Your web.yml file now documents exactly how you built your web servers, and can be used to create additional servers, or rebuild a server if its disk dies.

This means that it's a valuable asset. You should store it somewhere safe, e.g. in a version control system like subversion or git, or in a backed-up file server.

5 Conclusion