Ansible Best Practices for Writing Clean Playbooks

By Anurag Singh

Updated on Nov 16, 2024

Ansible Best Practices for Writing Clean Playbooks

In this tutorial, we'll explain what is the Ansible best practices for writing clean playbooks that covers best practices, structuring techniques, and tools like ansible-lint for enforcing standards. This guide is structured to provide deep insights, ensuring your audience can learn effectively and apply it to their Ansible playbooks.

Ansible is a powerful automation tool that helps streamline infrastructure management. However, writing Ansible playbooks that are maintainable, readable, and efficient requires following certain best practices. This guide will provide a deep dive into writing clean playbooks, structuring them for maintainability, using roles, tags, and includes, and leveraging ansible-lint for quality assurance.

Ansible Best Practices for Writing Clean Playbooks

1. Structuring Playbooks for Readability and Maintainability

a. Use YAML Best Practices

Ansible playbooks are written in YAML, so adhering to YAML best practices is crucial:

Use 2 spaces for indentation. Avoid tabs, as YAML is whitespace-sensitive.

Clearly define your hosts, vars, and tasks.

Use - consistently for lists, such as task definitions.

Example:

---
- name: Ensure the web server is installed and running
  hosts: webservers
  become: true
  tasks:
    - name: Install Nginx
      ansible.builtin.yum:
        name: nginx
        state: present
    - name: Start Nginx service
      ansible.builtin.service:
        name: nginx
        state: started

b. Use Descriptive Names

Each playbook, play, and task should have clear, descriptive names:

  • Avoid generic names like "Install software." Instead, be specific: "Install and start Nginx web server."
  • This makes troubleshooting easier when you read through logs or playbook outputs.

c. Organize Variables

Variables should be structured to maintain clarity:

  • Use a vars directory or file to manage variables centrally.
  • Use group_vars and host_vars for group-specific or host-specific configurations.
  • Keep sensitive variables in encrypted vault files using ansible-vault.

d. Use Comments Generously

Well-placed comments explain why a task is performed, not just what it does. Add comments to complex parts of the playbook to help future maintainers understand your logic.

e. Use Conditionals and Loops Wisely

Ansible allows you to use conditionals and loops. Use these features to avoid redundant tasks, but don’t overcomplicate the playbook:

  • Use when conditions to make tasks conditional.
  • Use with_items or loop to iterate over items.

Example:

- name: Install multiple packages
  ansible.builtin.yum:
    name: "{{ item }}"
    state: latest
  loop:
    - httpd
    - php
    - mariadb-server
  when: ansible_os_family == "RedHat"

2. Using Roles, Tags, and Includes to Organize Playbooks

a. Using Roles for Better Structure

Roles are a key component of organizing Ansible playbooks. They provide a modular structure:

A role can contain tasks, handlers, variables, templates, and files related to a specific functionality.

Use the ansible-galaxy init <role_name> command to create a skeleton for a role.
Each role should have a specific purpose, such as installing Nginx, configuring a firewall, or deploying an application.

Role Directory Structure:

my_role/
├── defaults/
│   └── main.yml
├── files/
├── handlers/
│   └── main.yml
├── tasks/
│   └── main.yml
├── templates/
├── vars/
│   └── main.yml
└── meta/
    └── main.yml

b. Using Tags for Task Selection

Tags allow you to run specific parts of a playbook without executing the entire playbook:

  • Use tags to categorize tasks by their function or purpose, such as setup, deploy, configure, etc.
  • Use the --tags option to run a playbook with specific tags or --skip-tags to exclude tasks.

Example with Tags:

tasks:
  - name: Install Apache
    ansible.builtin.yum:
      name: httpd
      state: present
    tags: install

  - name: Start Apache service
    ansible.builtin.service:
      name: httpd
      state: started
    tags: config

Command to Run Tagged Tasks:

ansible-playbook site.yml --tags "install"

c. Using Includes and Imports for Readability

To split large playbooks into smaller, more manageable pieces, use includes or imports:

  • include_tasks dynamically includes tasks at runtime.
  • import_tasks statically includes tasks during the playbook parsing phase.
  • include_vars to include variables from external files.

Example of Task Inclusion:

- name: Configure Web Server
  import_tasks: tasks/webserver.yml

- name: Install Database
  include_tasks: tasks/database.yml

3. Tips for Using ansible-lint to Enforce Best Practices

ansible-lint is a tool that enforces Ansible playbook best practices. It provides feedback on syntax errors, security risks, and style violations.

a. Install ansible-lint

You can install ansible-lint using pip:

pip install ansible-lint

b. Use a .ansible-lint Configuration File

To customize the behavior of ansible-lint, create a .ansible-lint configuration file in your project directory:

---
skip_list:
  - '403'  # Skip specific rule by ID
warn_list:
  - '106'  # Warnings for rules
rulesdir: ~/.config/ansible-lint/rules

c. Run ansible-lint Against Playbooks

Execute ansible-lint to check your playbooks for best practices and errors:

ansible-lint playbook.yml

Review and resolve any issues reported. Some key areas ansible-lint covers:

  • Indentation issues.
  • Task names must be unique.
  • Unused or undefined variables.
  • Deprecated modules or parameters.
  • Unquoted when conditions.

d. Custom Lint Rules

You can add custom rules to suit your project’s needs. Create a directory for custom rules and reference it in your .ansible-lint file. Each rule is a Python script that extends Ansible’s linting capabilities.

e. Integrating ansible-lint in CI/CD Pipelines

Integrate ansible-lint into your Continuous Integration (CI) pipeline to enforce quality checks:

Use tools like GitHub Actions, GitLab CI, or Jenkins to automatically run ansible-lint on pull requests.

  • This ensures playbook quality without manual reviews.
  • Summary of Key Best Practices
  • Use YAML best practices for clarity.
  • Leverage roles to modularize playbooks.
  • Use tags for targeted playbook execution.
  • Split complex playbooks with includes and imports.
  • Use ansible-lint to enforce quality standards.
  • Comment your code generously and use meaningful task names.

By following these guidelines, you'll write cleaner, more maintainable Ansible playbooks that are easier for teams to manage and scale.

Checkout our dedicated servers India, Instant KVM VPS, and Web Hosting India