Bookmark this page

Working with Roles

Objectives

After completing this section, you should be able to:

  • Describe the structure of a role.

  • Create a role.

  • Execute a play that uses one or more roles.

Creating Reusable Playbooks

You can write a playbook in one very large file (and you might start out learning playbooks this way), but eventually you will want to reuse files and start to organize things. In Ansible, there are three ways to do this: includes, imports, and roles.

What are imports and includes? With Ansible 2.5 you can:

  • Import playbooks.

  • Include or import task files.

  • Include or import roles.

What is the difference between including and importing?

  • Import statements are processed when the playbooks are parsed.

  • Include statements are processed as they are encountered during the execution of the playbook.

Organizing Playbooks with Roles

You can use roles as an effective way to organize your playbooks.

  • Roles allow you to make your Ansible code more reusable, by saving tasks, template files, and variables in a generic way that can be reused by different playbooks and people.

  • Roles allows you to better separate the procedure used to configure a device (in the role) from the details of the configuration that is applied (in the play, inventory, and variables).

  • A well-written role can be shared safely with others without exposing sensitive information about your configuration.

Installing Roles

Here are two ways of installing roles:

  • Provide a roles/requirements.yml file that contains links to SCM-based roles. These roles are loaded automatically. This is a highly scalable way to install roles.

  • Create roles locally by:

    • Using ansible-galaxy init to initialize a directory structure.

    • Populating the directory structure with files that implement the role.

Automating Role Installation

A sample requirements.yml file is described below.

# sample requirements.yml

- src: ssh://git@git.example.com:8989/ansible-role-network-spine.git
  name: network-spine
  scm: git

- src: ssh://git@git.example.com:8989/ansible-role-network-leaf.git
  name: network-leaf
  scm: git

The requirements.yml file controls how roles are located and loaded.

Initializing Roles Locally

Use the command ansible-galaxy init rolename to initialize a role named rolename.

The ansible-galaxy init command does not create the roles directory.

Use the following command sequence to initialize a role skeleton for a role named myrole.

$ mkdir roles && ansible-galaxy init roles/myrole

These commands generate the following directory structure:

└── roles
        └── myrole
            ├── defaults
            │   └── main.yml
            ├── files
            ├── handlers
            │   └── main.yml
            ├── meta
            │   └── main.yml
            ├── README.md
            ├── tasks
            │   └── main.yml
            ├── templates
            ├── tests
            │   ├── inventory
            │   └── test.yml
            └── vars
                └── main.yml

Identifying Role Subdirectories

roles/
     rolename/               # this hierarchy represents a "role"
         defaults/           #
             main.yml        # <-- default lower priority variables for this role
         files/              #
             bar.txt         # <-- files for use with the copy resource
             foo.sh          # <-- script files for use with the script resource
         handlers/           #
             main.yml        # <-- handlers file
         meta/               #
             main.yml        # <-- role dependencies
         tasks/              #
             main.yml        # <-- tasks file can include smaller files
         templates/          # <-- files for use with the template resource
             ntp.conf.j2     # <-- templates end in .j2
         vars/               #
            main.yml         # <-- variables associated with this role

Always provide content in the meta/main.yml file.

Identifying Your Role

Two examples of what meta/main.yml might look like are shown below. Always fill in the author and description values.

---
galaxy_info:
  author: Bob
  description: Ansible role to deploy myAcmeApp
  company: Bob Inc.
  license: MIT
  min_ansible_version: 2.2
  platforms:
    - name: EL
      versions:
        - all
  categories:
    - cloud
    - web
dependencies: []
dependencies: []
galaxy_info:
  author: Alice
  company: Alice Inc.
  description: Nethosts, resolver and interfaces
  license: MIT
  min_ansible_version: 2.2.0
  platforms:
    - name: Debian
      versions:
        - wheezy
        - jessie
        - stretch
  categories:
    - system
    - network

The main justification for roles is reusability. Failing to provide role metadata severely impairs reusability.

Roles and Variables

Variables can be set on the command line, within the inventory file, within inventory-level group_vars and host_vars files, within role files, and default or variable files at the role level.

More commonly used ones are shown in boldface. Listed in order of precedence, from low to high:

  • role defaults

  • inventory file or script group vars

  • inventory group_vars/all

  • playbook group_vars/all

  • inventory group_vars/*

  • playbook group_vars/*

  • inventory file or script host vars

  • inventory host_vars/*

  • playbook host_vars/*

  • host facts

  • play vars

  • play vars_prompt

  • play vars_files

  • role vars (defined in role/vars/main.yml)

  • block vars (only for tasks in block)

  • task vars (only for the task)

  • role (and include_role) params

  • include params

  • include_vars

  • set_facts / registered vars

  • extra vars (always win precedence)

Familiarize yourself with a few of the more commonly used types of variables and stick with those unless you come across special requirements.

Creating an Ansible Project

When roles are used in conjunction with an Ansible project, put configuration files and playbooks in the project root with roles in a roles directory.

.
└── acme-network
    ├── ansible.cfg
    ├── leafs.yml
    ├── roles
    │   ├── common
    │   ├── leaf
    │   └── spine
    └── spines.yml

This makes it easy to manage the project directory with a VCS such as Git. It can be deployed to any Ansible control node that holds a host inventory locally, and used there with little or no extra operational overhead.

Incorporating Roles into Playbooks

There are two conventions for incorporating roles into playbooks: classic and modern.

The classic way of using roles is with the roles directive:

---
- hosts: ourhosts
  roles:
    - common
    - role1
    - role2

You can also use roles inline with any other tasks using import_role or include_role:

---
- hosts: ourhosts
  tasks:
    - debug:
        msg: "before we run our role"
    - import_role:
        name: role1
    - include_role:
        name: role2
    - debug:
        msg: "after we ran our role"

Adapting Playbooks to Use Roles

It is easy to adapt playbooks that do not use roles into ones that do, as shown below:

  1. If some sequence of tasks applies to all hosts, create a role named "common" and move those tasks into it.

  2. Identify sequences of tasks to be applied to all hosts that play a particular role.

  3. Optionally, decompose further by grouping tasks together that work toward a particular end result (a goal or subgoal).

  4. With each such sequence of tasks, create a role and move the tasks into it.

  5. Adapt the playbook, or create a new playbook, using the original hosts block and replacing the original tasks block with a roles block.

An Example of Adapting a Playbook

An example is provided here that illustrates how to adapt playbooks to use roles.

Create a spine role (roles/spine/tasks/main.yml) and a playbook that uses it (spines.yml).

---
- name: configure interface descriptions on spine devices
  hosts: spines

  tasks:
    - name: set interface description
      vyos_interface:
        aggregate:
        - { name: eth0, description: Outside }
        - { name: eth1, description: Inside }

Given this simple playbook that does not use roles:

---
- name: the spine role
  tasks:
  - name: set interface description
    vyos_interface:
    aggregate:
    - { name: eth0, description: Outside }
    - { name: eth1, description: Inside }
---
- name: the spines playbook
  hosts: spines
  roles:
    - spine

Role Dependencies

Roles can include other roles as dependencies. Dependencies are defined in the meta/main.yml file. A role, for instance, that configures an IOS device to support SSH access might also require a vty_lines role.

# meta/main.yml

dependencies:
  - { role: ssh_access, ssh_version: 2 }!
  - { role: vty_lines, range_start: 0, range_end: 4 }

By default, a given role is only added as a dependency to a playbook once. This can be overridden by setting the allow_duplicates variable to yes in the meta/main.yml file.

Order of Execution

When playbooks contain both roles and non-role tasks, tasks in roles normally run before non-role ones. Ansible also lets you override this with the special pre_tasks and post_tasks sections. You can define one sequence of non-role tasks that execute before roles, and another sequence that executes after roles.

---
- hosts: somehost.example.com
  pre_tasks:
    - debug:
        msg: 'before'

  roles:
    - role1
    - role2

  tasks:
    - debug:
        msg: 'implicitly after'

  post_tasks:
    - debug:
        msg: 'explicitly after'

In this example, even if the tasks block preceded the roles block, the "implicitly after" debug message would still run after the roles (with roles being called in the classic way).

Revision: do457-2.5-4693601