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 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: Organize your playbook into a directory structure with separate files for tasks, handlers, defaults, vars, files, and templates. Creating a Role: Incorporate a role into your playbook with the property or the module for dynamic inclusion. Using Roles: roles include_role - 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. Use to group tasks and to define remediation steps if any task in the block fails. Block and Rescue: block rescue 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 to your playbook. Use one of the following values to control when the debugger is activated: debugger : always activate debugger always : never activate debugger never : activate only on task failure on_failed : activate when a host is unreachable on_unreachable : activate when a task is skipped on_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: : display task details print : update arg value task.args['arg-name'] = 'updated-val' : rerun the task redo : exit the debugger quit 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. 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 , and tasks templates 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 in your browser. http://localhost:2203 Error Handling The aim is to use and to print a custom message on error. block rescue 13. Create a new playbook that should install Apache but use the wrong package name, then add block & rescue properties.Filename: Location: error_test.yaml 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 and dynamically fix an error in a task. debugger 16. Update the playbook and add the property error_test.yaml debugger - 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