Blogs

Best Practices for Writing Ansible Playbooks

By Chao Ho Chu posted 2 hours ago

  

Introduction

Hitachi Ansible modules for VSP One Block and VSP One Object provide a simplified and automated way to configure and manage Hitachi storage systems, enabling seamless integration with Ansible playbooks and workflows.  These modules are used to perform specific tasks within playbooks.  Below are some best practices for creating playbooks with Hitachi’s Ansible modules. 

  • Encrypt sensitive data
  • Simplify tasks with Ansible roles
  • Organize tasks and handlers
  • Use dynamic inventory
  • Documentation
  • Test and validate playbooks

The sections below will dive into each of these best practices.

Encrypt Sensitive Data

Sensitive data used within Ansible playbooks should be encrypted and securely stored using Ansible Vault. Ansible Vault is a built-in feature of Ansible that protects sensitive information such as storage serial numbers, IP addresses, usernames, passwords, API tokens and secret keys. Encrypting sensitive data ensures it is not stored in plain text and reduces security risks in automation workflows.

See the following recommendations to securely manage sensitive data:

  • Create a YAML file containing required sensitive variables (for example: storage serial, address, username, password). Avoid storing sensitive information directly inside playbooks.  See the following YAML file as an example.
  • Use ansible-vault to encrypt the file.  The contents of the YAML file will be AES256 encrypted.
    ansible-vault encrypt ansible_vault_storage_var.yml
  • In the playbook, reference the encrypted file with the following:
    vars_files:
      - ./ansible_vault_vars/ansible_vault_storage_var.yml
  • When running the playbook, use the --ask-vault-pass flag
    ansible-playbook playbook.yml --ask-vault-pass
  • For non-interactive execution (such as CI/CD pipelines), use a vault password file with --vault-password-file.  Where the password file can be hidden file in local directory as secret.pass
  • For multi-environment deployments, use vault IDs that manage separate vault passwords.
  • Avoid hardcoding vault password files in project directories.  For improved security, use environment variables configuration or secure CI/CD secret mechanisms.
  • Alternatively, you encrypt individual texts instead of entire files.  These encrypted text can be used in YAML files. To encrypt individual texts:
    ansible-vault encrypt_string 'MyPassword' --name 'vsp_password'
  • Use no_log: true in tasks that handle credentials to prevent sensitive data from appearing in logs.
  • Ensure secure communication with VSP One Block systems by enabling TLS certificate validation:  validate_certs: true
  • Follow the principle of least privilege by using dedicated automation service accounts with minimal required permissions.
  • For enterprise deployments, consider integrating with external secret management solutions such as CyberArk, HashiCorp Vault, cloud based key management services to centrally manage and rotate credentials.

Simplify Tasks with Ansible Roles

Use roles to organize your playbook content. Roles allow you to group tasks, handlers, variables, and other components logically.  This also makes the code for Ansible tasks easily reusable across different playbooks and/or projects.

See the following playbook cleanup_gad_pairs_pvol.yml using the ansible role: hv_vsp_host_volume_deletion

Playbook calls the hv_vsp_host_volume_deletion role

image

The hv_vsp_host_volume_deletion role consolidates 6 tasks in a simple role called from the playbook.

Multiple tasks are grouped in a role.  In this case, this role performs numerous get tasks (system info primary and secondary system), delete volumes after migration, and others.   Instead of rewriting the code for these tasks, in other playbooks, the role can be reused.  Additionally, the playbook that uses these roles, the code is greatly simplified.

Task and Handler Organization

Write simple and readable tasks.  Avoid complex logic within tasks whenever possible.  The helps to simplify sustainability and maintenance efforts for the playbook.  Some examples to avoid are:

  • Deep nesting
  • Excessive use of inline parsing
  • Multiple branches
  • Use of mutable facts

Below is a deeper look at a negative example with complex logic to find GAD pairs by primary volume ID.

image
In this example, there is multi-level nesting of tasks to filer for specific GAD pairs.  It makes the playbook unnecessarily long and complicated.  Resulting in a playbook that is harder to troubleshoot and maintain.
Below is a example of a playbook using built-in filtering of the Ansible task.
image
This example, we are using the spec: to identify GAD pairs.  The playbook tasks are much shorter and easier to troubleshoot and maintain.

Organize Variables and Vaults

Use separate files for variables and organize them by environment or role.  This helps to understand and modify code quickly.

Below is an example layout with some descriptions.

image

Dynamic Inventory

Whenever possible, use dynamic inventory with Ansible.  Use Ansible to query an external system (cloud, API, CMDB, etc.) at runtime instead of using a static hosts file. For example, 

ansible-playbook -i hosts.txt provision_volumes.yml

hosts.txt file contains static host information (access information, host connectivity identifiers, etc.) to provisioning volumes.  Host information could change or be outdated where provisioning task could fail or incorrectly provisioning volumes.   

Using dynamic inventory help mitigate these types of issues.  There are multiple ways of achieving dynamic inventory. 

Inventory Plugins

Ansible inventory plugins are one method to achieve dynamic Inventory.  Running the command:

ansible-doc -t inventory -l

This will list all the inventory plugins available through Ansible community. Documentation on inventory plugins is also available by running the command:

ansible-doc -t inventory <name of plugin>

Note that there are plugins available to retrieve hosts info from LDAP/AD server, AWS, GCP, VMware, etc. Depending how your hosts and storage systems is centralized, choose the appropriate plugin to retrieve hosts and storage inventory information. 

Custom Scripts

When inventory plugins do not suffice, alternatively you can create your own script.  For example:

ansible-playbook -i get_hosts.py provision_volumes.yml

where host.py could be a simple Python script to query an external system to get the latest host information, ensuring the provisioning task runs correctly on specified hosts.

Documentation and Comments

As with any coding practice, it’s always a good practice to add comments to explain complex tasks or decisions.  This section describes some specifics for Ansible playbooks.

Always name your tasks clearly to describe their purpose.  This helps with understanding what the playbook is aiming for for each task.

Example:

image

See playbooks located in Hitachi solution playbooks as examples of naming and documenting tasks

Maintain documentation for your playbooks and roles to help others understand and use them. For example in github, provide MD files to describe the playbook and how to use it.  See the readme.md file as example.  This example provides an overview of the playbooks, architecture, what each playbook does along with key features and requirements.  Not every playbook needs to have these sections, but document as much is necessary to avoid ambiguity.

Testing and Validation

Any playbooks will need to be thoroughly tested before they are put into production.  Ansible provides some tools to facilitate testing and validations.

Ansible-lint

Use tools like ansible-lint to check your playbooks for errors and best practices.  This will analyze the playbooks for syntax error and/or other potential bugs.  Running playbooks does not automatically check for errors before execution – the playbook will run until it hits the incorrect syntax or bug, which could leave the resource in a “dirty” or incomplete state, requiring additional cleanup tasks.  Ansible-lint does pre-flight tests on the playbook to avoid these issues.

Check Mode

Test run playbooks in check mode with the flag --check

ansible-playbook --check <playbookname.yml>

This will run the playbook to see what changes would be made without applying the changes to the target resources.  It will also report any errors that could have occurred during an actual run.

The --check option will:

  • Evaluate tasks.
  • Show what would change, but nothing is installed or copied.
  • Not actually modify remote systems.
Below is an example playbook site.yml.
image

To run this playbook in check mode:

ansible-playbook site.yml --check

Example output:

image

For more detailed validations, check mode can be combined with diff mode --diff

ansible-playbook site.yml --check --diff

This will simulate the changes that would have been applied but also provides line-by-line of what would have been changed.  This provides a more detailed information to help identify potential problems.

Make Tasks Check-Mode Aware

You can control each task’s check mode using check_mode: true or false Use of check_mode: forces the task to run with or without check mode regardless of if --check flag was used to run the playbook.  For example, set check-mode to false to ensure the destructive tasks will not run in check-mode.

Example:

image

Use Assert for Safety Risk Operations

Use the built-in module ansible.builtin.assert for validations before running risky operations.  This ensures that specific preconditions are met before running subsequent tasks.  If the assertion fails, the playbook will be stops prevent possible further errors.

Example:

image

Use Blocks for Transaction-Like Logic to Control Failure Handling

Blocks can be used to group tasks.  In this example, create volume task and present new LDEV to hostgroup are grouped into a single block:.

image

When blocks are combined with rescue:, this provides a method should an error occur when running tasks in a block, another task can be ran handle the error or failure in the block’s tasks.  There is also the option to use always, no matter the status of block’s task, always specified task will be ran.

Summary

The best practices for writing Ansible playbooks can be summarized as the following:

  • Encrypt sensitive data using ansible-vault.
  • Simplify tasks with Ansible roles.
  • Organize tasks and handlers with different variable files based on its intended purpose.
  • Use dynamic inventory by leveraging inventory plugins or build custom scripts.
  • Documentation on playbooks and its usage.
  • Test and validate playbooks using tools like ansible-lint and run playbooks with --check and --diff flags.

Below are some links where more resources can found.

Galaxy:

Github for Hitachi's Ansible modules:

Github for Hitachi's solution playbooks



0 comments
7 views

Permalink