In this blog, 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!

1. Introduction

In the three previous Ansible posts, you learned how to setup an Ansible test environmenthow to create an Ansible inventory and how to create an Ansible playbook. This post continues this series, but it is not necessary to read the first three posts. In this post, 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 blog, it is advised to read the official Ansible documentation roles section.

In case you did not read the previous blogs 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 blog, 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 blog are available in the corresponding git repository at GitHub.

2. Prerequisites

The following prerequisites apply to this blog:

  • You need an Ansible test environment, see a previous blog how to set up a test environment;
  • You need to have basic Ansible knowledge, read the previous blogs 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.

3. Starting Point

The starting point will be a playbook which was created in a previous blog. 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.

4. Role With Fixed Message

In this section, you will restructure the playbook in order 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. 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

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.

5. 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 in order that 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 what changes is the role name. Change it into webservervariable.

- name: Install Apache webserver for all targets
  hosts: targets
  become: yes
  roles:
    - webservervariable

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 in order that 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

6. 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.

6.1 Using Roles at the Play Level

This is what you have used up to now. In order 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   

6.2 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 

6.3 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 

6.4 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. There are some subtle differences however which are clearly explained in this blog.

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

7. Ansible Galaxy

As written before, a nice use case for using roles is that they can be shared with other users. Ansible Galaxy is a free website where many roles can be found. So, when creating playbooks yourself, take a look at this community supported collection of roles. You might find what you need and you will not need to invent things yourself.

8. Conclusion

In this blog, 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.