How To Use Ansible Roles
In this article, readers will use a tutorial to learn about the basics of Ansible Roles, experimenting with features, and how to use them in playbooks.
Join the DZone community and get the full member experience.
Join For FreeIn this article, you will learn the basics of Ansible Roles. With Ansible Roles, you can reuse Ansible content you create and share them with other users. You will learn about Ansible Roles step-by-step by means of examples. Enjoy!
Introduction
In the three previous Ansible posts, you learned how to setup an Ansible test environment, how to create an Ansible inventory, and how to create an Ansible playbook.
To be reminded, here are the three articles:
- How To Setup an Ansible Test Environment
- How To Create an Ansible Inventory
- How To Create an Ansible Playbook
This article continues this series, but it is not necessary to read the first three posts. In this article, you will learn how to use Ansible Roles. Ansible Roles will allow you to structure your Ansible content in such a way that it can be easily reused. This way, you can share your Ansible content with other users. In addition to this article, it is advised to read the official Ansible documentation roles section.
In case you did not read the previous articles, or just as a reminder, the environment consists out of one “Controller” and two “Target machines.” The “Controller” and “Target machines” run in a VirtualBox VM. Development of the Ansible scripts is done with IntelliJ on the host machine. The files are synchronized from the host machine to the “Controller” by means of a script. In this article, the machines have the following IP addresses:
- Controller: 192.168.2.11
- Target 1: 192.168.2.12
- Target 2: 192.168.2.13
The files being used in this article are available in the corresponding git repository at GitHub.
Prerequisites
The following prerequisites apply to this article:
- You need an Ansible test environment, see How To Setup an Ansible Test Environment.
- You need to have basic Ansible knowledge (read the previous articles in this series if you do not have this knowledge).
- If you use your own environment, you should know that Ubuntu 22.04 LTS is used for the “Controller” and “Target machines” and Ansible version 2.13.3.
- Basic Linux knowledge.
Starting Point
The starting point will be a playbook, which was created in a previous article. The playbook installs Apache webserver onto both target machines and changes the index page with a welcome message:
- name: Install Apache webserver for target 1
hosts: target1
become: yes
tasks:
- name: Install apache httpd (state=present is optional)
ansible.builtin.apt:
name: apache2
update_cache: yes
state: present
- name: Create index page for target 1
ansible.builtin.copy:
content: 'Hello world from target 1'
dest: /var/www/html/index.html
- name: Install Apache webserver for target2
hosts: target2
become: yes
tasks:
- name: Install apache httpd (state=present is optional)
ansible.builtin.apt:
name: apache2
update_cache: yes
state: present
- name: Create index page for target 2
ansible.builtin.copy:
content: 'Hello world from target 2'
dest: /var/www/html/index.html
As you can see, this playbook contains quite some duplications. This is something that can be solved by using roles.
Role With Fixed Message
In this section, you will restructure the playbook to make use of roles.
Create, in the root of the repository, a roles
directory. Inside this roles
directory, you create another directory with the name for the role. In this case, you will name it webserverfixed
. Inside the webserverfixed
directory, you create a tasks
directory. The directory structure is the following:
roles
└── webserverfixed
└── tasks
Inside the tasks
directory, you create a main.yml
file containing the tasks as used in the playbook. The only difference is that the welcome message contains a fixed message instead of a specific one for each target. You will fix this in the next section:
- name: Install apache httpd (state=present is optional)
ansible.builtin.apt:
name: apache2
update_cache: yes
state: present
- name: Create index page for target
ansible.builtin.copy:
content: 'Hello world from target'
dest: /var/www/html/index.html
The next step is to create a playbook playbook-roles-fixed-hello-message.yml
and use the role:
- name: Install Apache webserver for all targets
hosts: targets
become: yes
roles:
- webserverfixed
Start your VM environment (if not already done), transfer the repository to the controller by means of the transferdata.sh
script, and execute the playbook from the controller. The “BECOME” password is “osboxes.org” and the “Vault” password is “itisniceweather:”
$ ansible-playbook playbook-roles-fixed-hello-message.yml -i inventory/inventory.ini --ask-vault-pass --ask-become-pass
BECOME password:
Vault password:
PLAY [Install Apache webserver for all targets] **************************************************************************************
TASK [Gathering Facts] ***************************************************************************************************************
ok: [target2]
ok: [target1]
TASK [webserverfixed : Install apache httpd (state=present is optional)] ************************************************************
ok: [target2]
ok: [target1]
TASK [webserverfixed : Create index page for target] *********************************************************************************
changed: [target2]
changed: [target1]
PLAY RECAP ***************************************************************************************************************************
target1 : ok=3 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
target2 : ok=3 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
As you can see in the above output, the index pages are changed. Verify on your host machine whether the welcome messages are correct:
$ curl http://192.168.2.12
Hello world from target
$ curl http://192.168.2.13
Hello world from target
Both target machines return the same welcome message, as expected.
Role With Variable Message
The above playbook works well, but this wasn’t exactly what was defined in the original playbook. The original playbook contained a welcome message specific for each target. So, let’s add this functionality. First of all, copy the webserverfixed
directory and paste it as webservervariable
. This gives you the following directory structure:
roles
├── webserverfixed
│ └── tasks
│ └── main.yml
└── webservervariable
└── tasks
└── main.yml
You will need a variable to create a specific message for each target. Create a directory defaults
inside directory webservervariable
and add a main.yml
file with the following contents:
hello_message: target
Change in tasks/main.yml
the welcome message so it uses the variable. Change the following part:
- name: Create index page for target
ansible.builtin.copy:
content: 'Hello world from target'
dest: /var/www/html/index.html
into:
- name: Create index page for target
ansible.builtin.copy:
content: 'Hello world from {{ hello_message }}'
dest: /var/www/html/index.html
Create playbook playbook-roles-variable-hello-message-default.yml
with the same contents as the playbook of the previous section. The only thing that changes is the role name. Change it into webservervariable
:
- name: Install Apache webserver for all targets
hosts: targets
become: yes
roles:
- webservervariable
Next, run the playbook.
Notice: in the output that nothing has changed for the index page:
$ ansible-playbook playbook-roles-variable-hello-message-default.yml -i inventory/inventory.ini --ask-vault-pass --ask-become-pass
BECOME password:
Vault password:
PLAY [Install Apache webserver for all targets] **************************************************************************************************************
TASK [Gathering Facts] ***************************************************************************************************************************************
ok: [target2]
ok: [target1]
TASK [webservervariable : Install apache httpd (state=present is optional)] *********************************************************************************
ok: [target2]
ok: [target1]
TASK [webservervariable : Create index page for target] ******************************************************************************************************
ok: [target2]
ok: [target1]
PLAY RECAP ***************************************************************************************************************************************************
target1 : ok=3 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
target2 : ok=3 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
When you check the contents by means of curl
, you will see no difference with the previous section. This is as expected, you only changed the role so it uses a variable, but the variable defaults to the value target.
Create a playbook playbook-roles-variable-hello-message.yml
and set the hello_message
variable to respectively target1
and target2
dependent on the host:
- name: Install Apache webserver for target1
hosts: target1
become: yes
roles:
- role: webservervariable
vars:
hello_message: 'target1'
- name: Install Apache webserver for target2
hosts: target2
become: yes
roles:
- role: webservervariable
vars:
hello_message: 'target2'
Run the playbook. As can be seen from the output, the index pages for target1
and target2
are changed:
$ ansible-playbook playbook-roles-variable-hello-message.yml -i inventory/inventory.ini --ask-vault-pass --ask-become-pass
BECOME password:
Vault password:
PLAY [Install Apache webserver for target1] ***********************************************************************************************************
TASK [Gathering Facts] ********************************************************************************************************************************
ok: [target1]
TASK [webservervariable : Install apache httpd (state=present is optional)] **************************************************************************
ok: [target1]
TASK [webservervariable : Create index page for target] ***********************************************************************************************
changed: [target1]
PLAY [Install Apache webserver for target2] ***********************************************************************************************************
TASK [Gathering Facts] ********************************************************************************************************************************
ok: [target2]
TASK [webservervariable : Install apache httpd (state=present is optional)] **************************************************************************
ok: [target2]
TASK [webservervariable : Create index page for target] ***********************************************************************************************
changed: [target2]
PLAY RECAP ********************************************************************************************************************************************
target1 : ok=3 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
target2 : ok=3 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Verify the index page by means of curl
and you can see that the correct responses are returned:
$ curl http://192.168.2.12
Hello world from target1
$ curl http://192.168.2.13
Hello world from target2
Using Roles
The Ansible documentation states that you can use roles in three different ways:
- At the play level with the
roles
option. - At the tasks level with
include_role
. - At the tasks level with
import_role
.
Let’s experiment with this in the next sections:
Using Roles at the Play Level
This is what you have used up to now. To see what happens when other tasks are added to the playbook, you add one extra print task.
Create playbook playbook-roles-at-play-level.yml
with the role webservervariable
and the one extra task:
- name: Install Apache webserver for target1
hosts: target1
become: yes
roles:
- role: webservervariable
vars:
hello_message: 'target1'
tasks:
- name: Print a message
ansible.builtin.debug:
msg: "this task runs after the webservervariable role"
Run the playbook and, as you can see in the output, the tasks of the webservervariable
role are executed first and, after that, the tasks of the playbook are executed:
$ ansible-playbook playbook-roles-at-play-level.yml -i inventory/inventory.ini --ask-vault-pass --ask-become-pass
BECOME password:
Vault password:
PLAY [Install Apache webserver for target1] ***********************************************************************************************************
TASK [Gathering Facts] ********************************************************************************************************************************
ok: [target1]
TASK [webservervariable : Install apache httpd (state=present is optional)] **************************************************************************
ok: [target1]
TASK [webservervariable : Create index page for target] ***********************************************************************************************
ok: [target1]
TASK [Print a message] ********************************************************************************************************************************
ok: [target1] => {
"msg": "this task runs after the webservervariable role"
}
PLAY RECAP ********************************************************************************************************************************************
target1 : ok=4 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Including Roles: Dynamic Reuse
With dynamic reuse, you can include a role in between other tasks. The order of the tasks are preserved.
Create a playbook playbook-roles-dynamic-reuse.yml
. Add a task before the webservervariable
role and add a task after the webservervariable
role. The webservervariable
role is included in the task by means of include_role
. Also note that the variables for the role need to be added at the task level:
- name: Install Apache webserver for target1
hosts: target1
become: yes
tasks:
- name: Print a message
ansible.builtin.debug:
msg: "this task runs before the webservervariable role"
- name: Install Apache webserver for target1
include_role:
name: webservervariable
vars:
hello_message: 'target1'
- name: Print a message
ansible.builtin.debug:
msg: "this task runs after the webservervariable role"
Run the playbook and, as you can see in the output, the order of the tasks are preserved:
$ ansible-playbook playbook-roles-dynamic-reuse.yml -i inventory/inventory.ini --ask-vault-pass --ask-become-pass
BECOME password:
Vault password:
PLAY [Install Apache webserver for target1] ***********************************************************************************************************
TASK [Gathering Facts] ********************************************************************************************************************************
ok: [target1]
TASK [Print a message] ********************************************************************************************************************************
ok: [target1] => {
"msg": "this task runs before the webservervariable role"
}
TASK [Install Apache webserver for target1] ***********************************************************************************************************
TASK [webservervariable : Install apache httpd (state=present is optional)] **************************************************************************
ok: [target1]
TASK [webservervariable : Create index page for target] ***********************************************************************************************
ok: [target1]
TASK [Print a message] ********************************************************************************************************************************
ok: [target1] => {
"msg": "this task runs after the webservervariable role"
}
PLAY RECAP ********************************************************************************************************************************************
target1 : ok=5 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Importing Roles: Static Reuse
With static reuse, you can include a role in between other tasks. The order of the tasks are preserved.
Create a playbook playbook-roles-static-reuse.yml
. The contents are a copy of the dynamic reuse playbook where you alter include_role
into import_role
:
- name: Install Apache webserver for target1
hosts: target1
become: yes
tasks:
- name: Print a message
ansible.builtin.debug:
msg: "this task runs before the webservervariable role"
- name: Install Apache webserver for target1
import_role:
name: webservervariable
vars:
hello_message: 'target1'
- name: Print a message
ansible.builtin.debug:
msg: "this task runs after the webservervariable role"
Run the playbook and, as you can see in the output, the order of the tasks are preserved:
$ ansible-playbook playbook-roles-static-reuse.yml -i inventory/inventory.ini --ask-vault-pass --ask-become-pass
BECOME password:
Vault password:
PLAY [Install Apache webserver for target1] ***********************************************************************************************************
TASK [Gathering Facts] ********************************************************************************************************************************
ok: [target1]
TASK [Print a message] ********************************************************************************************************************************
ok: [target1] => {
"msg": "this task runs before the webservervariable role"
}
TASK [webservervariable : Install apache httpd (state=present is optional)] **************************************************************************
ok: [target1]
TASK [webservervariable : Create index page for target] ***********************************************************************************************
ok: [target1]
TASK [Print a message] ********************************************************************************************************************************
ok: [target1] => {
"msg": "this task runs after the webservervariable role"
}
PLAY RECAP ********************************************************************************************************************************************
target1 : ok=5 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Difference Between Dynamic and Static Reuse
So, you are probably wondering what the difference is between dynamic reuse and static reuse as the output is exactly the same.
One major difference is that dynamic reuse reports errors in the tasks during the execution of the role. As with static reuse, errors in the tasks will be reported at the start of the playbook.
Create a playbook playbook-roles-dynamic-reuse-with-error.yml
with similar contents as playbook-roles-dynamic-reuse.yml
. However, you use the role webserver
, which does not exist instead of role webservervariable
.
Run the playbook and, as you can see in the output, the first task is executed and the playbook fails when trying to execute the tasks from the non-existing role:
$ ansible-playbook playbook-roles-dynamic-reuse-with-error.yml -i inventory/inventory.ini --ask-vault-pass --ask-become-pass
BECOME password:
Vault password:
PLAY [Install Apache webserver for target1] ***********************************************************************************************************
TASK [Gathering Facts] ********************************************************************************************************************************
ok: [target1]
TASK [Print a message] ********************************************************************************************************************************
ok: [target1] => {
"msg": "this task runs before the webservervariable role"
}
TASK [Install Apache webserver for target1] ***********************************************************************************************************
ERROR! the role 'webserver' was not found in /home/osboxes/MyAnsiblePlanet/roles:/home/osboxes/.ansible/roles:/usr/share/ansible/roles:/etc/ansible/roles:/home/osboxes/MyAnsiblePlanet
The error appears to be in '/home/osboxes/MyAnsiblePlanet/playbook-roles-dynamic-reuse-with-error.yml': line 12, column 15, but may
be elsewhere in the file depending on the exact syntax problem.
The offending line appears to be:
include_role:
name: webserver
^ here
PLAY RECAP ********************************************************************************************************************************************
target1 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Create a playbook playbook-roles-static-reuse-with-error.yml
with similar contents as playbook-roles-static-reuse.yml
. However, you use the role webserver
, which does not exist instead of role webservervariable
.
Run the playbook and, as you can see in the output, the playbook fails immediately:
$ ansible-playbook playbook-roles-static-reuse-with-error.yml -i inventory/inventory.ini --ask-vault-pass --ask-become-pass
BECOME password:
Vault password:
ERROR! the role 'webserver' was not found in /home/osboxes/MyAnsiblePlanet/roles:/home/osboxes/.ansible/roles:/usr/share/ansible/roles:/etc/ansible/roles:/home/osboxes/MyAnsiblePlanet
The error appears to be in '/home/osboxes/MyAnsiblePlanet/playbook-roles-static-reuse-with-error.yml': line 12, column 15, but may
be elsewhere in the file depending on the exact syntax problem.
The offending line appears to be:
import_role:
name: webserver
^ here
Conclusion
In this article, you learned the basics of Ansible Roles, you experimented with some features and, hopefully, you will be able to use them in your playbooks. Ansible Roles is a nice way of reusing and sharing tasks.
Published at DZone with permission of Gunter Rotsaert, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments