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 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:
roles
property or the include_role
module for dynamic inclusion.- hosts: all
roles:
- role: my_custom_role
Error Handling
Ansible provides ways to define custom failure conditions and manage the playbook execution flow when encountering errors.
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 debuggernever
: never activate debuggeron_failed
: activate only on task failureon_unreachable
: activate when a host is unreachableon_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 detailstask.args['arg-name'] = 'updated-val'
: update arg valueredo
: rerun the taskquit
: exit the debuggerOur 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.
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
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.
main.yaml
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.
main.yaml
ansible_learn/roles/nginx/handlers
- name: Restart NGINX
listen: "restart nginx"
ansible.builtin.service:
name: nginx
state: restarted
9. Create the playbook.
nginx_setup.yaml
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.
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.yaml
Location: 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
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
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.