Loading...

Lab 99: Creating and Using Ansible Roles

Convert a growing playbook into a reusable role with tasks, handlers, and templates. Deploy a simple Nginx site using a production-style structure that scales as your automation grows.

ansible roles handlers templates ansible-lint

Scenario

You are managing a growing set of playbooks and the structure is starting to sprawl. To keep things clean and reusable, you migrate to roles and implement a production pattern: tasks install and configure, handlers restart services only when needed, and templates render host-specific content.

Operator context

Roles are the unit of reuse in Ansible. The goal is a layout you can drop into any repo, compose into a site playbook, and lint for consistency.

Objective

  • Create a role skeleton with ansible-galaxy init.
  • Define tasks to install Nginx and deploy a templated landing page.
  • Create a handler that restarts Nginx only when notified.
  • Write a site playbook that applies the role to the web group.
  • Validate structure and syntax with ansible-lint.

Concepts

  • Role anatomy: standard directories (tasks/, handlers/, templates/, defaults/) and predictable entry points.
  • Handlers as change-controlled operations: restart services only when configuration changes.
  • Templates as configuration rendering: use facts and variables to generate host-specific output.
  • Site playbooks as composition: keep orchestration readable by attaching roles to groups.
  • Linting as a quality gate: enforce consistent patterns as roles become shared artifacts.

Walkthrough

Step 1 : Create a new role named nginx.
Command
ansible-galaxy init nginx

This creates the standard role layout. You will primarily work in tasks/main.yml, handlers/main.yml, and templates/.

Step 2 : Define the role tasks to install and run Nginx.
Command
nano nginx/tasks/main.yml
# OR
vim nginx/tasks/main.yml
nginx/tasks/main.yml (baseline)
- name: Install nginx
  package:
    name: nginx
    state: present

- name: Ensure nginx is enabled and running
  service:
    name: nginx
    state: started
    enabled: yes

The package module keeps installs portable across distros. The service task enforces runtime state.

Step 3 : Add a handler to restart Nginx only when notified.
Command
nano nginx/handlers/main.yml
# OR
vim nginx/handlers/main.yml
nginx/handlers/main.yml
- name: restart nginx
  service:
    name: nginx
    state: restarted

Handlers are designed for change-driven operations. You only restart when something actually changed.

Step 4 : Create the HTML template.
Command
nano nginx/templates/index.html.j2
# OR
vim nginx/templates/index.html.j2
nginx/templates/index.html.j2
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Ansible Role Deployed</title>
  </head>
  <body>
    <h1>Deployed by Ansible</h1>
    <p>Host: {{ ansible_hostname }}</p>
  </body>
</html>

This uses ansible_hostname from gathered facts to render host-specific output.

Step 5 : Deploy the template and notify the handler.
Command
nano nginx/tasks/main.yml
# OR
vim nginx/tasks/main.yml
nginx/tasks/main.yml (final)
- name: Install nginx
  package:
    name: nginx
    state: present

- name: Ensure nginx is enabled and running
  service:
    name: nginx
    state: started
    enabled: yes

- name: Deploy custom index.html
  template:
    src: index.html.j2
    dest: /usr/share/nginx/html/index.html
    owner: root
    group: root
    mode: "0644"
  notify: restart nginx

The handler runs only if the template task reports a change.

Step 6 : Create a site playbook that applies the role to the web group.
Command
nano site.yml
# OR
vim site.yml
site.yml
- name: Configure web servers
  hosts: web
  become: yes
  roles:
    - nginx

This gives you a clean entry point. As you add more roles, keep site.yml readable by composing roles per host group.

Step 7 : Run the role-based playbook.
Command
ansible-playbook -i ~/inventory site.yml

First run should show changes. Subsequent runs should converge to mostly ok unless tasks or templates change.

Step 8 : Lint the playbook and role for best practices.
Command
ansible-lint site.yml

Linting is production hygiene. It helps keep roles consistent and reduces surprises when roles get shared across teams.

Common breakpoints

ansible-lint not installed

Install it with your Python tooling or distro package and keep it pinned in your environment so linting is reproducible.

template deploy succeeds but page does not change

Confirm the destination path matches your Nginx document root and that the service is running. Also confirm you are targeting the correct inventory and group.

handler does not run

Handlers only run when notified and a task reports a change. If the template is identical on disk, no restart will occur.

role not found when running site.yml

Ensure the role directory (for example ./nginx) is in the playbook working directory or in your configured roles_path.

Cleanup checklist

Remove Nginx from the web group and keep your repo clean by removing the role directory if you are rebuilding from scratch.

Commands
ansible web -i ~/inventory -m package -a "name=nginx state=absent" -b
ansible web -i ~/inventory -m service -a "name=nginx state=stopped" -b
Success signal

Nginx is absent on web hosts and re-running the playbook converges predictably with minimal changes.

Reference

  • ansible-galaxy init <role> : Creates a standard role directory structure.
  • ansible-playbook -i <inventory> <playbook> : Runs a playbook using the specified inventory.
    • -i <inventory> : Points Ansible at a specific inventory file.
  • ansible-lint <playbook> : Lints a playbook (and referenced roles) for best practices.
  • package: : Installs or removes packages in an idempotent way.
    • state: present : Ensures the package is installed.
    • state: absent : Ensures the package is removed.
  • service: : Manages service state and enablement.
    • state: started : Ensures the service is running.
    • state: restarted : Restarts the service.
    • enabled: yes : Enables the service at boot.
  • template: : Renders a Jinja2 template to a destination on the target host.
    • src: : Template file inside the role templates/ directory.
    • dest: : Destination path on the managed host.
    • notify: : Triggers a handler when the task changes.
  • ansible <group> -i <inventory> -m package -a "<args>" -b : Runs the package module ad-hoc against a target group.
    • -m package : Selects the package module.
    • -a "<args>" : Supplies module args (for example name=nginx state=absent).
    • -b : Enables privilege escalation (become).
  • ansible <group> -i <inventory> -m service -a "<args>" -b : Runs the service module ad-hoc against a target group.
    • -m service : Selects the service module.
    • -a "<args>" : Supplies module args (for example name=nginx state=stopped).
    • -b : Enables privilege escalation (become).