Ansible Roles

When dealing with extensive playbooks, it is easier to split the tasks into roles. This also helps in reusing the roles in the future. Roles are a collection of tasks, which can be moved from one playbook to another, can be run independently but only through a playbook file. In other words, roles are a feature of Ansible that facilitate reuse and further promote modularization of configuration.

The role is the primary mechanism for breaking a playbook into multiple files. Therefore, this simplifies writing complex playbooks, and it makes them easier to reuse. The breaking of playbook allows you to logically break the playbook into reusable components.

The ansible-galaxy init command creates a directory structure for a new role that will be developed. The author and name of the role is specified as an argument to the command and it creates the directory structure in the current directory. ansible-galaxy interacts with the Ansible Galaxy website API when it performs most operations. The --offline option permits the init command to be used when Internet access is unavailable.  To create a role using the ansible-galaxy command, we can simply use the below syntax in our terminal:

or

Let’s create a role apache in the control node:

Running this command will give us a basic directory structure in our current working directory and this directory structure will be like the one below:

  • defaults: contains default variables for the role. Variables in default have the lowest priority so they are easy to override.
  • files: contains files which we want to be copied to the remote host. We don’t need to specify a path of resources stored in this directory.
  • handlers: contains handlers which can be invoked by “notify” directives and are associated with service.
  • meta: contains metadata of role like an author, support platforms, dependencies. Defines certain meta data for the role. Relevant meta data includes role dependencies and various role level configurations such as allow duplicates. The meta-directory is entered via a main.ymi
  • tasks: contains the main list of steps to be executed by the role.
  • templates: contains file template which supports modifications from the role. We use the Jinja2 templating language for creating templates.
  • tests: This directory can contain an inventory and test.yml playbook that can be used to test the role.
  • vars: contains variables for the role. Variables in vars have higher priority than variables in defaults directory.

Let’s create tasks for apache role:

Lets’s copy httpd.conf file from apache and save as template httpd.conf.j2:

As we see the template has variable:

ServerAdmin {{ apache_server_admin }}

Now we can create a file with default variables:

And we can create a handler:

 

Now we can create a playbook which use the apache role:

Let’s run the install.yml playbook:

As we see apache service has been installed and started on managedhost1 with http.conf.j2 template:

The varriables in defaults dir can be overwritten in other files:

After we run install.yml playbook ffor the second time:

We can see that ServerAdmin parameter has changed:

It is also possible to use the never syntax:

With this syntax we can use conditionals:

 

Dependencies

Roles may include other roles using the dependencies keyword. Dependent roles are applied prior to the role dependent on them. A role using the same parameters will not be applied more than one time. This can cause complication with role dependencies. Having 'allow_duplicates: true' defined in meta/main.yml within a role will allow the role to be applied more than once.

Let’s create php-webserver role:

We create a tasks:

We modify depedencies section in meta file:

And write a playbook install2 to use php-webserver role:

Let’s run install2 playbook:

As we see role php-webserver has used depedency role apache.

Controlling order of execution
Normally, the tasks of roles execute before the tasks of the playbooks that use them. Ansible provides a way of overriding this default behavior: the pre_tasks and post_tasks tasks. The pre_tasks tasks are performed before any roles are applied. The post_tasks tasks are performed after all the roles have completed.

 

Example 1.

1. Creating the role directory structure
Ansible looks for roles in a subdirectory called roles in the project directory. Roles can also be kept in the directories referenced by the roles_path variable in Ansible configuration files. This variable contains a colon-separated list of directories to search. Each role has its own directory with specially named subdirectories. The following directory structure contains the files that define the motd role.

 

2. Defining the role content
After the directory structure is created, the content of the Ansible role must be defined. A good place to start would be the ROLENAME/tasks/main.yml file. This file defines which modules to call on the managed hosts that this role is applied. The following tasks/main.yml file manages the /etc/motd file on managed hosts. It uses the template module to copy the template named motd.j2 to the managed host. The template is retrieved from the templates subdirectory of the role.

The following command displays the contents of the templates/motd.j2 template of the motd role. It references Ansible facts and a system-owner variable.

The role can define a default value for the system_owner variable. The defaults/main.yml file in the role’s directory structure is where these values are set. The following defaults/main.yml file sets the system_owner variable to
user@host.example.com. This will be the email address that is written in the /etc/motd file of managed hosts that this role is applied to.

 

3. Using the role in a playbook
To access a role, reference it in the roles: section of a playbook. The following playbook refers to the motd role. Because no variables are specified, the role will be applied with its default variable values.

When the playbook is executed, tasks performed because of a role can be identified by the role name prefix. The following sample output illustrates this with the motd: deliver motd file message.

 

 

4. Changing a role’s behavior with variables
Variables can be used with roles, like parameters, to override previously defined, default values. When they are referenced, the variable: value pairs must be specified as well. The following example shows how to use the motd role with a different value for the system_owner role variable. The value specified, someone@host.example.com, will replace the variable reference when the role is applied to a managed host.

Lets run the playbook use-motd-role2.yml:

And check if the motd file changed:

 

Example 2.

In this exercise, you will create two roles that use variables and parameters: myvhost and myfirewall. The myvhost role will install and configure the Apache service on a host. A template is provided that will be used for /etc/httpd/conf.d/vhost.conf: vhost.conf.j2. The myfirewall role installs, enables, and starts the firewalld daemon. It opens the firewall service port specified by the firewall_service variable.

Create the directory structure for a role called myvhost.

Create the main.yml file in the tasks subdirectory of the role. The role should perform four tasks:
• Install the httpd package.
• Start and enable the httpd service.
• Download the HTML content into the virtual host DocumentRoot directory.
• Install the template configuration file that configures the webserver.

Use a text editor to create a file called roles/myvhost/tasks/main.yml. Include
code to use the yum module to install the httpd package. Add additional code to use the service module to start and enable the httpd service. Add another stanza to copy the HTML content from the role to the virtual host DocumentRoot directory. Use the copy module and include a trailing slash after the source directory name. This will cause the module to copy the contents of the html directory immediately below the destination directory (similar to rsync usage). The ansible_hostname variable will expand to the short host name of the managed host. Add another stanza to use the template module to create /etc/httpd/conf.d/vhost.conf on the managed host. It should call a handler to restart the httpd daemon when this file is updated. The file contents should look
like the following:

Create the handler for restarting the httpd service. Use a text editor to create a file called roles/myvhost/handlers/main.yml. Include code to use the service module. The file contents should look like the following:

Create the HTML content that will be served by the webserver. The role task that called the copy module referred to an html directory as the src.
Create this directory below the files subdirectory of the role. Create an index.html file below that directory with the contents: “simple index”. Be
sure to use this string verbatim because the grading script looks for it.

Write a playbook that uses the role, called use-vhost-role.yml. It should have the following content:

Run the playbook. Review the output to confirm that Ansible performed the actions on the web server,

Run ad hoc commands to confirm that the role worked. The httpd package should be installed and the httpd service should be running.

Run ad hoc commands to confirm that the role worked. The httpd package should be installed and the httpd service should be running.

Use a web browser on managedhost2 to check if the web content is available locally. It should succeed and display content.

Check if you can connect from controlnode to the webserver on managedhost2.example.com:

The issue is due to firewall on managedhost2 don’t allow for http connection.  So we need to configure firewall on managedhost2.

Create the role directory structure for a role called myfirewall:

Create the main.yml file in the tasks subdirectory of the role. The role should perform three tasks:
• Install the firewalld package.
• Start and enable the firewalld service.
• Open a firewall service port.

Include code to use the yum module to install the firewalld package.  Add additional code to use the service module to start and enable the firewalld service. Add another stanza to use the firewalld module to immediately, and persistently, open the service port specified by the firewall_service variable. The file contents should look like the following:

Create the handler for restarting the firewalld service. Use a text editor to create a file called roles/myfirewall/handlers/main.yml. Include code to use the service module. The file contents should look like the following:

Create the file that defines the default value for the firewall_service variable. It should have a default value of ssh initially. We will override the value to open the port for http when we use the role in a later step. The file myfirewall/defaults/main.yml should contain the following content:

Modify the myvhost role to include the myfirewall role as a dependency, then retest the modified role. Use a text editor to modify a file, called myvhost/meta/main.yml, that makes  myvhost depend on the myfirewall role. The firewall_service variable should be set to http so the correct service port is opened. By using the role in this way, the default value of ssh for firewall_service will be ignored. The explicitly assigned value of http will be used instead. The dependencies section in the file should look like the following:

Run the playbook again. Confirm the additional myfirewall tasks are successfully executed.

Now we can connect from controlnode to webserver on managedhost2:

 

Example 3.

In this lab, you will create two roles that use variables and parameters: student.myenv and myapache. The myenv role customizes a system with required packages and a helpful script for all users. It will customize a user account, specified by the myenv_user variable, with a profile picture and an extra command alias in their ~/.bashrc file. The myapache role will install and configure the Apache service on a host. Two templates are provided which will be used for the /etc/httpd/conf/httpd.conf and the /var/www/html/index.html files: apache_httpdconf.j2 and apache_indexhtml.j2 respectively.

Create directories to contain the Ansible roles. They should be contained in the myenv and myapache directories below ~/ansible/roles directory:

Install the mkcd.sh.j2 file as a template for the myenv role.
The lab setup script copied the file to the lab-roles working directory. Move it to the roles/student.myenv/templates/ subdirectory.

Define student.myenv role tasks to perform the following steps:
• Install packages defined by the myenv_packages variable.
• Copy the standard profile picture to the user’s home directory as ~/profile.png. Use the myenv_user variable for the user name.
• Add the following line to the user’s ~/.bashrc file: alias tree=’tree -C’ . Use the
myenv_user variable for the user name. Hint: The lineinfile module might be well suited for this task.
• Install the mkcd.sh.j2 template as /etc/profile.d/mkcd.sh. It should have
user:group ownership of root:root and have -rw-r–r– permissions.
The role should fail with an error message when the myenv_user variable is an empty string.
Modify roles/myenv/tasks/main.yml so that it contains the following:

Define the myenv_packages variable for the myenv role so it contains the following packages: git, tree, and vim-enhanced.
Create roles/myenv/vars/main.yml with the following contents:

Assign the empty string as the default value for the myenv_user variable.
Create roles/myenv/defaults/main.yml with the following contents

Create a playbook, called use-myenv.yml, that runs on all hosts. It should use the myenv role, but do not set the myenv_user variable. Test the myenv role and confirm that it fails.

Run the myenv.yml playbook. Check the ansible-playbook output to make sure it fails.

Update the myenv.yml playbook so that it uses the student.myenv role, setting the myenv_user variable to student. Test the myenv role and confirm that it works properly.

Run the use-myenv.yml playbook. Check the ansible-playbook output to make sure the tasks ran properly.

Create a handler that will restart the httpd service. Modify roles/myapache/handlers/main.yml so that it contains the following:

Define myapache role tasks to perform the following steps:
• Install the httpd and firewalld packages.
• Copy the apache_httpdconf.j2 template to /etc/httpd/conf/httpd.conf. The
target file should be owned by root with -r–r–r– permissions. Restart Apache using the handler created previously.
• Copy the apache_indexhtml.j2 template to /var/www/html/index.html. The
target file should be owned by root with -r–r–r– permissions.
• Start and enable the httpd and firewalld services.
• Open port 80/tcp on the firewall.
Package installation should always occur when this role is used, but the other tasks should only occur when the apache_enable variable is set to true. The role should restart the Apache service when the configuration file is updated.
Modify roles/myapache/tasks/main.yml so that it contains the following:

Create the default variable values for the myapache role. The apache_enable variable should have a default value of false.
Modify roles/myapache/defaults/main.yml so it contains the following:

Create a playbook, called use-myapache.yml, that runs on serverb. It should use the myapache role, but use the default value of the apache_enable variable. Test the myapache role and confirm that it installs the packages, but does not deploy the web server.

Run the use-myapache.yml playbook. Check the ansible-playbook output to make sure it installs the needed packages, but skips the remaining tasks.

Modify the apache.yml playbook so that it uses the myapache role, setting the
apache_enable variable to true. Test the myapache role and confirm that it works properly.

Run the use-myapache.yml playbook. Check the ansible-playbook output to make sure the tasks ran properly.

Use a web browser to confirm that serverb is serving web content.