paint-brush
Ansible 101: Modularization & Debuggingby@cloudkungfu
191 reads

Ansible 101: Modularization & Debugging

by cloud-kung-fuJanuary 31st, 2024
Read on Terminal Reader
Read this story w/o Javascript
tldt arrow

Too Long; Didn't Read

By understanding roles, you can create modular playbooks that are easier to extend and maintain. With error handling and debugging, you can ensure that your playbooks are reliable and easier to troubleshoot.
featured image - Ansible 101: Modularization & Debugging
cloud-kung-fu HackerNoon profile picture

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

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

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

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 an incorrect package name and dynamically fix it.

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

2. Spin up the environment using docker-compose

docker compose up -d --build

3. SSH into the Ansible server

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

4. Change directory to ansible_learn

cd ansible_learn

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

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>

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
   

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

9. Create the playbook.

  • Filename: nginx_setup.yaml
  • Location: ansible_learn
- name: Install NGINX
  hosts: all
  roles:
   - role: nginx    

10. Run the linter

ansible-lint nginx_setup.yaml

11. Execute the playbook.

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

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'] }}"

14. Run the linter

ansible-lint error_test.yaml

15. Execute the playbook

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

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'] }}"

17. Run the linter

ansible-lint error_test.yaml

18. Execute the playbook

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

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

20. Confirm successful installation of Apache

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

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! 👋


Also published here.