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.
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.
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.
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.
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.
Use the command ansible-galaxy init to initialize a role named rolenamerolename.
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.ymlroles/
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.
Two examples of what meta/main.yml might look like are shown below.
Always fill in the author and description values.
--- galaxy_info:author:Bobdescription: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.
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.
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.ymlThis 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.
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"It is easy to adapt playbooks that do not use roles into ones that do, as shown below:
If some sequence of tasks applies to all hosts, create a role named "common" and move those tasks into it.
Identify sequences of tasks to be applied to all hosts that play a particular role.
Optionally, decompose further by grouping tasks together that work toward a particular end result (a goal or subgoal).
With each such sequence of tasks, create a role and move the tasks into it.
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 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
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.
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).