DEV Community

Cover image for Ansible 101: Modularization & Debugging
Javel Rowe
Javel Rowe

Posted on • Originally published at javel.dev

Ansible 101: Modularization & Debugging

Overview

When playbooks grow in size, they become challenging to manage. Modularization helps by separating logic from data, making playbooks cleaner and more maintainable. As you introduce these abstractions, it's crucial to integrate robust error handling and debugging techniques to ensure reliability.

Roles

Diagram showing roles directory structure

Roles in Ansible are units of organization that allow you to group related tasks, variables, files, and templates. They make your playbooks modular. Here’s how to create and use them:

  • Creating a Role: Organize your playbook into a directory structure with separate files for tasks, handlers, defaults, vars, files, and templates.

  • Using Roles: Incorporate a role into your playbook with the roles property or the include_role module for dynamic inclusion.

- hosts: all
  roles:
    - role: my_custom_role

Enter fullscreen mode Exit fullscreen mode

Error Handling & Debugging

Error Handling

Ansible provides ways to define custom failure conditions and manage the playbook execution flow when encountering errors.

  • Block and Rescue: Use block to group tasks and rescue to define remediation steps if any task in the block fails.
tasks:
  - block:
      - name: Attempt to do something
        command: /bin/false
    rescue:
      - name: This will run on failure
        command: /bin/true
Enter fullscreen mode Exit fullscreen mode

Debugger

Ansible’s debugger lets you interactively troubleshoot tasks that fail during playbook execution. It can be invoked by adding debugger to your playbook. Use one of the following values to control when the debugger is activated:

  • always: always activate debugger

  • never: never activate debugger

  • on_failed: activate only on task failure

  • on_unreachable: activate when a host is unreachable

  • on_skipped: activate when a task is skipped

- name: Install NGINX Web Server
  hosts: webservers
  tasks:
   - name: Install NGINX
     ansible.builtin.apt:
      name: httpd
      state: present
     debugger: on_failed

Enter fullscreen mode Exit fullscreen mode

Core commands when working with the debugger include:

  • print: display task details

  • task.args['arg-name'] = 'updated-val': update arg value

  • redo: rerun the task

  • quit: exit the debugger

Practice

Our aim is to modularize the process of installing NGINX and updating the homepage. We also want to handle an error caused by incorrect package name and dynamically fix it.

  1. Clone the repo
git clone https://github.com/perplexedyawdie/ansible-learn.git
Enter fullscreen mode Exit fullscreen mode

2. Spin up the environment using docker-compose

docker compose up -d --build
Enter fullscreen mode Exit fullscreen mode

3. SSH into the Ansible server

ssh -o StrictHostKeyChecking=no -o NoHostAuthenticationForLocalhost=yes root@localhost -p 2200# password: test123
Enter fullscreen mode Exit fullscreen mode

4. Change directory to ansible_learn

cd ansible_learn
Enter fullscreen mode Exit fullscreen mode

Roles & Modularization

5. Create the roles directory with the relevant folders. For this task, we'll only require tasks , templates and handlers

cd ansible_learn

mkdir -p roles/nginx/tasks roles/nginx/templates roles/nginx/handlers

Enter fullscreen mode Exit fullscreen mode

6. Create the template that will generate the homepage.

Filename: index.html.j2

Location: ansible_learn/roles/nginx/templates

<html>
<head>
  <title>Welcome to {{ ansible_facts['os_family'] }}</title>
</head>
<body>
  <h1>Server running on {{ ansible_facts['distribution'] }}</h1>
</body>
</html>

Enter fullscreen mode Exit fullscreen mode

7. Create the tasks that will install NGINX, generate the homepage, and copy it to the server.

  • Filename: main.yaml

  • Location: ansible_learn/roles/nginx/tasks

- name: Install NGINX for Debian-based systems   
  ansible.builtin.apt:
   name: nginx
   state: present
- name: Create Homepage with Jinja2 Template for NGINX
  ansible.builtin.template:
   src: index.html.j2
   dest: /var/www/html/index.html
   mode: '644'
  notify: restart nginx

Enter fullscreen mode Exit fullscreen mode

8. Create the handler that will restart NGINX upon update.

  • Filename: main.yaml

  • Location: ansible_learn/roles/nginx/handlers

- name: Restart NGINX
  listen: "restart nginx"
  ansible.builtin.service:
   name: nginx
   state: restarted
Enter fullscreen mode Exit fullscreen mode

9. Create the playbook.

  • Filename: nginx_setup.yaml

  • Location: ansible_learn

- name: Install NGINX
  hosts: all
  roles:
   - role: nginx    

Enter fullscreen mode Exit fullscreen mode

10. Run the linter

ansible-lint nginx_setup.yaml

Enter fullscreen mode Exit fullscreen mode

11. Execute the playbook.

ansible-playbook --key-file /root/.ssh/id_rsa_ansible -u root -i inventory.yaml nginx_setup.yaml

Enter fullscreen mode Exit fullscreen mode

12. Confirm deployment by visiting http://localhost:2203 in your browser.

Error Handling

The aim is to use block and rescue to print a custom message on error.

13. Create a new playbook that should install Apache but use the wrong package name then add block & rescue properties.Filename: error_test.yamlLocation: ansible_learn

- name: Install Apache on Ubuntu
  hosts: all
  tasks:
    - name: Install Apache for Debian-based systems
      block:
        - name: Installer
          ansible.builtin.apt:
            name: httpd
            state: present
      rescue:
        - name: Installer errors
          ansible.builtin.debug:
            msg: "Error installing Apache on {{ ansible_facts['distribution'] }}"
Enter fullscreen mode Exit fullscreen mode

14. Run the linter

ansible-lint error_test.yaml

Enter fullscreen mode Exit fullscreen mode

15. Execute the playbook

ansible-playbook --key-file /root/.ssh/id_rsa_ansible -u root -i inventory.yaml error_test.yaml
Enter fullscreen mode Exit fullscreen mode

Debugging

The aim is to use the debugger and dynamically fix an error in a task.

16. Update the error_test.yaml playbook and add the debugger property

- name: Install Apache on Ubuntu
  hosts: all
  debugger: on_failed  
  tasks:
    - name: Install Apache for Debian-based systems
      block:
        - name: Installer
          ansible.builtin.apt:
            name: httpd
            state: present
      rescue:
        - name: Installer errors
          ansible.builtin.debug:
            msg: "Error installing Apache on {{ ansible_facts['distribution'] }}"
Enter fullscreen mode Exit fullscreen mode

17. Run the linter

ansible-lint error_test.yaml

Enter fullscreen mode Exit fullscreen mode

18. Execute the playbook

ansible-playbook --key-file /root/.ssh/id_rsa_ansible -u root -i inventory.yaml error_test.yaml

Enter fullscreen mode Exit fullscreen mode

19. In the interactive debugger, run the following commands to update the package name

# display all args in the failed task, we are interested in the name, since that contains the name of the package
p task.args

# update the package name. When installing Apache on Ubuntu, we use apache2
task.args['name'] = 'apache2'

# rerun the task
redo

# exit the debugger
quit
Enter fullscreen mode Exit fullscreen mode

20. Confirm successful installation of Apache

ssh -i /root/.ssh/id_rsa_ansible root@server1 apache2 -V

Enter fullscreen mode Exit fullscreen mode

Recap

Great effort! We learned how to create playbooks that are easier to extend and maintain along with how to handle errors and debug tasks to ensure reliability. For the final tutorial, we'll look at how to safely store sensitive data using Ansible Vault. Til then, take care! 👋

Top comments (0)