As your Ansible projects grow in complexity, simply having a single playbook with many tasks can become unwieldy. Roles provide a standardized, reusable, and self-contained way to organize related Ansible content (tasks, handlers, templates, files, variables, etc.).

Roles are essentially predefined directory structures with specific subdirectories that Ansible automatically looks for. This modularity makes playbooks cleaner, easier to understand, and promotes code reuse across different projects.

Why Use Roles?

  • Organization: Keeps your tasks, handlers, variables, templates, and files logically separated.
  • Reusability: Share roles easily within your organization or with the wider Ansible community (e.g., Ansible Galaxy).
  • Maintainability: Easier to update or debug specific components of your automation.
  • Clarity: Improves readability by abstracting details into well-named roles.

Role Directory Structure

A typical Ansible role directory structure looks like this:

roles/
└── <role_name>/
    ├── tasks/
    │   └── main.yml        # Main entry point for tasks
    ├── handlers/
    │   └── main.yml        # Main entry point for handlers
    ├── vars/
    │   └── main.yml        # Main entry point for role-specific variables
    ├── defaults/
    │   └── main.yml        # Default variables (lowest precedence)
    ├── files/
    │   └── my_static_file.txt # Static files copied directly
    ├── templates/
    │   └── my_template.j2  # Jinja2 templates
    ├── meta/
    │   └── main.yml        # Role metadata (dependencies, author, etc.)
    └── README.md           # Documentation for the role
  • tasks/main.yml: This is where the core tasks for the role reside. When you include a role, the tasks defined in this file are executed.
  • handlers/main.yml: Contains handlers that can be notified by tasks within this role (or other roles/playbooks).
  • vars/main.yml: Defines variables specific to this role. These variables have a higher precedence than defaults/.
  • defaults/main.yml: Defines default variables for the role. These have the lowest precedence, meaning they can be easily overridden by any other variable source (inventory, group_vars, host_vars, playbook vars, etc.). This is useful for providing sensible defaults that users can easily customize.
  • files/: Contains static files that the role might copy to managed hosts (e.g., shell scripts, pre-built binaries). The ansible.builtin.copy module can directly reference files in this directory without needing a full path.
  • templates/: Contains Jinja2 templates that the role might deploy to managed hosts. The ansible.builtin.template module can directly reference files in this directory.
  • meta/main.yml: Provides metadata about the role, such as author, license, and (crucially) role dependencies.
  • README.md: Good practice to document what the role does.

Creating Your First Role: A “Webserver” Role

Let’s refactor our Nginx installation and configuration into a reusable webserver role.

  1. Create the Role Directory Structure:

From your main project directory, create the following structure:

mkdir -p roles/webserver/{tasks,handlers,vars,defaults,files,templates}
touch roles/webserver/tasks/main.yml
touch roles/webserver/handlers/main.yml
touch roles/webserver/vars/main.yml
touch roles/webserver/defaults/main.yml
touch roles/webserver/templates/nginx.conf.j2
  1. Populate roles/webserver/tasks/main.yml:

Move the Nginx installation and configuration tasks here.

# roles/webserver/tasks/main.yml
---
- name: Ensure Nginx package is present
  ansible.builtin.yum: # or apt for Debian/Ubuntu
    name: nginx
    state: present
    update_cache: true

- name: Deploy Nginx default configuration from template
  ansible.builtin.template:
    src: nginx.conf.j2 # No need for full path, Ansible knows to look in templates/
    dest: "{{ webserver_config_path }}"
    owner: root
    group: root
    mode: '0644'
  notify: Reload Nginx

- name: Ensure Nginx service is started and enabled
  ansible.builtin.service:
    name: nginx
    state: started
    enabled: true
  1. Populate roles/webserver/handlers/main.yml:

Move the Nginx reload handler here.

# roles/webserver/handlers/main.yml
---
- name: Reload Nginx
  ansible.builtin.service:
    name: nginx
    state: reloaded
  1. Populate roles/webserver/defaults/main.yml:

Define default values for role-specific variables. These are the lowest precedence.

# roles/webserver/defaults/main.yml
---
webserver_port: 80
webserver_document_root: /usr/share/nginx/html
webserver_config_path: /etc/nginx/conf.d/default.conf
  1. Populate roles/webserver/templates/nginx.conf.j2:

Use the variables defined in defaults/main.yml.

# roles/webserver/templates/nginx.conf.j2
server {
    listen {{ webserver_port }};
    server_name {{ ansible_fqdn }};
    root {{ webserver_document_root }};
    index index.html index.htm;

    location / {
        try_files $uri $uri/ =404;
    }
}

Using the Role in a Playbook

Now, your main playbook becomes much simpler. Instead of defining all tasks, you just “include” the role.

  1. Create playbooks/deploy_webserver.yml:
---
# playbooks/deploy_webserver.yml
- name: Deploy a web server using the webserver role
  hosts: webservers
  become: true
  roles:
    - webserver # This tells Ansible to execute the 'webserver' role
  1. Update inventory.ini:

Ensure you have a webservers group with at least one host.

# inventory.ini
[webservers]
your_webserver_ip_or_hostname

[all:vars]
ansible_user=your_ssh_user
ansible_private_key_file=~/.ssh/id_rsa

Running the Playbook with a Role

From your project root (where roles/ and playbooks/ are located):


ansible-playbook -i inventory.ini playbooks/deploy_webserver.yml

Ansible will automatically find the webserver role within the roles/ directory.

Overriding Role Variables

The beauty of defaults/main.yml is that its variables can be easily overridden.

  1. Overriding via group_vars (Recommended):

Create group_vars/webservers.yml and define variables there:

# group_vars/webservers.yml
webserver_port: 8080
webserver_document_root: /var/www/my_custom_site

Now, when you run the deploy_webserver.yml playbook, the Nginx configuration will use port 8080 and the custom document root for hosts in the webservers group.

  1. Overriding via Playbook Variables:
---
# playbooks/deploy_webserver_custom_port.yml
- name: Deploy web server with custom port via playbook vars
  hosts: webservers
  become: true
  vars:
    webserver_port: 9000 # This will override defaults/ and group_vars
  roles:
    - webserver
  1. Overriding via Command Line:

ansible-playbook -i inventory.ini playbooks/deploy_webserver.yml -e "webserver_port=8088"

Role Dependencies (meta/main.yml)

Roles can also depend on other roles. For instance, a wordpress role might depend on a webserver role and a mysql role.

Example roles/wordpress/meta/main.yml:

# roles/wordpress/meta/main.yml
---
dependencies:
  - role: webserver
  - role: mysql

When you include the wordpress role in a playbook, Ansible will automatically ensure that the webserver and mysql roles are executed first.

Ansible Galaxy

Ansible Galaxy is a hub for finding, sharing, and downloading community-contributed roles.

Installing a role from Galaxy:


ansible-galaxy install geerlingguy.apache

This will download the apache role by geerlingguy into your ~/.ansible/roles (or a configured roles_path).

You can then use it in your playbooks like any other role:

---
- name: Deploy Apache using a Galaxy role
  hosts: webservers
  become: true
  roles:
    - geerlingguy.apache

Next Steps

In Part 6, we will delve into Ansible Vault for managing sensitive data securely, and briefly cover Collections for extending Ansible’s capabilities.