Best Practices for Writing Ansible Playbooks

An Ansible playbook is a collection of tasks written in YAML format that defines the desired state of a system or infrastructure. These tasks are organized in a specific order and executed sequentially on target hosts by the Ansible automation engine. Playbooks serve as the core mechanism for automating IT tasks, allowing users to define, execute, and manage infrastructure configurations, application deployments, and system operations in a repeatable and consistent manner. They facilitate automation by eliminating manual intervention, promoting code reusability, and enabling collaboration among team members.

As your use of Playbooks increase in number and complexity, you may encounter challenges around efficient writing, organization, tracking changes, debugging, security and reliability of these playbooks.

This article shares best practices for developing and maintaining reusable and high-quality Ansible Playbooks. These best practices include prioritizing simplicity and readability to ensuring idempotency and leveraging variables and secrets wisely. Additionally, they also include strategies for error handling, conditional execution, and security considerations which are important for maintaining integrity of the playbooks.

This article assumes you have hands-on experience working with Ansible, especially playbooks.

Keep It Simple and Readable

Simple playbooks are easy to read and understand providing a clear description of what the playbook does. Using a standardized approach to writing them makes it more readable, for example descriptive names. Here are some best practices to keep it simple and readable:

  • Use YAML Linting: YAML’s strict indentation can be tricky. Using a linter can help avoid syntax errors and maintain consistency. You can use an online tool such as https://www.yamllint.com/ for this.
  • Descriptive Names: Use meaningful names for playbooks, tasks, variables, and files. This enhances readability and makes maintenance easier. In the example below, the task is named “Install Apache web server” clearly indicating its purpose.
    - name: Install Apache web server 
      apt: 
        name: apache2 
        state: present 

Use Version Control

Version control systems (VCS), such as Git, provide centralized code management and change tracking, and should be applied to Ansible playbooks.

  • Track Changes: Keep your playbooks in a version control system (VCS), like Git. This allows you to track changes, collaborate with others, and revert to previous versions if necessary.
git commit -m "Added task to install Apache web server"

Organize Your Playbooks

Organizing your playbooks enhances clarity, simplifies management, and promotes reusability. Structuring your playbooks in a logical manner ensures efficient navigation and maintenance. Here are some of the best practices to organize playbooks:

  • Roles and Includes: Use roles to group tasks that achieve a part of your overall goal. This not only makes your playbook more organized but also reusable and easier to manage.
- name: Include tasks from common role 

  include_role: 

    name: common 
  • Directory Structure: Adhere to a recommended directory structure for your projects e.g., separate directories for roles, playbooks, vars, etc.
my_project/ 

├── playbooks/ 

│   ├── site.yml 

├── roles/ 

│   ├── common/ 

│       ├── tasks/ 

             └── main.yml 

         ├── handlers/ 

│          └── main.yml 

         ├── templates/ 

│          └── ntp_conf.j2

Make Playbooks Idempotent

Ensuring idempotency in your Ansible playbooks is crucial for maintaining a stable and predictable infrastructure. Idempotency means that running the playbook multiple times produces the same result without causing unexpected changes or failures. While Ansible’s design inherently promotes idempotency, it’s essential to verify and test your playbooks to confirm their behavior.

Here are some strategies to make your playbooks idempotent, with an example playbook:

- name: Ensure Nginx is installed and configured 

  hosts: all 

  become: yes 

  tasks: 

    - name: Check if Nginx is installed 

      command: nginx -v 

      register: nginx_version 

      ignore_errors: true 

  

    - name: Install Nginx if not present 

      apt: 

        name: nginx 

        state: present 

      when: nginx_version.rc != 0 
  • Check the Current State: Before making any changes, ensure that you check the current state of the system. Use Ansible modules like command, shell, or appropriate lookup plugins to gather information about the system’s current configuration.
    The first task in the playbook above checks if Nginx is already installed by attempting to run nginx -v. We have used ignore_errors:true to prevent the playbook from failing if Nginx is not found.
  • Use Stateful Modules: Ansible provides modules that are inherently idempotent, such as apt, yum, copy, file, service, etc. These modules automatically check the current state and only make changes if necessary.
  • Use Conditional Statements: Incorporate conditional statements (when clause) in your tasks to execute them only when certain conditions are met. This prevents unnecessary execution of tasks and ensures idempotency.

It’s important to note that not all playbooks and modules behave idempotency by default. Testing playbooks in a sandbox environment before running them in production, preferably multiple times, ensures that idempotency is maintained and unintended changes are avoided.

Check mode is an option in Ansible that allows you to simulate the execution of playbooks without making any changes to the target systems. When you run Ansible playbooks in check mode, it performs a dry run where it shows you what changes would be made, but it does not apply those changes.

To enable check mode, you can use the –check or -C flag when running the ansible-playbook command. 

ansible-playbook playbook.yml --check

Use Variables and Secrets Wisely

Effectively managing variables and secrets in Ansible Playbook, is essential for maintaining flexibility, security, and scalability in your automation workflows. Here are some best practices to follow:

  • Parameterization: Define variables for parts of your playbook that vary between environments (e.g., development, staging, production). This makes your playbooks more flexible.
- name: Copy config file with environment-specific values  

  template: 

    src: "{{ environment }}_config.j2" 

    dest: /etc/myapp/config.ini 
  • Secret Management: Use tools like Ansible Vault to encrypt secrets (passwords, API keys) instead of hardcoding them into your playbooks.
ansible-vault encrypt secret.yml

Error Handling and Debugging

Error handling and debugging are crucial aspects of ensuring smooth playbook execution and troubleshooting issues effectively. Here are some important practices to follow:

  • Fail Gracefully: Use error handling to manage task failures elegantly. This can include retries or specific tasks that run on failure.
- name: Restart Apache web server 

  service: 

    name: apache2 

    state: restarted 

  ignore_errors: yes 
  • Debugging: Use the debug module to print variables or messages, helping you understand the playbook’s flow and troubleshoot issues.
- name: Debug message 

  debug: 

    msg: "Value of variable: {{ my_variable }}" 
  • Conditional Execution: Use conditions to skip tasks that don’t need to be run, based on certain criteria, optimizing runtime.
- name: Update package cache only if needed 

  apt: 

    update_cache: yes 

  when: ansible_os_family == 'Debian' 

Keep Security in Mind

When working with Ansible, keeping security considerations in mind is of utmost importance to safeguarding your infrastructure and sensitive data. Here are some important practices to follow:

  • Minimal Privileges: Run playbooks with the minimum necessary privileges. Avoid running everything as root unless necessary.
- name: Restart Apache web server 

  service: 

    name: apache2 

    state: restarted 

  become: yes 
  • Avoid hardcoding sensitive information such as passwords, API keys, or SSH private keys directly into your playbooks.
  • Utilize Ansible Vault to encrypt sensitive data and securely store credentials.
  • Use environment variables or external credential stores where possible to dynamically inject credentials at runtime.
  • Enable encryption for communication between Ansible control nodes and managed nodes using SSH or TLS.
  • Encrypt sensitive data at rest and in transit to protect against eavesdropping and data breaches.
  • Implement data masking techniques to obfuscate sensitive information in log files and output.
  • Regular Audits: Regularly review and audit your playbooks and inventory for security compliance and best practices adherence.

Document Your Playbooks

Documenting your Ansible playbooks is essential for ensuring clarity, maintainability, and collaboration among team members. One of the significant benefits of documentation is that it enhances the understanding of the playbook’s purpose, logic and functionality, leading to smoother operations and troubleshooting.

Here are two major ways to document Ansible playbooks:

  • Comments: Use comments to explain “why” something is done a certain way, especially for complex tasks or decisions.
# Install Apache web server 

- name: Install Apache web server 

  apt: 

    name: apache2 

    state: present 
  • README Files: Include README files in your roles or playbook directories to explain the purpose, structure, and any prerequisites.

Continuous Learning and Community Engagement

Continuous learning plays a crucial role in writing playbooks and staying ahead in automation practices. Here are some key points on why continuous learning is beneficial and best practice to follow:

  • Stay Updated: Ansible undergoes frequent updates and improvements to meet evolving IT needs. By staying updated, you can leverage new features, modules, and enhancements to optimize your automation workflows.
  • Engage with the Community: The Ansible community is a vast resource. Participate in forums, read blogs, and contribute to open-source projects to learn from others’ experiences.

Ansible Tower Best Practices

  • Use Source Control: When you’re working with Ansible Tower, it’s tempting to keep all your playbooks directly on the Tower server. But it’s a best practice to store them in ‘source control’. Storing your playbooks in source control makes sharing them with your team or other parts of your setup easy. It’s like having a central hub where everyone can access and work on the playbooks together.

    Example: Utilize source control repositories like GitLab or GitHub to store Ansible playbooks and roles centrally, facilitating collaboration and version control across teams.

  • Leverage Credentials Management: Store sensitive information such as passwords and API tokens securely in AWX/Tower’s credential store and reference them within your playbooks.

    Example: Instead of hardcoding database credentials in a playbook, utilize credentials stored in AWX/Tower and reference them in tasks that require database access.

  • Integrate with Inventories and Hosts: Dynamically manage hosts and inventories using AWX/Tower’s inventory sources to ensure scalability and flexibility in your automation workflows.

    Example: Use dynamic inventory scripts to automatically populate inventory based on information retrieved from cloud providers, virtualization platforms, or other sources.

By implementing these best practices, you can create Ansible playbooks that are efficient, maintainable, and secure. It’s essential to bear in mind that the goal of automation is to streamline processes. Upholding cleanliness, organization, and comprehensive documentation within your playbooks will ensure their ongoing effectiveness as essential components of your automation infrastructure.

By Madhuri Jha

Leave a Comment

Your email address will not be published.