2. Ansible Role

2.1 Ansible Role vs. Module

The initial code directly used Ansible Modules - atomic Ansible steps, used to build playbooks. Now the code will be refactored to utilize Ansible Roles.

Ansible Modules and Roles serve different purposes in the automation ecosystem. A module is a single, self-contained unit of work that performs a specific task, such as installing a package (yum, apt), or copying files (copy). Modules are the fundamental building blocks in Ansible; each task in a playbook typically invokes a module to carry out a particular action on the managed hosts.

In contrast, a role is a higher-level organizational structure that groups together multiple related tasks, along with their defaults, handlers, templates, files, and variables. Roles provide a standardized way to package and reuse automation logic, making it easy to share and apply complex configurations across different projects or environments. By organizing content into roles, you can separate concerns, promote consistency, and reduce duplication in your automation code.

2.2 Scaffold Ansible Role directory structure

As Ansible role requires specific directory structure it’s handy use ansible tool to initialize the directory.

ansible-galaxy role init apache

This creates a full role skeleton in roles/apache/ with the standard Ansible structure for the role:

roles/
  apache/
    defaults/
      main.yml
    files/
    handlers/
      main.yml
    meta/
      main.yml
    tasks/
      main.yml
    templates/
    tests/
      inventory
      test.yml
    vars/
      main.yml

It’s important to understand each place in a role hierarchy, however not all of them are critical for regular use. Here is a list of critical directories:

  • tasks: the role’s executable logic. Split into additional task files and import/include as needed.

  • defaults: lowest-precedence vars for the role. Use for safe, overridable settings users might tweak.

  • vars: Higher-precedence vars than defaults (vars/main.yml). Use for internal/platform-specific values rarely overridden.

  • meta: role metadata and dependencies: supported platforms, required roles/collections, Galaxy info. Recent Ansible describes here role’s argument.

2.3 Move existing tasks into the Role

Role delivers multiple features, however on this stage we are interested in roles/apache/tasks/main.yml file to move playbook’s core logic to this place.

---
- name: Install Apache on RedHat
  ansible.builtin.yum:
    name: httpd
    state: present
  when: ansible_os_family == "RedHat"

- name: Install Apache on Debian
  ansible.builtin.apt:
    name: apache2
    state: present
  when: ansible_os_family == "Debian"

Now the complexity is encapsulated in Ansible Role, and the user see only top level technical function - make apache up and running.

2.4 Revert the Playbook to the simplest format

Having above Role ready, the playbook may be super simple. It’s even more simple that the initial one. All the complexity is hidden now in Role and the administrator calls pure business need to activate apache.

- hosts: webservers
  become: yes

  roles:
    - apache

Note that one more element should be simplified - the root control, by moving Ansible’s "become" to the lower level. It will be done later during refactoring supported by Molecule testing.

2.5 Limitations of the module and role based approaches

The examples above show a natural evolution: starting from a simple RedHat-only playbook, extending it to support Debian systems, and finally moving the complexity into an Ansible Role. While using roles helps organize and encapsulate the automation logic, the traditional approach of copying playbooks and roles between multiple projects remains problematic.

Duplicating these playbooks and roles across projects leads to multiple copies that can diverge over time, causing version drift and inconsistencies. This fragmentation makes it difficult to maintain and update automation content effectively, as changes applied in one place are not automatically reflected elsewhere. Let’s imagine that tha playbook was used by multiple users, who copied it to their environments. Does not smell good.

As a result, version control becomes scattered, and managing updates requires significant manual effort and coordination. Although this approach may work for small environments, enterprise-scale automation demands better separation of concerns, strict versioning, and mechanisms that prevent code duplication to ensure maintainability and consistency across teams.

One may argue that a role can be stored in its own Git repository and then included in a project. This approach indeed solves some challenges, such as version control and reuse across multiple playbooks. However, it still leaves other problems unresolved — for example, potential naming clashes, the lack of a consistent packaging format, and difficulties in managing dependencies.

Using just Ansible Roles is a partial solution, as role was introduced for soke purposes, Ansible community noticed a need to make next step. Finally as for today all aspects of domain specific logic are packaged in Ansible Collection.

Collections are top level distribution components used world-wide by all small and corporate size providers. On the other hand collection are super easy to use and maintain, giving enterprise level capabilities to Ansible adopters.