Ansible Role Structure
Introduction
When working with Ansible, you'll eventually want to organize your automation code into reusable, modular components. This is where Ansible roles come in. A role is essentially a way to bundle automation content (like tasks, handlers, files, and variables) into a standardized file structure.
Think of roles as the building blocks of your Ansible automation - they allow you to:
- Create reusable, modular components
- Share and reuse configurations between projects
- Improve readability and organization of your code
- Focus on what each component should do rather than how to do it
In this guide, we'll explore the standard structure of Ansible roles and how to organize your automation code effectively.
Standard Role Directory Structure
An Ansible role has a defined directory structure with specific directories for different types of content. Let's look at what a complete role structure looks like:
roles/
└── my_role/
├── defaults/ # Default variables (lowest precedence)
│ └── main.yml
├── files/ # Static files to be deployed
├── handlers/ # Handlers to be triggered by tasks
│ └── main.yml
├── meta/ # Role metadata and dependencies
│ └── main.yml
├── tasks/ # Tasks to be executed by the role
│ └── main.yml
├── templates/ # Jinja2 templates
├── tests/ # Tests for the role
│ ├── inventory
│ └── test.yml
└── vars/ # Role variables (higher precedence)
└── main.yml
Let's understand each directory and its purpose in detail.
Role Components Explained
1. tasks/main.yml
- The Core of Your Role
The tasks
directory contains the main list of tasks that the role will execute. This is the core functionality of your role.
# roles/webserver/tasks/main.yml
---
- name: Install Apache
ansible.builtin.package:
name: apache2
state: present
- name: Configure Apache
ansible.builtin.template:
src: apache.conf.j2
dest: /etc/apache2/apache.conf
notify: Restart Apache
- name: Ensure Apache is running
ansible.builtin.service:
name: apache2
state: started
enabled: yes
You can also split tasks into multiple files and include them in the main tasks file:
# roles/webserver/tasks/main.yml
---
- name: Import install tasks
ansible.builtin.import_tasks: install.yml
- name: Import configure tasks
ansible.builtin.import_tasks: configure.yml
- name: Import service tasks
ansible.builtin.import_tasks: service.yml
2. defaults/main.yml
- Default Variables
The defaults
directory contains default variables for the role that have the lowest precedence and can be easily overridden.
# roles/webserver/defaults/main.yml
---
apache_port: 80
apache_document_root: /var/www/html
enable_ssl: false
3. vars/main.yml
- Role Variables
The vars
directory contains variables that are less likely to be overridden and have higher precedence than defaults.
# roles/webserver/vars/main.yml
---
apache_packages:
- apache2
- apache2-utils
apache_config_path: /etc/apache2
4. handlers/main.yml
- Event Responders
Handlers are tasks that only run when notified by another task. They're typically used for restarting services or triggering other actions.
# roles/webserver/handlers/main.yml
---
- name: Restart Apache
ansible.builtin.service:
name: apache2
state: restarted
- name: Reload Apache
ansible.builtin.service:
name: apache2
state: reloaded
5. files/
- Static Files
The files
directory contains static files that need to be transferred to the managed hosts. Files in this directory can be referenced directly by name.
# Using files in tasks
- name: Copy static HTML file
ansible.builtin.copy:
src: index.html # Refers to roles/webserver/files/index.html
dest: /var/www/html/index.html
6. templates/
- Dynamic Files with Jinja2
The templates
directory contains Jinja2 templates that will be processed before being copied to the managed hosts.
<!-- roles/webserver/templates/vhost.conf.j2 -->
<VirtualHost *:{{ apache_port }}>
DocumentRoot {{ apache_document_root }}
<Directory {{ apache_document_root }}>
AllowOverride All
Require all granted
</Directory>
ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined
</VirtualHost>
Using templates in tasks:
- name: Configure virtual host
ansible.builtin.template:
src: vhost.conf.j2
dest: /etc/apache2/sites-available/000-default.conf
notify: Reload Apache
7. meta/main.yml
- Role Metadata
The meta
directory contains role metadata, including dependencies on other roles.
# roles/webserver/meta/main.yml
---
galaxy_info:
author: Your Name
description: Role to install and configure Apache webserver
company: Your Company
license: MIT
min_ansible_version: 2.9
platforms:
- name: Ubuntu
versions:
- focal
- jammy
galaxy_tags:
- web
- apache
- httpd
dependencies:
- role: common
vars:
some_parameter: value
8. tests/
- Role Testing
The tests
directory contains an inventory and test playbook for testing the role.
# roles/webserver/tests/test.yml
---
- hosts: localhost
roles:
- role: webserver
apache_port: 8080
Role Organization in Playbooks
Now that we understand the structure of a role, let's see how to use roles in playbooks:
---
- name: Configure webservers
hosts: webservers
become: yes
roles:
- common
- webserver
- { role: monitoring, when: enable_monitoring | default(true) }
You can also use roles with the include_role
or import_role
tasks:
---
- name: Configure servers
hosts: all
tasks:
- name: Include common role
ansible.builtin.import_role:
name: common
- name: Configure database if needed
ansible.builtin.include_role:
name: database
when: inventory_hostname in groups['db_servers']
Role Organization with Collections
In newer versions of Ansible, roles can be part of collections. A typical collection structure looks like:
collection/
├── docs/
├── galaxy.yml
├── plugins/
│ └── ...
├── README.md
└── roles/
├── role1/
│ └── ...
└── role2/
└── ...
When a role is part of a collection, you can reference it using the fully qualified name:
- name: Use a role from a collection
hosts: all
roles:
- namespace.collection.role_name
Creating Roles with ansible-galaxy
The easiest way to create a new role with the correct structure is to use the ansible-galaxy
command:
ansible-galaxy role init my_new_role
This creates a new role with all the necessary directories and template files.
Real-World Example: Creating a LAMP Stack Role
Let's see a more comprehensive example of creating roles for a LAMP stack (Linux, Apache, MySQL, PHP).
We'll create three separate roles:
apache
- Installs and configures Apache web servermysql
- Installs and configures MySQL database serverphp
- Installs PHP and necessary extensions
roles/
├── apache/
│ ├── defaults/main.yml
│ ├── handlers/main.yml
│ ├── tasks/main.yml
│ └── templates/
│ └── vhost.conf.j2
├── mysql/
│ ├── defaults/main.yml
│ ├── handlers/main.yml
│ ├── tasks/
│ │ ├── main.yml
│ │ ├── install.yml
│ │ └── secure.yml
│ └── templates/
│ └── my.cnf.j2
└── php/
├── defaults/main.yml
├── tasks/main.yml
└── templates/
└── php.ini.j2
Then, we can create a playbook that uses these roles:
---
- name: Configure LAMP servers
hosts: lamp_servers
become: yes
roles:
- apache
- mysql
- php
Best Practices for Role Structure
Here are some best practices to follow when creating and structuring Ansible roles:
- Keep roles focused - Each role should have a single responsibility
- Use defaults appropriately - Put variables that are meant to be overridden in
defaults/main.yml
- Document your roles - Include a README.md file with usage instructions
- Use proper dependency management - Define role dependencies in
meta/main.yml
- Test your roles - Include tests for your roles in the
tests/
directory - Use version control - Store your roles in a Git repository
- Consider publishing - Share useful roles on Ansible Galaxy
- Use tags - Tag tasks in your roles for selective execution
Role Structure Visualization
Here's a visual representation of how data flows through an Ansible role:
Summary
Ansible roles provide a standardized way to structure your automation code, making it more modular, reusable, and maintainable. The key components of an Ansible role include:
- Tasks: The core functionality of the role
- Handlers: Respond to notifications from tasks
- Variables: Both default and role-specific variables
- Files and Templates: Static and dynamic content for deployment
- Meta: Role metadata and dependencies
By organizing your Ansible code into roles, you can:
- Simplify complex deployments
- Share and reuse configuration code
- Improve collaboration with other team members
- Create a library of standardized infrastructure components
Additional Resources
To further explore Ansible roles, consider these resources:
- The official Ansible documentation on roles
- Explore community roles on Ansible Galaxy
- Learn about role testing frameworks like Molecule
Exercises
- Create a basic role that installs and configures Nginx with a custom configuration file.
- Modify an existing role to make it more parameterized using default variables.
- Create a role dependency structure where one role depends on another.
- Refactor an existing playbook into multiple roles for better organization.
- Create a role that works across multiple Linux distributions by using conditionals.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)