Four reference files covering: - ansible.md — core commands, ansible.cfg, key settings - ansible-inventory.md — static/dynamic inventory, directory layout - ansible-variables.md — 22-level precedence, scoping, merge behavior - ansible-roles.md — structure, defaults vs vars, dependencies
5.7 KiB
5.7 KiB
Ansible Roles
Reusable, self-contained units of automation with a standard directory structure.
Role Directory Structure
roles/
└── nginx/
├── defaults/
│ └── main.yml # Low-precedence defaults (user overrides these)
├── vars/
│ └── main.yml # High-precedence vars (internal to role)
├── tasks/
│ └── main.yml # Entry point for tasks
├── handlers/
│ └── main.yml # Handlers triggered by notify
├── templates/
│ └── nginx.conf.j2 # Jinja2 templates
├── files/
│ └── index.html # Static files for copy module
├── meta/
│ └── main.yml # Dependencies, galaxy metadata
└── README.md
All directories are optional — include only what the role needs.
What Goes Where
| Directory | Purpose | Precedence |
|---|---|---|
defaults/ |
Default variable values | Lowest — meant to be overridden |
vars/ |
Internal role variables | High — not easily overridden |
tasks/ |
Main task list | Entry point: tasks/main.yml |
handlers/ |
Restart/reload triggers | Run once at end of play |
templates/ |
Jinja2 files (.j2) |
Referenced as template: nginx.conf.j2 |
files/ |
Static files | Referenced as copy: src=index.html |
meta/ |
Role deps, platform info | Processed before role tasks run |
Creating a Role
# Scaffold with ansible-galaxy
ansible-galaxy role init roles/nginx
# Minimal manual structure
mkdir -p roles/nginx/{tasks,defaults,templates,handlers,meta}
Using Roles
In a Playbook
# site.yml
- hosts: webservers
roles:
- nginx # simple
- role: nginx # with params
vars:
nginx_listen_port: 8080
- role: nginx # conditional
when: install_nginx | bool
- role: nginx # tagged
tags: [web, nginx]
With include_role / import_role
tasks:
# Static import — processed at parse time
- import_role:
name: nginx
# Dynamic include — processed at runtime (supports loops/conditionals)
- include_role:
name: "{{ item }}"
loop:
- nginx
- certbot
import_role |
include_role |
|
|---|---|---|
| When parsed | Playbook load | Runtime |
| Tags/when | Applied to all tasks inside | Applied to include itself |
| Loops | Not supported | Supported |
| Handlers | Visible globally | Scoped to include |
Role Dependencies
# roles/nginx/meta/main.yml
dependencies:
- role: common
- role: firewall
vars:
firewall_allowed_ports:
- 80
- 443
Dependencies run before the role's own tasks. Duplicate dependencies are skipped unless allow_duplicates: true is set.
Example Role: nginx
# roles/nginx/defaults/main.yml
nginx_listen_port: 80
nginx_worker_processes: auto
nginx_server_name: "_"
nginx_root: /var/www/html
nginx_access_log: /var/log/nginx/access.log
# roles/nginx/tasks/main.yml
- name: Install nginx
apt:
name: nginx
state: present
update_cache: true
- name: Deploy configuration
template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
owner: root
group: root
mode: "0644"
notify: Reload nginx
- name: Enable and start nginx
service:
name: nginx
state: started
enabled: true
# roles/nginx/handlers/main.yml
- name: Reload nginx
service:
name: nginx
state: reloaded
Splitting Tasks
# roles/nginx/tasks/main.yml
- import_tasks: install.yml
- import_tasks: configure.yml
- import_tasks: service.yml
# Conditional platform tasks
- import_tasks: debian.yml
when: ansible_os_family == "Debian"
- import_tasks: redhat.yml
when: ansible_os_family == "RedHat"
Project Layout with Roles
ansible-project/
├── ansible.cfg
├── site.yml # Master playbook
├── webservers.yml # Play for web tier
├── dbservers.yml # Play for db tier
├── inventory/
│ ├── hosts.yml
│ ├── group_vars/
│ └── host_vars/
├── roles/
│ ├── common/ # Shared baseline
│ ├── nginx/
│ ├── postgresql/
│ └── requirements.yml # Galaxy dependencies
└── collections/
└── requirements.yml # Collection dependencies
# roles/requirements.yml
- src: geerlingguy.docker
version: "6.1.0"
- src: geerlingguy.certbot
ansible-galaxy install -r roles/requirements.yml -p roles/
Gotchas
defaults/is for users to override;vars/is for role internals — mixing them up breaks precedence- Handlers run at end of play, not after each task — use
meta: flush_handlersif needed mid-play - Role names must be valid Python identifiers (no hyphens) when used as variable namespaces
import_rolemakes role tasks visible to--list-tasks;include_roledoes not- Dependencies declared in
meta/run every time the role is referenced unless deduplicated - Template paths are relative to
templates/— usenginx.conf.j2, nottemplates/nginx.conf.j2 - File paths in
copyare relative tofiles/— useindex.html, notfiles/index.html
See Also
ansible— core commands and ansible.cfgansible-variables— how defaults/ vs vars/ fits into precedenceansible-inventory— inventory structure for role-consuming projects