Chapter 8: Simplifying Playbooks with Roles and Collections
Creating roles
Writing a role is mostly a matter of putting things you already know how to write into the right directories. This section walks through the steps, from an empty skeleton to a role a play can use.
- Create a role skeleton with
ansible-galaxy role init. - Fill in tasks, defaults, handlers and templates.
- Declare dependencies on other roles.
- Follow the habits that make a role reusable.
Start from a skeleton
ansible-galaxy role init creates the standard directories with a main.yml in each:
[student@workstation project]$ ansible-galaxy role init roles/web_site
- Role roles/web_site was created successfully
[student@workstation project]$ ls roles/web_site
defaults files handlers meta README.md tasks templates tests varsKeeping a project's own roles in a roles/ directory beside the playbooks means Ansible finds them without any configuration. Delete the directories you do not need; an empty vars/ or tests/ only adds noise.
Write the tasks
tasks/main.yml is a task file: a list of tasks with no play around it.
---
- name: Web packages are installed
ansible.builtin.dnf:
name: httpd
state: present
- name: Site configuration is in place
ansible.builtin.template:
src: site.conf.j2
dest: /etc/httpd/conf.d/site.conf
mode: "0644"
notify: restart httpd
- name: httpd is running
ansible.builtin.service:
name: httpd
state: started
enabled: trueA long role can split its tasks over several files and pull them in from main.yml with import_tasks or include_tasks, exactly as in chapter 7. Paths are relative to the role's tasks/ directory.
Give it defaults
Every value a user might reasonably change becomes a variable with a default:
---
web_site_title: Welcome
web_site_admin: root@localhostA role that works with no variables set at all is much easier to try out.
Add handlers and templates
---
- name: restart httpd
ansible.builtin.service:
name: httpd
state: restarted# {{ ansible_managed }}
ServerAdmin {{ web_site_admin }}
ServerName {{ ansible_facts['fqdn'] }}Describe it in meta
meta/main.yml records who wrote the role and what it needs. The part that changes behaviour is dependencies: roles listed there run automatically before this one.
---
galaxy_info:
author: your name
description: Publishes a one-page web site with Apache
license: MIT
min_ansible_version: "2.14"
dependencies:
- role: firewall_baseA dependency can pass its own variables to the role it pulls in:
dependencies:
- role: apache
port: 8080
- role: postgres
dbname: serverlist
admin_user: felixA dependency runs once per play, however many roles in that play depend on it. If a role really must run every time it is listed, set allow_duplicates: true in that role's meta/main.yml.
Use it
---
- name: Web servers run the site
hosts: web
roles:
- role: web_site
vars:
web_site_title: Staff intranetChanging a role's behaviour with variables
A well-written role takes its settings from variables, so one role can serve many hosts. Where you set them decides whether they win:
| Set in | Overrides the role's defaults/? | Overrides the role's vars/? |
|---|---|---|
The inventory, group_vars/ or host_vars/ | Yes | No |
The play's vars: | Yes | No |
vars: under the role entry in roles: | Yes | No |
| A role parameter: a key written directly on the role entry | Yes | Yes |
include_vars, set_fact, registered variables | Yes | Yes |
A role parameter looks like this, with the variable at the same level as role::
roles:
- role: motd
system_owner: someone@host.example.comThat very high precedence is exactly why vars/ is the place for values the role needs to work, and defaults/ for everything a user is meant to change. Never put secrets in either: a role is meant to be shared. Let the playbook supply them, from an Ansible Vault file.
Task names in the output are prefixed with the role name, which shows at a glance where each task comes from:
TASK [web_site : Web packages are installed] ***********************************
TASK [web_site : Site configuration is in place] *******************************
RUNNING HANDLER [web_site : restart httpd] *************************************What makes a role reusable
A role that installs a web server and a database is two roles. Small roles combine; large ones get copied and edited.
Anything that differs between environments is a variable with a default. The role never mentions a particular host or group.
web_site_title, web_site_admin. It avoids collisions and shows which role a variable belongs to.
Run the play twice; the second run must report changed=0. Use modules, not command, wherever a module exists.
List the variables and show an example play in README.md, and keep meta/main.yml accurate. Future you is the most likely reader.
Keep each role in its own version control repository. Projects then pin the version they were tested with, and a bad change can be rolled back.
When a role does not quite fit a new case, improve the role (add a variable, split a task) rather than copying it into a near-duplicate. Test the change against the plays that already use it.
Takeaway
Create a role skeleton with ansible-galaxy role init. Use the chapter lab to check this on a real host.