Advanced

Ansible Roles: Complete Guide with Real-World Examples

Teach me Ansible | 2025-02-15 | 28 min read

Master Ansible roles to organize your automation code like a pro. This comprehensive guide covers role structure, best practices, and real-world examples to help you build reusable, maintainable automation.

What Are Ansible Roles?

Ansible roles are a way to group related tasks, variables, files, templates, and handlers into a reusable structure. Think of roles as packages of automation logic that can be easily shared and reused across different playbooks and projects.

Why Use Roles?

  • Modularity - Break down complex automation into manageable pieces
  • Reusability - Write once, use everywhere
  • Organization - Clear structure makes code easier to maintain
  • Collaboration - Easy to share with team members or the community
  • Testing - Isolated components are easier to test

Ansible Role Directory Structure

A standard Ansible role follows this directory structure:

roles/
└── webserver/
    ├── defaults/          # Default variables (lowest precedence)
    │   └── main.yml
    ├── vars/              # Role variables (high precedence)
    │   └── main.yml
    ├── tasks/             # Main task list
    │   └── main.yml
    ├── handlers/          # Handlers triggered by tasks
    │   └── main.yml
    ├── templates/         # Jinja2 templates
    │   └── nginx.conf.j2
    ├── files/             # Static files to copy
    │   └── index.html
    ├── meta/              # Role metadata and dependencies
    │   └── main.yml
    └── README.md          # Documentation

Creating Your First Role

Use the ansible-galaxy command to create a role skeleton:

ansible-galaxy init webserver
cd webserver
ls -la

Example: Building a Complete Webserver Role

1. Define Default Variables

File: roles/webserver/defaults/main.yml

---
# Default variables for webserver role
http_port: 80
https_port: 443
document_root: /var/www/html
server_name: localhost

# Package names (can be overridden per OS)
web_package: nginx
web_service: nginx

# SSL configuration
enable_ssl: false
ssl_cert_path: /etc/ssl/certs/nginx.crt
ssl_key_path: /etc/ssl/private/nginx.key

2. Create Main Tasks

File: roles/webserver/tasks/main.yml

---
- name: Install web server package
  package:
    name: "{{ web_package }}"
    state: present
  become: yes

- name: Ensure document root exists
  file:
    path: "{{ document_root }}"
    state: directory
    owner: www-data
    group: www-data
    mode: '0755'
  become: yes

- name: Copy website files
  copy:
    src: index.html
    dest: "{{ document_root }}/index.html"
    owner: www-data
    group: www-data
    mode: '0644'
  become: yes
  notify: restart webserver

- name: Deploy Nginx configuration
  template:
    src: nginx.conf.j2
    dest: /etc/nginx/sites-available/{{ server_name }}.conf
    owner: root
    group: root
    mode: '0644'
  become: yes
  notify: restart webserver

- name: Enable site configuration
  file:
    src: /etc/nginx/sites-available/{{ server_name }}.conf
    dest: /etc/nginx/sites-enabled/{{ server_name }}.conf
    state: link
  become: yes
  notify: restart webserver

- name: Ensure webserver is running and enabled
  service:
    name: "{{ web_service }}"
    state: started
    enabled: yes
  become: yes

3. Add Handlers

File: roles/webserver/handlers/main.yml

---
- name: restart webserver
  service:
    name: "{{ web_service }}"
    state: restarted
  become: yes

- name: reload webserver
  service:
    name: "{{ web_service }}"
    state: reloaded
  become: yes

4. Create Templates

File: roles/webserver/templates/nginx.conf.j2

server {
    listen {{ http_port }};
    server_name {{ server_name }};

    root {{ document_root }};
    index index.html index.htm;

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

{% if enable_ssl %}
    listen {{ https_port }} ssl;
    ssl_certificate {{ ssl_cert_path }};
    ssl_certificate_key {{ ssl_key_path }};
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers HIGH:!aNULL:!MD5;
{% endif %}

    # Security headers
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-XSS-Protection "1; mode=block" always;
}

5. Add Metadata

File: roles/webserver/meta/main.yml

---
galaxy_info:
  author: Your Name
  description: Nginx webserver role
  company: Your Company
  license: MIT
  min_ansible_version: 2.9

  platforms:
    - name: Ubuntu
      versions:
        - focal
        - jammy
    - name: Debian
      versions:
        - bullseye
        - bookworm

  galaxy_tags:
    - web
    - nginx
    - webserver

dependencies: []

Using Roles in Playbooks

Basic Usage

---
- name: Deploy web servers
  hosts: webservers
  roles:
    - webserver

With Custom Variables

---
- name: Deploy web servers with SSL
  hosts: webservers
  roles:
    - role: webserver
      vars:
        server_name: example.com
        http_port: 8080
        enable_ssl: true
        ssl_cert_path: /etc/letsencrypt/live/example.com/fullchain.pem
        ssl_key_path: /etc/letsencrypt/live/example.com/privkey.pem

Conditional Role Execution

---
- name: Deploy infrastructure
  hosts: all
  roles:
    - role: webserver
      when: "'webservers' in group_names"

    - role: database
      when: "'databases' in group_names"

Advanced Role Patterns

Role Dependencies

Roles can depend on other roles. Example in meta/main.yml:

dependencies:
  - role: common
    vars:
      ntp_server: pool.ntp.org

  - role: firewall
    vars:
      allowed_ports:
        - 80
        - 443

Role with Pre and Post Tasks

---
- name: Deploy with pre/post tasks
  hosts: webservers

  pre_tasks:
    - name: Update package cache
      apt:
        update_cache: yes
      become: yes

  roles:
    - webserver

  post_tasks:
    - name: Run smoke tests
      uri:
        url: "http://{{ inventory_hostname }}"
        status_code: 200

Dynamic Role Inclusion

---
- name: Deploy based on OS
  hosts: all
  tasks:
    - name: Include OS-specific role
      include_role:
        name: "webserver-{{ ansible_os_family | lower }}"

Role Best Practices

1. Use Meaningful Names

Choose descriptive role names:

  • nginx-webserver
  • postgresql-database
  • role1
  • my-stuff

2. Document Everything

Always include a comprehensive README.md:

# Webserver Role

Installs and configures Nginx web server.

## Requirements

- Ansible 2.9+
- Target: Ubuntu 20.04+

## Role Variables

| Variable | Default | Description |
|----------|---------|-------------|
| http_port | 80 | HTTP port |
| server_name | localhost | Server name |

## Example Playbook

```yaml
- hosts: webservers
  roles:
    - webserver
```

## License

MIT

3. Make Roles Idempotent

Always ensure tasks can run multiple times safely:

# Good - idempotent
- name: Ensure service is running
  service:
    name: nginx
    state: started

# Bad - not idempotent
- name: Start service
  command: systemctl start nginx

4. Use Tags for Flexibility

---
- name: Install packages
  package:
    name: "{{ web_package }}"
    state: present
  tags:
    - install
    - packages

- name: Configure service
  template:
    src: config.j2
    dest: /etc/nginx/nginx.conf
  tags:
    - configure
    - config

Run specific tags:

ansible-playbook site.yml --tags "configure"
ansible-playbook site.yml --skip-tags "install"

5. Version Your Roles

Use Git tags for versioning:

git tag v1.0.0
git push origin v1.0.0

Testing Roles

Molecule Framework

Use Molecule for comprehensive role testing:

# Install Molecule
pip install molecule molecule-docker

# Initialize Molecule in role
cd roles/webserver
molecule init scenario

# Test role
molecule test

Kitchen-CI

Alternative testing with Test Kitchen:

# .kitchen.yml
---
driver:
  name: docker

provisioner:
  name: ansible_playbook
  roles_path: roles
  playbook: test.yml

platforms:
  - name: ubuntu-22.04

suites:
  - name: default

Sharing Roles via Ansible Galaxy

1. Prepare Your Role

# Ensure meta/main.yml is complete
# Add README.md with documentation
# Add LICENSE file
# Create git repository

2. Publish to Galaxy

# Login to Ansible Galaxy
ansible-galaxy login

# Import role from GitHub
ansible-galaxy role import yourusername your-role-repo

3. Install from Galaxy

# Install specific role
ansible-galaxy install geerlingguy.nginx

# Install from requirements.yml
ansible-galaxy install -r requirements.yml

Common Role Patterns

Multi-OS Support

---
# vars/Debian.yml
web_package: nginx
web_service: nginx

# vars/RedHat.yml
web_package: nginx
web_service: nginx

# tasks/main.yml
- name: Include OS-specific variables
  include_vars: "{{ ansible_os_family }}.yml"

- name: Install web server
  package:
    name: "{{ web_package }}"
    state: present

Environment-Specific Configuration

# group_vars/production.yml
http_port: 80
enable_ssl: true

# group_vars/development.yml
http_port: 8080
enable_ssl: false

Troubleshooting Roles

Debugging Tips

# Verbose output
ansible-playbook site.yml -vvv

# Check syntax
ansible-playbook site.yml --syntax-check

# Dry run
ansible-playbook site.yml --check

# List all tasks
ansible-playbook site.yml --list-tasks

Conclusion

Ansible roles are essential for building scalable, maintainable automation. By following the patterns and best practices in this guide, you'll create roles that are:

  • Easy to understand and maintain
  • Reusable across projects
  • Well-documented and tested
  • Ready to share with the community

Start small with simple roles, then gradually incorporate advanced patterns as your needs grow. The modular nature of roles will transform how you approach infrastructure automation.

Ready to Practice?

Try creating your own role in our Interactive Playground or explore more automation patterns in our Learning Center.