In lab three, we will focus on learning how to create and structure Ansible Playbooks. After completing this section, you should be able to write basic Ansible playbooks and use the ansible-playbook command. This information will be handy to interact programmatically against our OpenStack instances in later labs.
In Lab 2 you learned how to run ad-hoc commands with Ansible. Ad-hoc commands can run a single, simple task. The real power of Ansible is in how to use playbooks to run multiple, complex tasks against a system or a group of systems in an easily repeatable way.
There are two central concepts to understand before writing playbooks:
-
A play: is an ordered set of tasks which should be run against hosts selected from your inventory.
-
A playbook: is a text file that contains a list of one or more plays to run in order
To better understand the structure of a playbook, let us first review a simple ad-hoc command:
$ ansible web,db -m command -a "hostname"
fda4c4a2-f655-49dc-997f-29d555d15d49 | CHANGED | rc=0 >>
web1.novalocal
3c0bc0ea-ce96-4ca5-ab89-442434c34088 | CHANGED | rc=0 >>
web0.novalocal
8572e2c5-36d2-489a-a71f-7ff911958a7b| CHANGED | rc=0 >>
db0.novalocalThe previous command should report the hostname of the system or systems used as target.
This can be rewritten as a simple single-task play and saved in a playbook. The resulting playbook might appear as follows:
---
- name: Execute raw command to get hostname from system
hosts: web, db
tasks:
- name: Hostname command
command: hostname
...
A playbook is a text file written in YAML format, and is normally saved with the extension yml. The playbook primarily uses indentation with space characters to indicate the structure of its data. YAML doesn’t place strict requirements on how many spaces are used for the indentation, but there are two basic rules:
-
Data elements at the same level in the hierarchy (such as items in the same list) must have the same indentation.
-
Items that are children of another item must be indented more than their parents. You can also add blank lines for readability.
The playbook begins with a line consisting of three dashes (---) as a start of document marker and it is a good practice to end each playbook with three dots (…) as an end of document marker.
In between those markers, the playbook is defined as a list of plays. An item in a YAML list starts with a single dash (-) followed by a space. For example, a YAML list might appear as follows:
- nova
- glance
- neutron
- cinder
- keystone
- manilaThe play itself is a collection (an associative array or hash/dictionary) of key: value pairs. Keys in the same play should have the same indentation. The following example describes a YAML hash/dictionary with three keys. The first two keys have simple values. The third has a list of three items as a value.
---
- name: Example
hosts: Fileservers
tasks:
- one
- two
- three
...
In the previous example, the play has three keys: name, hosts, and tasks. These keys all have the same indentation because they belong to the play. The first line of the example play starts with a dash and a space (indicating the play is the first item of a list), and then the first key, the name attribute.
|
Note
|
The order in which the plays and tasks are listed in a playbook is important, because Ansible runs them in the same order. |
Now that we know the structure of a playbook and how easy it is to write one, let’s discuss
the ansible-playbook command. The ansible-playbook command is used to run playbooks
and is executed on the control node. Using the ansible-playbook command run the
single-task-playbook.yml from the previous example.
$ ansible-playbook single-task-playbook.yml
PLAY [Execute raw command to get hostname from system] ************************************************************************************
TASK [Gathering Facts] ********************************************************************************************************************
ok: [fda4c4a2-f655-49dc-997f-29d555d15d49]
ok: [3c0bc0ea-ce96-4ca5-ab89-442434c34088]
ok: [8572e2c5-36d2-489a-a71f-7ff911958a7b]
TASK [Hostname command] *******************************************************************************************************************
changed: [fda4c4a2-f655-49dc-997f-29d555d15d49]
changed: [3c0bc0ea-ce96-4ca5-ab89-442434c34088]
changed: [8572e2c5-36d2-489a-a71f-7ff911958a7b]
PLAY RECAP ********************************************************************************************************************************
3c0bc0ea-ce96-4ca5-ab89-442434c34088 : ok=2 changed=1 unreachable=0 failed=0
8572e2c5-36d2-489a-a71f-7ff911958a7b : ok=2 changed=1 unreachable=0 failed=0
fda4c4a2-f655-49dc-997f-29d555d15d49 : ok=2 changed=1 unreachable=0 failed=0After the playbook is executed, output is generated to show the play and tasks.
In general, tasks in Ansible playbooks are idempotent, and it is safe to run the playbook multiple times. If the targeted managed hosts are already in the correct state, no changes should be made.
Before executing a playbook, it is best practice to perform a verification to ensure
that the syntax of its contents is correct. The ansible-playbook command offers a
--syntax-check option which can be used to verify the syntax of a playbook file.
It is essential to notice that this will only check the syntax and not if the code
is correct for the intended use. The following example shows the successful
syntax verification of the single-task-playbook.yml playbook.
$ ansible-playbook --syntax-check single-task-playbook.ymlWhen syntax verification fails, a syntax error is reported. The following example shows the failed syntax verification of a playbook:
[ansible@ansiblehost ~]$ ansible-playbook --syntax-check single-task-playbook.yml
ERROR! Syntax Error while loading YAML.
The error appears to have been in '/data/ansible/single-task-playbook.yml': line 8, column 2, but
may be elsewhere in the file depending on the exact syntax problem.Another best practice while executing playbooks is to perform a dry run. Dry runs
are invoked with the -C option, which runs Ansible to report the changes that would
have occurred if the playbook was executed, but without making actual changes to the managed hosts.
$ ansible-playbook -C single-task-playbook.ymlAt this point, we know how to write and execute basic playbooks. In this guided
exercise we will create a simple Ansible playbook that takes advantage of the
command module to write an openstack command that will create a backup of the
existing dbvol that resides within Cinder.
Create the initial portion of our playbook that defines the name, target and the task to create the backup:
---
- name: Backing up the database via the Cinder service
hosts: localhost
tasks:
- name: Create a Cinder Backup of Database Volume
command: "openstack volume backup create --force --name dbvol_backup dbvol"
...
Verify the syntax of the playbook via:
$ ansible-playbook --syntax-check cinder_backup.yml
Execute the playbook via the following command:
$ ansible-playbook cinder_backup.yml
The dbvol_backup volume can be verified using the following OpenStack command:
$ openstack volume backup list
For advanced users, the verification process can be included within the playbook
itself. However, this requires knowledge of certain topics yet to be discussed
such as using the register variable and debug module. For completeness, we’ve
included an additional task that verifies if the dbvol_backup exists.
---
- name: Backing up the database via the Cinder service
hosts: localhost
tasks:
- name: Create a Cinder Backup of Database Volume
command: "openstack volume backup create --force --name dbvol_backup dbvol"
## wait for backup to complete
- name: Run the openstack volume backup list command
shell: "sleep 45 && openstack volume backup list"
register: output
- debug: var=output.stdout_lines
...
Prior to re-running this updated playbook, for simplicity, we will delete the
existing dbvol_backup manually. The steps are as follows:
$ openstack volume backup delete dbvol_backup
|
Note
|
If you notice, the existing Ansible playbook is not very idempotent. By the end of all these lab exercises, you will have the knowledge and skills necessary to make the required changes. |
Re-execute the playbook via the following command:
$ ansible-playbook cinder_backup.yml
PLAY [Backing up the database via the Cinder service] ************************************************************************************
TASK [Gathering Facts] *******************************************************************************************************************
ok: [localhost]
TASK [Create a Cinder Backup of Database Volume] *****************************************************************************************
changed: [localhost]
TASK [Wait for backup to complete and then run the openstack volume backup list command] *************************************************
changed: [localhost]
TASK [debug] *****************************************************************************************************************************
ok: [localhost] => {
"output.stdout_lines": [
"+--------------------------------------+--------------+-------------+-----------+------+",
"| ID | Name | Description | Status | Size |",
"+--------------------------------------+--------------+-------------+-----------+------+",
"| ea8f0821-a41a-4c43-bd20-2e7b08b9b972 | dbvol_backup | None | available | 10 |",
"+--------------------------------------+--------------+-------------+-----------+------+"
]
}
PLAY RECAP *******************************************************************************************************************************
localhost : ok=4 changed=2 unreachable=0 failed=0 skipped=0 : ok=4 changed=2 unreachable=0 failed=0 skipped=0
In the case of MongoDB, there is no need to perform any tasks on the database itself prior to the backup. However in the case of Oracle or other vendors, the database may need to be placed in a special mode to achieve a consistent state.
For illustrative purposes, the backup playbook can be modified to include tasks to stop and start the database first. Those tasks can be replaced with something relevant to your specific database platform.
---
- name: Backing up the database via the Cinder service
hosts: db
tasks:
- name: Stop the database
systemd:
name: mongod
state: stopped
become: true
- name: Create a Cinder Backup of Database Volume
command: "openstack volume backup create --force --name dbvol_backup dbvol"
delegate_to: localhost
- name: Wait for backup to complete and then run the openstack volume backup list command
shell: "sleep 45 && openstack volume backup list"
register: output
delegate_to: localhost
- name: Start the database
systemd:
name: mongod
state: started
become: true
- debug: var=output.stdout_lines
...
Note the the hosts field has changed, since we now must interact with the instance itself, as well as perform local OpenStack commands. The delegate_to keyword is used to assign the specific task to be run on a specific server. In this particular case, we want our OVH instance to handle any OpenStack commands and we do this by delegating it to our localhost which is also our control node.
Prior to re-running this updated playbook, for simplicity, we will delete the
existing dbvol_backup manually. The steps are as follows:
$ openstack volume backup delete dbvol_backup
|
Note
|
If you notice, the existing Ansible playbook is not very idempotent. By the end of all these lab exercises, you will have the knowledge and skills necessary to make the required changes. |
Run the playbook one final time:
$ ansible-playbook cinder_backup.yml
PLAY [Backing up the database via the Cinder service] ********************************************************************************************************
TASK [Gathering Facts] ***************************************************************************************************************************************
ok: [6c4cdbf2-bbe2-4ee9-8dfa-1eaf1a776c7f]
TASK [Stop the database] *************************************************************************************************************************************
changed: [6c4cdbf2-bbe2-4ee9-8dfa-1eaf1a776c7f]
TASK [Create a Cinder Backup of Database Volume] *************************************************************************************************************
changed: [6c4cdbf2-bbe2-4ee9-8dfa-1eaf1a776c7f -> localhost]
TASK [Wait for backup to complete and then run the openstack volume backup list command] *********************************************************************
changed: [6c4cdbf2-bbe2-4ee9-8dfa-1eaf1a776c7f -> localhost]
TASK [Start the database] ************************************************************************************************************************************
changed: [6c4cdbf2-bbe2-4ee9-8dfa-1eaf1a776c7f]
TASK [debug] *************************************************************************************************************************************************
ok: [6c4cdbf2-bbe2-4ee9-8dfa-1eaf1a776c7f] => {
"output.stdout_lines": [
"+--------------------------------------+--------------+-------------+-----------+------+",
"| ID | Name | Description | Status | Size |",
"+--------------------------------------+--------------+-------------+-----------+------+",
"| 5cc8e54d-0f2b-4538-bb4f-078054451ca6 | dbvol_backup | None | available | 1 |",
"+--------------------------------------+--------------+-------------+-----------+------+"
]
}
PLAY RECAP ***************************************************************************************************************************************************
6c4cdbf2-bbe2-4ee9-8dfa-1eaf1a776c7f : ok=6 changed=4 unreachable=0 failed=0
Now that we have a working database and it has been properly backed up, we can access this database information via http://localhost:1234 in our browser. Below is a image of what you should be seeing once you access the link.
|
Note
|
If you can not access the website, ensure you set up SSH forwarding as outlined in the overview lab: |
$ ssh -XL 1234:<LOAD BALANCER IP>:80 stack@<OVH IP>
The loadbalancer IP can be found out via the command:
$ openstack loadbalancer list +--------------------------------------+-------+----------------------------------+-------------+---------------------+----------+ | id | name | project_id | vip_address | provisioning_status | provider | +--------------------------------------+-------+----------------------------------+-------------+---------------------+----------+ | cc5b4311-6181-43e7-8051-0e705aa8c321 | weblb | b0796a9f0938466b9e9771c01d5bd2ba | 172.24.4.27 | ACTIVE | amphora | +--------------------------------------+-------+----------------------------------+-------------+---------------------+----------+
In the next lab, we will be restoring the existing database back to what you are currently seeing on our existing browser. In order to do this, we need to break the existing database.
We have added a simple blinking link labeled "DON’T CLICK ME" that breaks the current database. For this simple exercise, click on the link to break the database and in the following sections we will create an Ansible playbook that restores the database back to it’s original state of when the backup was taken.
|
Note
|
When accessing via browser for the first time, it may take some time to initially load, please be patient. |
