Skip to content

Ansible Roles: Step by Step

  • 14 min read

Ansible is a potent tool for automation, but using a large playbook can get messy fast. Think of your kitchen. You can cook a meal with all your ingredients tossed on the counter, but it’s not ideal, is it? You’d much rather have your spices grouped, your meats in the fridge, and your vegetables in the crisper. That’s where Ansible roles come in. They bring order and structure to your automation efforts, making them easy to manage, share, and reuse. This article, an Ansible roles guide, will take you step by step through understanding, creating, and using Ansible roles for your automation tasks.

What are Ansible Roles?

Ansible roles are like pre-packaged automation routines, each designed for a specific task or set of related tasks. Instead of jamming all your automation logic into one massive playbook, you break it down into roles. These roles then handle their part of the overall automation job.

For example, you might have roles to:

  • Set up a web server
  • Install a database
  • Configure firewalls
  • Deploy code

Each of these roles contains all the files, tasks, and variables needed for their specific job. It’s like having individual tools in your toolbox, each ready to use on a different part of your project.

Roles make your automation:

  • More organized: Playbooks become easier to read and understand.
  • Reusable: You can use the same role across different projects.
  • Easier to maintain: Small changes can be made in one place (the role), rather than in many places in your playbooks.

Why Use Ansible Roles?

Using roles is not just a good idea, it is a must for any Ansible project that goes beyond basic tasks. Here’s why:

  • Modularity: Roles bring the power of modularity to Ansible. Just as software engineers use modules to build complex programs, you use roles to create your automation pieces. Each role does its own job and can be put together with other roles to complete bigger tasks.

  • Reusability: Once a role is written, you can use it in any playbook. This means less repetitive work and more time to tackle new challenges. For instance, a role that configures your web server can be used in new projects with a few tweaks to the variables.

  • Maintainability: When things change, you need to be able to update your automation easily. Roles keep your tasks organized. If you need to change how your web servers are set up, you change the web server role, and every playbook using it gets the update.

  • Readability: A playbook that calls roles is much cleaner and easier to read than one with all the automation logic mixed into it. Your playbooks become a simple list of what needs to happen. The details of how it happens are tucked away in the roles. This makes playbooks easier for you and your team to understand.

  • Collaboration: Using roles helps teams collaborate on automation projects. Different team members can work on different roles, making progress on tasks at the same time, without stepping on each other’s toes.

Anatomy of an Ansible Role

Now that you see the value in using roles, let’s take a closer look at the parts of a role and the structure of a role directory:

my_role/
├── defaults/
│   └── main.yml
├── files/
├── handlers/
│   └── main.yml
├── meta/
│   └── main.yml
├── tasks/
│   └── main.yml
├── templates/
├── tests/
│   └── test.yml
└── vars/
    └── main.yml

Here’s what each part does:

  • defaults/main.yml: This file is where you set the default values for variables used in the role. These are the starting points for the configuration, but they can be changed in playbooks. If you do not specify values in your playbook, the role defaults will be used.
  • files/: This folder stores files that the role copies to the managed nodes. These could be configuration files, scripts, or any other file that should be copied onto servers. This folder is for fixed content that will not change based on variables.
  • handlers/main.yml: Handlers are special tasks that are triggered when other tasks notify them, usually after a change has been made. For example, if a configuration file changes, it might notify a handler to restart a service.
  • meta/main.yml: This file is for metadata about the role, such as the author, the license, and the platform requirements. It also allows you to set dependencies on other roles, which are essential for a proper workflow.
  • tasks/main.yml: The main logic of the role is placed here. The tasks file contains the main series of steps or commands that should be taken on your managed nodes. It might have instructions for installing software, creating directories, or setting up configuration.
  • templates/: This folder holds files that use Jinja2 templating. Templating lets you fill in values based on variables. You might use templates for creating configurations that vary depending on the environment.
  • tests/test.yml: This file is for testing the role. It lets you define test playbooks, which you run to ensure the role works as intended.
  • vars/main.yml: This file stores variables used in the role. These variables can be overridden in playbooks and are useful for storing settings that are specific to a given role.

How to Create an Ansible Role

Let’s dive into the steps for creating your own Ansible role.

Step 1: Setting Up the Role Structure

The best way to start with roles is by using the ansible-galaxy command. This tool will make the base directory structure for you, so you do not need to create every folder yourself. If you want to name your new role “my_webserver”, you can do that using the following command:

ansible-galaxy init my_webserver

This command will create a folder called my_webserver and create the base directory structure shown previously. It will also create a README.md file to describe the function of the role.

Step 2: Defining Variables in defaults/main.yml

Let’s start with the defaults/main.yml file. This is where you define the default variables for your role. These are the settings that your role will use if you do not change them in your playbook. For example, if you are setting up a web server, you might specify the port as follows:

webserver_port: 80

This means that if you do not define the webserver_port in your playbook, the role will default to using port 80.

You can also define other parameters, like:

webserver_package: apache2
webserver_user: www-data
webserver_group: www-data

You might want to add descriptive comments to your variables, so you or other users know the purpose of each variable:

# The port that the webserver should listen on
webserver_port: 80

# The package name of the webserver to install
webserver_package: apache2

# The user that will run the webserver service
webserver_user: www-data

# The group that will run the webserver service
webserver_group: www-data

Step 3: Creating Tasks in tasks/main.yml

The tasks file is the core of your role. This is where you put the commands for setting up the webserver. Let’s start with a simple example:

- name: Install web server package
  apt:
    name: "{{ webserver_package }}"
    state: present
  become: true

This task will install the web server package. Notice how the apt module uses the webserver_package variable, which we defined in our defaults/main.yml.

Next, you might want to configure the web server service. Here’s how you can create a new user for the web server and ensure the service starts:

- name: Create user for webserver
  user:
    name: "{{ webserver_user }}"
    group: "{{ webserver_group }}"
    createhome: false
  become: true

- name: Ensure webserver service is enabled and running
  service:
    name: "{{ webserver_package }}"
    state: started
    enabled: true
  become: true

These tasks use more variables and start the web server service after installation.

Step 4: Adding Handlers in handlers/main.yml

Handlers are tasks that only run when notified. For example, let’s say you need to restart the web server when the config file changes. Here’s how you define a handler in handlers/main.yml:

- name: Restart web server
  service:
    name: "{{ webserver_package }}"
    state: restarted
  become: true

Then, to notify this handler, add a notify line to your tasks where configuration files are modified, like so:

- name: Copy web server configuration file
  template:
    src: webserver.conf.j2
    dest: /etc/apache2/apache2.conf
  become: true
  notify: Restart web server

Now, every time your configuration changes, your handler will make sure your web server is restarted. This avoids the web server being out of sync with its config.

Step 5: Using Templates in templates/

Templates are used to dynamically create config files based on variables. For example, you might have an apache2.conf file in a templates folder:

<VirtualHost *:{{ webserver_port }}>
    ServerAdmin webmaster@localhost
    DocumentRoot /var/www/html

    ErrorLog ${APACHE_LOG_DIR}/error.log
    CustomLog ${APACHE_LOG_DIR}/access.log combined
</VirtualHost>

This template uses the variable webserver_port, defined in defaults/main.yml.
Back in your tasks, make sure to use template when deploying templated configuration files:

- name: Copy web server configuration file
  template:
    src: webserver.conf.j2
    dest: /etc/apache2/apache2.conf
  become: true
  notify: Restart web server

Step 6: Using Files in files/

The files directory contains static files that are copied to managed nodes. For instance, you might have a default webpage named index.html:

<!DOCTYPE html>
<html>
<head><title>Default Page</title></head>
<body><h1>Hello from your Ansible Server!</h1></body>
</html>

You can then use a task to copy this to your server:

- name: Copy default web page
  copy:
    src: index.html
    dest: /var/www/html/index.html
  become: true

Step 7: Metadata in meta/main.yml

The meta/main.yml file is used to document the role and declare its dependencies. A basic meta/main.yml file looks like this:

galaxy_info:
  author: Your Name
  description: Role to install and configure a basic webserver
  min_ansible_version: "2.9"
  license: MIT
  platforms:
  - name: Ubuntu
    versions:
    - all

You might also use it to define dependencies on other roles:

dependencies:
  - role: common_setup
  - role: monitoring_tools

Using an Ansible Role in a Playbook

Using the role you’ve created is simple. In your playbook, you can include the role by specifying the role’s directory path. Assuming the role and the playbook are in the same directory:

- hosts: webservers
  roles:
    - my_webserver

If the role is stored in a different directory, such as ./roles, you can specify the path in this way:

- hosts: webservers
  roles:
    - ./roles/my_webserver

Overriding Role Variables in a Playbook

You can override any default variables defined in your role. Add the new values to your playbook under vars:

- hosts: webservers
  vars:
    webserver_port: 8080
    webserver_package: nginx
  roles:
    - my_webserver

Here, the role will use port 8080 instead of port 80, and it will use nginx instead of apache2.

Using a Role from Ansible Galaxy

Ansible Galaxy is a community resource where you can find and share roles. To use a role from Ansible Galaxy, first install it:

ansible-galaxy install geerlingguy.apache

Then use it in your playbook:

- hosts: webservers
  roles:
    - geerlingguy.apache

You can also specify a version of the role if needed:

- hosts: webservers
  roles:
    - geerlingguy.apache,2.0.0

Best Practices for Ansible Roles

As you continue your journey with Ansible roles, here are some helpful practices to keep in mind:

  • Keep Roles Focused: Each role should handle a single, clear task. This makes your roles simpler to understand and easier to reuse. Avoid having a single role do everything. For example, keep your web server role separate from your database role.

  • Use Variables: Make your roles flexible and reusable by using variables for configurations. Do not hard-code values into tasks.

  • Descriptive Naming: Choose descriptive names for roles and variables, so that they are easy to understand.

  • Avoid Duplication: If you find yourself writing similar code in different roles, try to put it into a shared role that can be used by others.

  • Test Your Roles: Test your roles to make sure they work as intended. Use tools such as Ansible Lint to find potential errors. Use molecule to test your roles in an isolated environment.

  • Document Your Roles: Make sure to document your roles by adding comments to your code. Your README.md should include a quick start and a full list of all the available variables.

  • Use Handlers: Handlers will trigger a service restart only when it’s needed. This can save you resources and prevent unwanted restarts.

  • Share Your Roles: Once you have created a role that works well, consider sharing it in Ansible Galaxy. This will help the community as a whole.

Advanced Topics in Ansible Roles

Here are some advanced concepts you can explore with Ansible roles:

Role Dependencies

Dependencies let you define which other roles must be used first before the current role. If the web server requires a common set of configurations for every server, declare this as a dependency using the meta/main.yml. Now that shared role, will be applied before your main role.

dependencies:
  - role: common_setup

Role Tags

You can use tags to select and run specific tasks within a role. If you add the tag install to your installation task, it can be run individually:

- name: Install web server package
  apt:
    name: "{{ webserver_package }}"
    state: present
  become: true
  tags:
    - install

Then, you can run it using this command:

ansible-playbook my_playbook.yml --tags "install"

Role Variables

You have already seen role defaults and that they can be overridden in playbooks. You can also use variables inside the role structure, making it easy to organize them in various vars files.
Let’s say you need different settings depending on which environment you are deploying your app. Instead of having hardcoded values, you might have files such as vars/dev.yml or vars/prod.yml or even use conditions to set variables dynamically based on the specific environment.

Troubleshooting Common Issues with Ansible Roles

Even with the best planning, you might run into some common issues while using Ansible roles. Here’s how to troubleshoot:

  • Role Not Found: If you get an error about a role not being found, make sure that the role exists in the given path. Check if the path you set in your playbook is correct and if the role is actually placed in that directory.

  • Variable Errors: If you’re getting variable-related errors, make sure that all variables are defined correctly. Ensure the right names are being used in your playbook and your role. Check the defaults to make sure they contain the values that your roles expect, and make sure you override these in your playbook if needed.

  • Permission Errors: If a task fails because of permission errors, use become: true to run the task with root or admin privileges. Check your playbook and make sure this is specified in the correct places.

  • Task Errors: If a task does not do what you expect, use the -v flag when you run your playbook to get more verbose output. Then, you can see the individual steps Ansible takes to perform the task. Review the output and use it to understand what went wrong.

  • Idempotency: Make sure your roles are idempotent. This means that running the same playbook multiple times will not make any further changes if the desired state has already been reached. Ansible must understand what the actual state of the managed node is before applying changes, and make changes only when they are required.

The Future of Ansible Roles

The power of roles will not lessen anytime soon. As Ansible continues to evolve, roles will become even more crucial for managing complex infrastructure. The community will continue to build more diverse and sophisticated roles to make automation tasks more simple. In the future, we may see features such as:

  • More AI integration: AI will simplify role creation. AI might help us create roles that are more accurate and have better best practices implemented in them.

  • Improved testing: Testing roles using isolated environments and automated tests will become even more essential. As more complex roles are created, the need to implement a proper testing environment becomes a requirement.

  • Standardized role formats: A clearer standard for building and organizing roles will enhance reusability across various systems. This will simplify sharing roles, making it easier to import from various community sources.

Getting Started With Your Own Ansible Roles

You have seen how powerful Ansible roles are, and how easy they are to implement. They can bring much needed order to your playbooks, and they can streamline your automation process. Now it’s time to put what you’ve learned into practice. Start small and build up your experience. Begin by creating a simple role, then, grow your expertise and make even more sophisticated roles. You can also use pre-existing roles from the community as inspiration and help, and modify them to your needs. With practice and patience, you’ll be building reusable and modular roles in no time. And you will discover the enormous power of Ansible Roles.