Ansible Playbooks pt 2: A Playbook for our Web Servers (90 Days of DevOps)
In this section of the 90 Days of DevOps series, we continue our coverage of Ansible playbooks that began in the previous post.
An Ansible playbook for our web servers
While the playbook from the previous post did not involve any managed nodes at all, the playbook in this post will work with the webservers we defined in our Ansible inventory file.
This new playbook will ensure that all of our webservers are running an up-to-date version of the Apache web server software, and are serving a simple website matching our specifications.
web_welcome.yml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
- hosts: webservers
become: yes
vars:
http_port: 8000
https_port: 4443
html_welcome_msg: "Hello 90DaysOfDevOps"
tasks:
- name: Ensure apache is at the latest version
apt:
name: apache2
state: latest
- name: Write the apache2 ports.conf config file
template:
src: templates/ports.conf.j2
dest: /etc/apache2/ports.conf
notify:
- Restart apache
- name: Write a basic index.html file
template:
src: templates/index.html.j2
dest: /var/www/html/index.html
notify:
- Restart apache
- name: Ensure apache is running
service:
name: apache2
state: started
handlers:
- name: Restart apache
service:
name: apache2
state: restarted
When we run this playbook, Ansible’s job will be to read it, connect to the web servers, and apply any changes required to those servers in order to make them match the state we’ve declared in the playbook.
Breaking down the playbook
Looking through the different parts of this playbook:
hosts
1
- hosts: webservers
- With the
hostskeyword, we specify that we want this playbook to manage our web servers. In our case, that will mean the servers named web01 and web02.
become
1
become: yes
-
With the
becomekeyword, we enable Ansible privilege escalation. This allows Ansible to perform administrative tasks on the managed nodes, e.g. installing software and starting/stopping services.The official documentation for Ansible’s
becomekeyword is available here.
vars
1
2
3
4
vars:
http_port: 8000
https_port: 4443
html_welcome_msg: "Hello 90DaysOfDevOps"
- With the
varskeyword, we specify some useful variables that can be referenced throughout our playbook.
The tasks
“Ensure apache is at the latest version”
1
2
3
4
- name: Ensure apache is at the latest version
apt:
name: apache2
state: latest
- In the first line of this task, we define its name. Ideally, a task name provides a brief summary of what the task is designed to do.
- The second line calls Ansible’s
aptmodule (short for ansible.builtin.apt).- Indented under that line, we specify two of the many parameters available within Ansible’s apt module :
name- this specifies the software package names we want Ansible to work withstate- this tells Ansible what state we want these packages to end up in after the task has completed. A state oflatestmeans that we want the newest available version of the package to be installed.
- Indented under that line, we specify two of the many parameters available within Ansible’s apt module :
“Write the apache2 ports.conf config file”
1
2
3
4
5
6
- name: Write the apache2 ports.conf config file
template:
src: templates/ports.conf.j2
dest: /etc/apache2/ports.conf
notify:
- Restart apache
- Again, we begin our task’s definition by naming it. The purpose of this task is to write a config file (
ports.conf) to our selected hosts. template:- this line calls theansible.builtin.templatemodule. Ansible provides this module for writing destination files to hosts based on our own predefined source template files.- Below that line, we provide the name of the source template and destination file to be written. The contents of the template file won’t be included in this post, but the file is available here.
notify: - this keyword means that we want to run a “handler” to perform specific actions if/when this task makes any changes to a node. In this task, we want to restart the apache service whenever Ansible modifies/etc/apache2/ports.conf. That way, any changes we’ve made to the apache service’s configuration are immediately “made live.”
“Write a basic index.html file”
1
2
3
4
5
6
- name: Write a basic index.html file
template:
src: templates/index.html.j2
dest: /var/www/html/index.html
notify:
- Restart apache
This task is structured the same as the previous one. It just writes a different file (this time, index.html ) and references a different source template file. While the previous task defines which IP addresses/TCP ports Apache will run on, this task determines the web content to be served over those ports.
- Again, a copy of the template file is provided here.
“Ensure apache is running”
1
2
3
4
- name: Ensure apache is running
service:
name: apache2
state: started
- We
namethis task. This one ensures that the Apache service is running. service:- this line calls theansible.builtin.servicemodule, which Ansible provides for managing services on targeted nodes.- Below that line, the module parameters
nameandstatetell Ansible that we want theapache2service to bestarted(if it isn’t already).
- Below that line, the module parameters
The Concept of Idempotence
Note that no matter how many times we run this playbook, the result should be the same: the specified nodes end up matching our declared configuration. This is known as idempotence, and it is an important concept in configuration management.
For example, if one of our web servers matches our declared configuration already, then Ansible won’t perform any configuration on it when we run our playbook.
On the other hand, if another of our web servers has the latest version of apache2 installed but the software is not currently running, then Ansible will start up the software. If apache2 is installed and running but not up to date, then Ansible will update the apache2 package and make sure the new version is running, and so on.
handlers
At the end of this playbook we define the handler that we previously referenced in two of our tasks (under their notify keywords).
“Restart apache”
1
2
3
4
5
handlers:
- name: Restart apache
service:
name: apache2
state: restarted
This handler does exactly as its name suggests, and will only run *if* it is called by one of our tasks performing a configuration change.
The official documentation link for Ansible handlers is available here .
Running the playbook
Now that we’ve reviewed the playbook, let’s run it:
Note: The -K option tells the command to prompt us for the sudo password.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
$ ansible-playbook web_welcome.yml --user=vagrant -K
BECOME password:
PLAY [webservers] ********************************************************************************
TASK [Gathering Facts] ***************************************************************************
ok: [web01]
ok: [web02]
TASK [Ensure apache is at the latest version] ****************************************************
changed: [web02]
changed: [web01]
TASK [Write the apache2 ports.conf config file] **************************************************
changed: [web01]
changed: [web02]
TASK [Write a basic index.html file] *************************************************************
changed: [web01]
changed: [web02]
TASK [Ensure apache is running] ******************************************************************
ok: [web01]
ok: [web02]
RUNNING HANDLER [Restart apache] *****************************************************************
changed: [web01]
changed: [web02]
PLAY RECAP ***************************************************************************************
web01 : ok=6 changed=4 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
web02 : ok=6 changed=4 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
The playbook’s output tells us that four changes were applied to each of our web servers (three of the tasks and the one handler made changes to each server).
The result of the playbook is that all of our web servers are now running the latest version of the Apache web server, and they are serving our simple welcome page over TCP port 8000.
We can verify this using a web browser, or by running curl from the command line.
For example, when testing out the the server web01 in a web browser, we get:
We could perform the same test on the other web server and see an identical website. This confirms that the playbook web_welcome.yml did what we wanted.
Next, we’ll prepare to expand this playbook so that it can also manage configurations for our database and load balancer. See you in the next post!
<< Back to 90 Days of DevOps posts
<<< Back to all posts
