Hello everyone.
We are slowly approaching the end of the "Ansible 101", the basics of Ansible. However, we still have one big topic to cover: Roles.
So far, we have been writing and running our Ansible code in playbooks. Now, if we want to write Ansible code that we want to use across projects - or even publish, for example - we quickly reach the limits of playbooks. This is where Ansible roles come in. They allow us to create reusable and parameterizable Ansible content. We can then integrate one - or more - playbooks into a role.
The structure of an Ansible role is represented by a directory structure. A typical role looks like this:
# playbooks
webservers.yml
roles/
my-webserver-role/
tasks/
- main.yml
handlers/
- main.yml
files/
templates/
vars/
- main.yml
defaults/
- main.yml
meta/
- main.yml
In this example we see a role called "my-webserver-role". Let me briefly explain each subdirectory:
- tasks - Here are files where we define our tasks
- handlers - Here are handlers defined if our role needs them
- files - This is where we put files we want to use with the copy module, for example
- templates - If we need Jinja2 templates, we put them here
- vars - This is where we store role-internal variable definitions
- defaults - Here we can define default values for variables, which can be overridden by the "role user"
- mta - This is where we store meta information about the role itself
An example of a more detailed web server role can be found here:
https://github.com/dermow/ansible-role-httpd
For our guide, however, I want to make the example a little simpler. Let's define the following tasks for the role:
Support limited to Ubuntu
Install the Apache web server
Providing a custom index.html
Starting and enabling the Apache service
First, it is important to note that by default, Ansible searches for roles in defined locations. These include "./roles" in your project directory. Let's start by creating the directory structure:
mkdir ~/ansible-guide-roles
cd ~/ansible-guide-roles
mkdir -p ./roles/my-webserver-role
cd ./roles/my-webserver-role
mkdir tasks templates handlers vars defaults
What's missing is an inventory - I'll use our test environment again. Of course, being the smart guy that I am, I took a snapshot beforehand and didn't have to reinstall it (chrm chrm).
The next step is to create a file for our variable definitions:
We have defined a list in which we specify which packages we want to install later. The next step is to create the corresponding task:
Now we have a first version of the role that we can test. To do this, we will create a playbook with the following code:
Lets start the playbook:
ansible-playbook -i inventory.txt playbook.yml
PLAY [webservers] ********************************************************************************************************************************************************
TASK [Gathering Facts] ********************************************************************************************************************************************************
ok: [ansible-guide-1]
ok: [ansible-guide-2]
TASK [my-webserver-role : install packages] ********************************************************************************************************************************************************
changed: [ansible-guide-1] => (item=apache2)
changed: [ansible-guide-1] => (item=libapache2-mod-php)
changed: [ansible-guide-2] => (item=apache2)
changed: [ansible-guide-2] => (item=libapache2-mod-php)
PLAY RECAP ********************************************************************************************************************************************************
ansible-guide-1 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
ansible-guide-2 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Great, it worked: The Apache web server and the PHP module should now be installed on both target machines. The next step is to make sure that the web server service is started and running:
Now we want to ship our own config with the role. To do this, I simply stole the "default.conf" from the default installation and removed the comments:
cat /etc/apache2/sites-available/000-default.conf | grep -v "#"
<VirtualHost *:80>
ServerAdmin webmaster@localhost
DocumentRoot /var/www/html
ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined
</VirtualHost>
Let's say we only want the "ServerAdmin" to be configurable. So we create the appropriate template:
<VirtualHost *:80>
ServerAdmin {{ my_server_admin }}
DocumentRoot /var/www/html
ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined
</VirtualHost>
Now we just have to define the variable "my_server_admin". Since this should be overridden by the "user", it makes sense to do this under "defaults":
Now all you need are the tasks to deploy the template:
---
- name: install packages
become: true
ansible.builtin.apt:
name: "{{ item }}"
state: present
loop: "{{ packages_to_install }}"
- name: start and enable apache
become: true
ansible.builtin.systemd:
name: apache2
state: started
enabled: true
- name: template web config
become: true
ansible.builtin.template:
src: 000-default.conf
dest: /etc/apache2/sites-available/000-default.conf
Now, of course, it would be great if the web server could be restarted automatically when changes are made to the configuration. We already discussed a suitable tool for this in Part 4: Handler.
So we create a handler...
... And adapt our mission accordingly:
And go:
ansible-playbook -i inventory.txt playbook.yml
PLAY [webservers] ********************************************************************************************************************************************************
TASK [Gathering Facts] ********************************************************************************************************************************************************
ok: [ansible-guide-1]
ok: [ansible-guide-2]
TASK [my-webserver-role : install packages] ********************************************************************************************************************************************************
ok: [ansible-guide-1] => (item=apache2)
ok: [ansible-guide-1] => (item=libapache2-mod-php)
ok: [ansible-guide-2] => (item=apache2)
ok: [ansible-guide-2] => (item=libapache2-mod-php)
TASK [my-webserver-role : start and enable apache] ********************************************************************************************************************************************************
ok: [ansible-guide-1]
ok: [ansible-guide-2]
TASK [my-webserver-role : template web config] ********************************************************************************************************************************************************
changed: [ansible-guide-1]
changed: [ansible-guide-2]
RUNNING HANDLER [my-webserver-role : restart-apache] ********************************************************************************************************************************************************
changed: [ansible-guide-1]
changed: [ansible-guide-2]
PLAY RECAP ********************************************************************************************************************************************************
ansible-guide-1 : ok=5 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
ansible-guide-2 : ok=5 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
That looks good :) All that remains is the last part of our to-do list: Provide our own index.html. With what we've learned so far, this shouldn't be a problem: we simply create another template:
<html>
<head>
<title>{{ my_website_title }}</title>
</head>
<body>
{{ my_website_content }}
</body>
</html>
So we created another template in which we want to use two new variables. We also create default values for them:
Finally, the task to deliver the index.html:
But let's say we want to use our own value for our title instead of the default from the role:
ansible-playbook -i inventory.txt -e "my_website_title='My fancy title'" playbook.yml
PLAY [webservers] ********************************************************************************************************************************************************
TASK [Gathering Facts] ********************************************************************************************************************************************************
ok: [ansible-guide-1]
ok: [ansible-guide-2]
TASK [my-webserver-role : install packages] ********************************************************************************************************************************************************
ok: [ansible-guide-1] => (item=apache2)
ok: [ansible-guide-1] => (item=libapache2-mod-php)
ok: [ansible-guide-2] => (item=apache2)
ok: [ansible-guide-2] => (item=libapache2-mod-php)
TASK [my-webserver-role : start and enable apache] ********************************************************************************************************************************************************
ok: [ansible-guide-1]
ok: [ansible-guide-2]
TASK [my-webserver-role : template web config] ********************************************************************************************************************************************************
changed: [ansible-guide-1]
changed: [ansible-guide-2]
TASK [my-webserver-role : template index.html] ********************************************************************************************************************************************************
changed: [ansible-guide-1]
changed: [ansible-guide-2]
RUNNING HANDLER [my-webserver-role : restart-apache] ********************************************************************************************************************************************************
changed: [ansible-guide-1]
changed: [ansible-guide-2]
PLAY RECAP ********************************************************************************************************************************************************
ansible-guide-1 : ok=5 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
ansible-guide-2 : ok=5 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Done! Now a quick test to see if it all worked:
curl 192.168.0.11
<html>
<head>
<title>My fancy title</title>
</head>
<body>
Hello World
</body>
</html>
Note: In a real scenario, you should define the variables you want to override in your groupvars, hostvars, or in the playbook using the role instead of setting them in the ansible command.
Pro tip
Ready made rolls are available for many applications. Take a look here:
THE END
This concludes the 101 Guide. You should now be able to continue learning on your own and just work with it in your own environment. I'm not sure yet if I'll start an "Advanced Guide" in the same style, or if I'll just cover certain topics as they come up.
Of course I would be very happy to get some feedback. See you soon
Mow