Ansible Role Tasks
Introduction
Tasks are the heart of any Ansible role. They define the specific actions that Ansible will execute on your managed hosts. When you organize tasks within roles, you create reusable, modular components that can significantly simplify your automation workflows.
In this guide, we'll explore how to create, organize, and implement tasks within Ansible roles. You'll learn best practices for structuring your tasks and see practical examples that you can adapt for your own projects.
Understanding Tasks in Ansible Roles
In Ansible roles, tasks are defined in YAML files that outline the specific operations to be performed. These tasks typically reside in the tasks
directory of your role structure:
my_role/
├── tasks/
│ ├── main.yml
│ └── additional_tasks.yml
├── handlers/
├── defaults/
├── vars/
├── files/
├── templates/
└── meta/
The main.yml
file serves as the primary entry point for your role's tasks. When Ansible executes your role, it automatically looks for this file first.
Basic Structure of Task Files
Let's examine the basic structure of a task file:
---
# tasks/main.yml
- name: Install required packages
apt:
name: "{{ item }}"
state: present
loop:
- nginx
- python3
- python3-pip
become: true
tags:
- packages
- setup
- name: Create application directory
file:
path: "/opt/myapp"
state: directory
mode: '0755'
become: true
tags:
- configuration
Each task consists of:
- A descriptive
name
(highly recommended for readability) - A module name (like
apt
,file
,template
) - Module parameters
- Optional elements like
become
,when
,tags
, etc.
Organizing Tasks with Include and Import
As your roles grow more complex, you might want to split your tasks into multiple files for better organization. Ansible provides two mechanisms for this: include_tasks
and import_tasks
.
Using import_tasks
The import_tasks
directive processes the included file during the playbook parsing phase:
---
# tasks/main.yml
- import_tasks: packages.yml
- import_tasks: configuration.yml
- import_tasks: services.yml
---
# tasks/packages.yml
- name: Install required packages
apt:
name: "{{ item }}"
state: present
loop:
- nginx
- python3
- python3-pip
become: true
tags:
- packages
Using include_tasks
The include_tasks
directive processes the included file during the execution phase:
---
# tasks/main.yml
- include_tasks: packages.yml
- include_tasks: configuration.yml
when: configure_app | bool
- include_tasks: services.yml
The key difference is that import_tasks
is static (processed at parse time), while include_tasks
is dynamic (processed at runtime). This means include_tasks
can use variables defined during the play execution and can be conditionally included.
Task Conditionals
Tasks can be selectively executed based on conditions:
- name: Install Apache for Debian/Ubuntu systems
apt:
name: apache2
state: present
when: ansible_os_family == "Debian"
become: true
- name: Install Apache for Red Hat systems
dnf:
name: httpd
state: present
when: ansible_os_family == "RedHat"
become: true
Loops in Tasks
When you need to perform the same action multiple times with different parameters, loops are invaluable:
- name: Create multiple directories
file:
path: "{{ item }}"
state: directory
mode: '0755'
loop:
- /opt/myapp/config
- /opt/myapp/data
- /opt/myapp/logs
become: true
Task Handlers
Tasks can notify handlers to trigger additional actions:
- name: Update Nginx configuration
template:
src: nginx.conf.j2
dest: /etc/nginx/sites-available/myapp.conf
become: true
notify: Restart Nginx
The handler referenced by notify
would be defined in the handlers/main.yml
file:
---
# handlers/main.yml
- name: Restart Nginx
service:
name: nginx
state: restarted
become: true
Role Tasks Visualization
Here's a simplified diagram of how tasks flow within a role:
Practical Examples
Example 1: Web Server Role
Let's create tasks for a web server role:
---
# tasks/main.yml
- import_tasks: install.yml
- import_tasks: configure.yml
- import_tasks: secure.yml
- import_tasks: deploy.yml
---
# tasks/install.yml
- name: Install Nginx web server
apt:
name: nginx
state: present
become: true
tags:
- install
- nginx
- name: Ensure Nginx is started and enabled
service:
name: nginx
state: started
enabled: yes
become: true
tags:
- service
- nginx
---
# tasks/configure.yml
- name: Create virtual host configuration
template:
src: virtualhost.conf.j2
dest: /etc/nginx/sites-available/{{ app_name }}.conf
become: true
tags:
- configure
- nginx
- name: Enable virtual host
file:
src: /etc/nginx/sites-available/{{ app_name }}.conf
dest: /etc/nginx/sites-enabled/{{ app_name }}.conf
state: link
become: true
notify: Restart Nginx
tags:
- configure
- nginx
Example 2: Database Role
Here's an example of tasks for a database role:
---
# tasks/main.yml for a PostgreSQL role
- name: Install PostgreSQL packages
apt:
name:
- postgresql
- postgresql-contrib
- python3-psycopg2
state: present
become: true
tags:
- install
- database
- name: Ensure PostgreSQL is started and enabled
service:
name: postgresql
state: started
enabled: yes
become: true
tags:
- service
- database
- name: Create application database
postgresql_db:
name: "{{ app_db_name }}"
state: present
become: true
become_user: postgres
tags:
- database
- configuration
- name: Create database user
postgresql_user:
db: "{{ app_db_name }}"
name: "{{ app_db_user }}"
password: "{{ app_db_password }}"
priv: "ALL"
state: present
become: true
become_user: postgres
tags:
- database
- configuration
no_log: true # Hides sensitive information in logs
Task Execution Control
Tags
Tags allow you to run or skip specific parts of your role:
- name: Install packages
apt:
name: "{{ item }}"
state: present
loop: "{{ required_packages }}"
become: true
tags:
- install
- packages
You can then run only tasks with specific tags:
ansible-playbook playbook.yml --tags "install"
Or skip tasks with specific tags:
ansible-playbook playbook.yml --skip-tags "install"
Error Handling
You can control how Ansible responds to errors in your tasks:
- name: Attempt to start application
command: /opt/myapp/bin/start.sh
register: start_result
ignore_errors: true
- name: Display error message if application failed to start
debug:
msg: "Application failed to start: {{ start_result.stderr }}"
when: start_result.rc != 0
Best Practices for Role Tasks
- Use descriptive names: Always include a clear
name
for each task. - Group related tasks: Use separate files for logical groups of tasks.
- Use tags effectively: Tag tasks to allow selective execution.
- Balance granularity: Too many small tasks can be inefficient, while monolithic tasks are hard to maintain.
- Consider idempotency: Tasks should be able to run multiple times without causing errors.
- Document your tasks: Use comments to explain complex logic or unusual configurations.
- Handle errors gracefully: Use
ignore_errors
,failed_when
, andchanged_when
to control execution flow. - Hide sensitive data: Use
no_log: true
for tasks that deal with passwords or keys.
Summary
Tasks are the fundamental building blocks of Ansible roles. By effectively organizing your tasks, you can create modular, reusable roles that simplify your automation efforts.
In this guide, we've covered:
- The basic structure of tasks within roles
- How to organize tasks using
import_tasks
andinclude_tasks
- Using conditionals and loops in tasks
- Practical examples of tasks for web server and database roles
- Best practices for writing and organizing tasks
By following these patterns and best practices, you'll be able to create robust, maintainable Ansible roles that can be easily shared and reused across your projects.
Further Learning
To continue developing your Ansible role skills, try these exercises:
- Create a role that installs and configures your favorite application
- Refactor an existing playbook into a role with properly organized tasks
- Implement error handling in your existing roles
- Experiment with using both
import_tasks
andinclude_tasks
to understand their differences
You can also explore Ansible Galaxy to see how other users structure their roles and tasks for inspiration.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)