In this exercise you will be writing your first Ansible playbooks containing tasks
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 ("vmX-gY").
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:
(venv) vmX-gY@ansible-host:~/ansible-playbook$ ansible all -m shell -a id -b
You should get a response like this:
vmX-gY.lab.workalaya.net | CHANGED | 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".
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.
You will do the following as the "sysadm" user. Check you are in your home directory:
(venv) vmX-gY@ansible-host:~/ansible-playbook$ pwd # should show/home/vmX-gY/ansible-playbook
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:
(venv) vmX-gY@ansible-host:~/ansible-playbook$ ansible-playbook first.yml
Did it work? The output should be like:
(venv) vmX-gY@ansible-host:~/ansible-playbook$ ansible-playbook first.yml
PLAY [all] *********************************************************************
TASK [Gathering Facts] *********************************************************
ok: [vmX-gY.lab.workalaya.net]
TASK [ping] ********************************************************************
ok: [vmX-gY.lab.workalaya.net]
PLAY RECAP *********************************************************************
vmX-gY.lab.workalaya.net : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Details about output of ansible-playbook 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.
(venv) vmX-gY@ansible-gY:~/ansible-playbook$ git add .
(venv) vmX-gY@ansible-gY:~/ansible-playbook$ git commit -am "add first.yml file"
To make a more realistic example, let's install a webserver (apache2) on your virtual machine.
Create web.yml which looks like this:
- hosts:
- vmX-gY.lab.workalaya.net
become: true
tasks:
- action: apt update_cache=yes cache_valid_time=3600
- action: apt pkg=apache2 state=present
Replace vmX-gY the hosts you are managing; they must exist in your inventory.
Run it (note that it may be slow the first time).
(venv) vmX-gY@ansible-host:~/ansible-playbook$ ansible-playbook web.yml
Did it work? Take a note of the "ok" and "changed" values.
Run it again:
(venv) vmX-gY@ansible-host:~/ansible-playbook$ 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 your virtual machine. Test it by pointing your laptop's web browser to vmX-gY.lab.workalaya.net
You should see "Apache2 Ubuntu Default Page"
(venv) vmX-gY@ansible-gY:~/ansible-playbook$ git add .
(venv) vmX-gY@ansible-gY:~/ansible-playbook$ git commit -am "add web.yml file"
It would be helpful if the playbook could be self-documenting, so edit web.yml so it now looks like this:
- hosts:
- vmX-gY.lab.workalaya.net
become: true
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.
(venv) vmX-gY@ansible-host:~/ansible-playbook$ ansible-playbook web.yml
PLAY [vmX-gY.lab.workalaya.net] ************************************************
TASK [Gathering Facts] *********************************************************
ok: [vmX-gY.lab.workalaya.net]
TASK [ensure package cache is up to date] **************************************
[WARNING]: Could not find aptitude. Using apt-get instead
ok: [vmX-gY.lab.workalaya.net]
TASK [install web server] ******************************************************
ok: [vmX-gY.lab.workalaya.net]
PLAY RECAP *********************************************************************
vmX-gY.lab.workalaya.net : ok=3 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
As a shortcut, instead of writing
action: foo ....
you can write
foo: ....
This means you can slightly simplify that playbook to:
- hosts:
- vmX-gY.lab.workalaya.net
become: true
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.
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:
- vmX-gY.lab.workalaya.net
become: true
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 vm 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.
lab@vmX-gY:~$ ls /var/www/html/
index.html index.html.379.2019-10-31@11:07:28~
(venv) vmX-gY@ansible-gY:~/ansible-playbook$ git add .
(venv) vmX-gY@ansible-gY:~/ansible-playbook$ git commit -am "add front.html and updated web.yml"
Suppose you want to know what changes ansible will make, before it makes them? Two flags are provided for this.
--check
will tell you which changes would be made, without actually making them. (Not all modules support this)--diff
shows you the differences between the old and new filesIt is common to use both flags. Try changing the text in front.html, and then running this command:
(venv) vmX-gY@ansible-host:~/ansible-playbook$ 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.
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:
- vmX-gY.lab.workalaya.net
become: true
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
(venv) vmX-gY@ansible-host:~/ansible-playbook$ ansible-playbook web.yml
PLAY [vmX-gY.lab.workalaya.net] ************************************************
TASK [Gathering Facts] *********************************************************
ok: [vmX-gY.lab.workalaya.net]
TASK [ensure package cache is up to date] **************************************
[WARNING]: Could not find aptitude. Using apt-get instead
ok: [vmX-gY.lab.workalaya.net]
TASK [install web server] ******************************************************
ok: [vmX-gY.lab.workalaya.net]
TASK [install index page] ******************************************************
changed: [vmX-gY.lab.workalaya.net]
RUNNING HANDLER [restart apache2] **********************************************
changed: [vmX-gY.lab.workalaya.net]
PLAY RECAP *********************************************************************
vmX-gY.lab.workalaya.net : ok=5 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
which shows that the handler was triggered.
(venv) vmX-gY@ansible-gY:~/ansible-playbook$ git add .
(venv) vmX-gY@ansible-gY:~/ansible-playbook$ git commit -am "add handler feature"
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:
- vmX-gY.lab.workalaya.net
become: true
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:
(venv) vmX-gY@ansible-host:~/ansible-playbook$ ansible-playbook web.yml -t configure
PLAY [vmX-gY.lab.workalaya.net] ************************************************
TASK [Gathering Facts] *********************************************************
ok: [vmX-gY.lab.workalaya.net]
TASK [install index page] ******************************************************
ok: [vmX-gY.lab.workalaya.net]
PLAY RECAP *********************************************************************
vmX-gY.lab.workalaya.net : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
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.
(venv) vmX-gY@ansible-gY:~/ansible-playbook$ git add .
(venv) vmX-gY@ansible-gY:~/ansible-playbook$ git commit -am "added tags feature on web.yml"
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.
(venv) vmX-gY@ansible-host:~/ansible-playbook$ ansible-playbook web.yml -l vmX-gY.lab.workalaya.net
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:
(venv) vmX-gY@ansible-host:~/ansible-playbook$ ansible-playbook web.yml -l 'vm1-g1.lab.workalaya.net;vm1-g2.lab.workalaya.net'
will only run those tasks which were already defined to run on your playbook.
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:
- vmX-gY.lab.workalaya.net
become: true
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:
- vmX-gY.lab.workalaya.net
become: true
tasks:
- name: install mysql server
apt: pkg=mysql-server state=present
Run this playbook to confirm that it does what you expect.
(venv) vmX-gY@ansible-gY:~/ansible-playbook$ git add .
(venv) vmX-gY@ansible-gY:~/ansible-playbook$ git commit -am "added more plays on web.yml"
Finally, a way to make your playbook easier to maintain is to make use of host groups in the inventory.
Edit your inventory file (/home/vmX-gY/ansible-playbook/inventory/hosts
). Divide it into groups by adding group headings surrounded by square brackets, so that it looks like this:
[app_web]
vm1-gY.lab.workalaya.net ansible_ssh_user=lab ansible_become_pass=lab
vm2-gY.lab.workalaya.net ansible_ssh_user=lab ansible_become_pass=lab
vm3-gY.lab.workalaya.net ansible_ssh_user=lab ansible_become_pass=lab
[app_db]
vmX-gY.lab.workalaya.net ansible_ssh_user=lab ansible_become_pass=lab
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.
(venv) vmX-gY@ansible-host:~/ansible-playbook$ ansible app_web -m shell -a 'ls /'
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.
We have been using git for this purpose and also created GitHub repository for remote storage.
From now on after successful ansible playbook run, please, commit changes done to git and push it to remote repository as well like
(venv) vmX-gY@ansible-gY:~/ansible-playbook$ git add .
(venv) vmX-gY@ansible-gY:~/ansible-playbook$ git commit -am "<meaningful commit message>"
(venv) vmX-gY@ansible-gY:~/ansible-playbook$ git push -u origin master