Post

Ansible Playbooks pt 2: A Playbook for our Web Servers (90 Days of DevOps)

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 hosts keyword, 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 become keyword, 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 become keyword is available here.

vars

1
2
3
4
  vars:
    http_port: 8000
    https_port: 4443
    html_welcome_msg: "Hello 90DaysOfDevOps"
  • With the vars keyword, 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 apt module (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 with
      • state - this tells Ansible what state we want these packages to end up in after the task has completed. A state of latest means that we want the newest available version of the package to be installed.

“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 the ansible.builtin.template module. 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 name this task. This one ensures that the Apache service is running.
  • service: - this line calls the ansible.builtin.service module, which Ansible provides for managing services on targeted nodes.
    • Below that line, the module parameters name and state tell Ansible that we want the apache2 service to be started (if it isn’t already).

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:

screenshot of web browser showing the contents of http://web01:8000; the page reads "Hello 90DaysOfDevOps".

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

This post is licensed under CC BY-NC-SA 4.0 by the author.