1. Introduction
While Ansible is helpful for automation and configuration management, its terminologies can get a bit confusing. The differences between interconnected concepts such as tasks, roles, plays, and playbooks easily get blurred. But considering their importance in Ansible, understanding what each one represents is vital.
In this tutorial, we compare tasks, roles, plays, and playbooks in Ansible, highlighting their differences and basic syntaxes.
2. Ansible Task
An Ansible task is basically the action-defining unit of a playbook. Each task in a playbook runs a code unit that configures resources in a certain way or executes a specific command on a managed node. These code units are called modules.
When defining a task, we typically pass arguments to the parameters of a module, particularly the required parameters. These arguments allow for specific module operation. For instance, if we wanted to install a package on a managed node, we could create a task using the ansible.builtin.package module:
- name: Install Apache
ansible.builtin.package:
name: apache2
state: present
In the snippet above, because the state parameter gets present as its argument, the task installs apache2 – the name parameter’s argument.
We can also write a task where ansible.builtin.package ensures a package doesn’t exist on a managed node:
- name: Remove MariaDB
ansible.builtin.package:
name: mariadb-server
state: absent
In this case, the task removes mariadb-server since the state parameter’s argument says absent.
As seen in the snippets above, tasks present as a list of dictionaries. However, the parameters of their modules may take on complex data structures such as dictionaries with lists as values.
3. Ansible Play
At its simplest, a play is a group of ordered tasks executed collectively. It’s the basic execution unit of a playbook as it maps a set of tasks to managed nodes for execution in a predefined order.
To map tasks to managed nodes, plays require the hosts parameter with a valid argument. Depending on the argument, the playbook may or may not need a host inventory. A host inventory is a grouped or ungrouped list of servers on which Ansible executes plays.
When hosts gets all, *, or a pattern, we must pass a host inventory to the playbook. However, if we pass an FQDN or IP address directly to the hosts parameter, the playbook may not need a host inventory. Of course, when working with a large fleet of managed nodes, creating a host inventory would be more practical. Specifying FQDNs or IP addresses for so many servers in the playbook will surely leave things disorganized.
Let’s define a play with the two tasks from the previous section:
- hosts: all
tasks:
- name: Install Apache
ansible.builtin.package:
name: apache2
state: present
- name: Remove MariaDB
ansible.builtin.package:
name: mariadb-server
state: absent
The play above will install apache2 on and remove mariadb-server from all the nodes in the provided host inventory. In addition, since plays execute tasks in their order of definition, the play will install apache2 before removing mariadb-server.
Apart from tasks and hosts, a play may also contain handlers, variables, and a reference to roles.
4. Ansible Role
Ansible roles are reusable collections of tasks, files, templates, variables, handlers, plugins, and so on. They promote modularization, which in turn encourages reusability while ensuring tidiness.
Unlike plays and tasks, roles don’t appear directly in a playbook. Instead, they come as a directory with at least one of seven subdirectories.
Using tree, let’s print the directory structure of a role with all seven standard subdirectories:
$ tree
.
└── role_1
├── README.md
├── defaults
│ └── main.yml
├── files
├── handlers
│ └── main.yml
├── meta
│ └── main.yml
├── tasks
│ └── main.yml
├── templates
└── vars
└── main.yml
9 directories, 6 files
Roles follow a specific directory structure, so sticking with said structure when using them is crucial.
4.1. Importing Roles Into a Play
As mentioned earlier, roles don’t appear directly in a playbook. Instead, we can import them in plays, in tasks, and as dependencies to other roles. Let’s import the role above into the play from the previous section:
- hosts: all
roles:
- baeldung_roles/role_1
tasks:
- name: Install Apache
ansible.builtin.package:
name: apache2
state: present
- name: Remove MariaDB
ansible.builtin.package:
name: mariadb-server
state: absent
The roles parameter in the play definition takes a list whose values are relative paths of the roles in the working directory. In the above, we specify our role as baeldung_roles/role_1. This means role_1 is a subdirectory of baeldung_roles, which, in turn, is a subdirectory of the working directory.
If role_1 were a subdirectory of the working directory, our play would’ve been different:
- hosts: all
roles:
- role_1
tasks:
...truncated...
4.2. Importing Roles Into a Task
Tasks use roles when we specify one of two keywords: import_role or include_role. The include_role keyword allows tasks to use roles dynamically:
- name: Include role_1
include_role:
name: role_1
In other words, Ansible only parses roles specified by include_role when it reaches the specific task.
Unlike include_role, import_role lets tasks use roles statically:
- name: Import role_1
import_role:
name: role_1
This is possible because Ansible parses roles passed to import_role at the beginning of the playbook execution.
4.3. Using Role Dependencies
To use a role as a dependency of another role, we’ll specify the role under the dependencies keyword in the meta/main.yml file of the dependent role:
$ cat baeldung_roles/role_1/meta/main.yml
dependencies:
- role: role_2
Above, we added a role named role_2 as a dependency of role_1.
5. Ansible Playbook
Playbooks are YAML files containing one or more plays. They form the basis of the execution of the ansible-playbook command and sit at the highest level in the Ansible automation structure.
Let’s create a playbook with two plays, with each one referencing different managed node groups:
$ cat playbook.yml
- hosts: databases
tasks:
- name: Remove MariaDB
ansible.builtin.package:
name: mariadb-server
state: absent
- hosts: webservers
tasks:
- name: Install Apache
ansible.builtin.package:
name: apache2
state: present
The first play in the playbook above will execute a task that removes mariadb-server from hosts under the databases group in the host inventory. Then, the second play will install apache2 in the hosts under the webservers group.
6. Conclusion
In this article, we examined the difference between a task, role, play, and playbook in Ansible. A playbook consists of multiple plays and is the file passed to the ansible-playbook command. A play, on the other hand, contains a group of tasks mapped to a specific server or group of servers.
Tasks define actions for execution on managed nodes by calling modules. However, roles are modularized, reusable collections of Ansible resources.