Bookmark this page

Implementing Recommended Ansible Practices

Objectives

  • Demonstrate and describe commonly recommended practices for developing and maintaining effective Ansible automation solutions.

The Effectiveness of Ansible

As you work with more advanced features and larger, more complex projects, it becomes more difficult to manage and maintain Ansible Playbooks, or to use them effectively.

This course introduces a number of advanced features of Red Hat Ansible Automation Platform. Using Ansible effectively is not just about features or tools, but about practices and organization.

To paraphrase Ansible developer Jeff Geerling, using Ansible effectively relies on three key practices:

  • Keeping things simple

  • Staying organized

  • Testing often

Keeping Things Simple

One of the strengths of Ansible is its simplicity. Simple playbooks are easier to use, modify, and understand.

Keeping Your Playbooks Readable

Keep your playbooks well commented and easy to read. Use vertical white space and comments liberally. Always give plays and tasks meaningful names that make clear what the play or task is doing. These practices help document the playbook and make it easier to troubleshoot a failed playbook run.

YAML is not a programming language. It is good at expressing a list of tasks or items, or a set of key-value pairs. If you are struggling to express a complex control structure or conditional in your Ansible Playbook, then consider approaching the problem differently. Templates and Jinja2 filters provide features to process data in a variable, which might be a better approach to your problem.

Use native YAML syntax, not the "folded" syntax. For example, the following example is not a recommended format:

  - name: Postfix is installed and updated
    ansible.builtin.yum: name=postfix state=latest
    notify: restart_postfix

  - name: Postfix is running
    ansible.builtin.service: name=postfix state=started

The following syntax is easier for most people to read:

  - name: Postfix is installed and updated
    ansible.builtin.yum:
      name: postfix
      state: latest
    notify: update_postfix

  - name: Postfix is running
    ansible.builtin.service:
      name: postfix
      state: started

Use Existing Modules

When writing a new playbook, start with a basic playbook and, if possible, a static inventory. Use ansible.builtin.debug tasks as stubs as you build your design. When your playbook functions as expected, break up your playbook into smaller, logical components by using imports and includes.

Use special-purpose modules included with Ansible when you can, instead of ansible.builtin.command, ansible.builtin.shell, ansible.builtin.raw, or other similar modules. It is easier to make your playbook idempotent and easier to maintain if you use the modules designed for a specific task.

Many modules have a default state or other variables that control what they do. For example, the yum module currently assumes that the package you name should be present in most cases. However, you should explicitly specify what state you want. Doing so makes it easier to read the playbook, and protects you against changes in the module's default behavior in later versions of Ansible.

Adhering to a Standard Style

Consider having a standard "style" that your team follows when writing Ansible projects. For example, how many spaces do you indent? How do you want vertical white space used? How should tasks, plays, roles, and variables be named? What should get commented on and how? Having a consistent standard can help improve maintainability and readability.

Staying Organized

Well-organized standards can help with maintainability, troubleshooting, and auditing. Take advantage of Ansible organization features, such as roles, so that you can reuse them.

Following Conventions for Naming Variables

Variable naming can be significant because Ansible has a reasonably flat namespace. Use descriptive variables, such as apache_tls_port rather than a less explanatory variable such as p. In roles, it is a good practice to prefix role variables with the role name.

If the name of your role is myapp then prefix your variables with myapp_ so that you can easily identify them from variables in other roles and the playbook.

Variable names should clarify contents. A name such as apache_max_keepalive clearly explains the meaning of the associated values. Prefix roles and group variables with the name of the role or group to which the variable belongs. apache_port_number is more error-resistant than port_number.

Standardizing the Project Structure

Use a consistent pattern when structuring the files of your Ansible project on a file system. The following is a good example:

.
├── dbservers.yml
├── inventories/
│   ├── prod/
│   │   ├── group_vars/
│   │   ├── host_vars/
│   │   └── inventory/
│   └── stage/
│       ├── group_vars/
│       ├── host_vars/
│       └── inventory/
├── roles/
│   └── std_server/
├── site.yml
├── storage.yml
└── webservers.yml

The site.yml file is the main playbook, which includes or imports playbooks that perform specific tasks: dbservers.yml, storage.yml, and webservers.yml. Each role is located in a subdirectory in the roles directory, such as std_server. Two static inventories in the inventories/prod and inventories/stage directories provide separate inventory variable files so that you can select different sets of servers by changing the inventory that you use.

One of the playbook structure benefits is that you can divide up your extensive playbook into smaller files to make it more readable. Those smaller playbooks can contain plays for a specific purpose that you can run independently.

Using Dynamic Inventories

Use dynamic inventories whenever possible. Dynamic inventories enable central management of your hosts and groups from one primary source of truth. They also ensure that you have an updated list of hosts and groups before you run your playbooks. Dynamic inventories are especially powerful when used in conjunction with cloud providers, containers, and virtual machine management systems. Those systems might already have inventory information in a form that Ansible can consume.

If you cannot use dynamic inventories, then other tools can help you construct groups or extra information. For example, you can use the group_by module to generate group membership based on a fact. That group membership is valid for the rest of the playbook.

- name: Generate dynamic groups
  hosts: all
  tasks:
    - name: Generate dynamic groups based on architecture
      ansible.builtin.group_by:
        key: arch_"{{ ansible_facts['architecture'] }}"

- name: Configure x86_64 systems
  hosts: arch_x86_64
  tasks:
    - name: First task for x86_64 configuration
...output omitted...

Taking Advantage of Groups

Hosts can be members of many groups. Consider dividing your hosts into different categories based on different characteristics:

Geographical

Differentiate hosts from different regions, countries, continents, or data centers.

Environmental

Differentiate hosts dedicated to different stages of the software lifecycle, including development, staging, testing, and production.

Sites or services

Group hosts that offer or link to a subset of functions, such as a specific website, an application, or a subset of features.

Important

Remember that hosts inherit variables from all groups of which they are members. If two groups have different settings for the same variable, and a host is a member of both, then the value that is used is the last one loaded.

If there are differences in settings between two different groups that might be used at the same time, then take special care to determine how those variables should be set.

Using Roles and Ansible Content Collections for Reusable Content

Roles and collections help you keep your playbooks simple and enable you to save work by reusing standard code across projects. If you write your own roles and collections, keep them focused on a particular purpose or function, just like you do with playbooks. Make roles generic and configurable through variables so that you do not need to edit them when you use them with a different set of playbooks.

Use the ansible-galaxy command to initialize the directory hierarchy for your role or collection and provide initial template files. This makes it easier to share your content through private automation hub or on the community Ansible Galaxy website.

Take advantage of Red Hat Ansible Certified Content Collections. These are supported with your Ansible Automation Platform subscription, and provide many useful functions and features.

You can also examine the roles provided by the community through Ansible Galaxy. Be aware that these roles have varying levels of quality, so choose the ones that you use carefully.

Keep your roles in the roles subdirectory of your project, and your collections in the collections subdirectory of your project. (Your collections might contain roles, of course, and those should stay in their collection's directory.) Use the ansible-galaxy command to get roles from automation hub or a separate Git repository automatically.

Running Playbooks Centrally

To control access to your systems and audit Ansible activity, consider using a dedicated control node from which all Ansible Playbooks are run. Ideally, you should use automation controller.

System administrators should still have their own accounts on the system, as well as credentials to connect to managed hosts and escalate privileges, if needed. When a system administrator leaves, their SSH key can be removed from managed hosts' authorized_keys file and their sudo command privileges revoked, without impacting other administrators.

Consider using automation controller as this central host. Automation controller is included with your Red Hat Ansible Automation Platform subscription, and provides features that make it easier to control access to credentials, control playbook execution, simplify automation for users who are not comfortable with the Linux command line, as well as audit and track playbook runs. Later in this course, you learn about using automation controller. However, even if you do not use automation controller, using a central control node can be beneficial.

Building Automation Execution Environments

Create a custom automation execution environment if you need to frequently use specific Ansible Content Collections, especially if the collection has Python dependencies or system executables not included in the supported automation execution environments.

However, if you can use an existing automation execution environment to run your playbooks, you should consider doing that. Reusing automation execution environments can reduce maintenance effort and the number of execution environments that you must manage.

Performing Regular Testing

Test your playbooks and your tasks frequently during the development process, when the tasks run, and after the playbooks are in use.

Testing the Results of Tasks

If you need to confirm that a task succeeded, then verify the result of the task rather than trusting the return code of the module. There is more than one way to verify a task, depending on the module involved.

  - name: Start web server
    ansible.builtin.service:
      name: httpd
      status: started

  - name: Check web site from web server
    ansible.builtin.uri:
      url: http://{{ ansible_fqdn }}
      return_content: true
    register: example_webpage
    failed_when: example_webpage.status != 200

Using the Block and Rescue Directives to Recover or Roll Back

The block directive is helpful for grouping tasks; when used in conjunction with the rescue directive, it is useful when recovering from errors or failures.

  - block:
      - name: Check web site from web server
        ansible.builtin.uri:
          url: http://{{ ansible_fqdn }}
          return_content: true
        register: example_webpage
        failed_when: example_webpage.status != 200

    rescue:
      - name: Restart web server
        ansible.builtin.service:
          name: httpd
          status: restarted

Developing Playbooks with the Latest Ansible Version

Even if you are not using the latest version of Ansible Core in production, you should routinely test your playbooks against the latest version of Ansible Core. This test helps you to avoid issues as Ansible modules and features evolve.

Automation execution environments can help make this easier by providing current and legacy versions of Ansible Core that you can use to test your playbooks.

If your playbooks print warnings or deprecation messages when they run, then you should pay attention to them and make adjustments. If a feature in Ansible Core is being deprecated or is changing, then the project provides deprecation notices for four minor releases before the feature gets removed or altered.

To prepare for changes and future updates to the upstream Ansible distribution, read the Ansible Porting Guides at https://docs.ansible.com/ansible/devel/porting_guides/porting_guides.html.

Important

Ansible 6 is not identical to Red Hat Ansible Automation Platform 2.2, but both use Ansible Core 2.13. Guidance in the upstream Porting Guides can be useful. However, you should use them with some caution.

Using Test Tools

Many commands and tools are available to help you test your playbooks. Use the ansible-navigator run playbook --syntax-check -m stdout command to validate the syntax of your playbook without running it.

The ansible-lint tool, added as a Technology Preview in Red Hat Ansible Automation Platform 2.2, parses your playbook and looks for possible issues. Not all the issues it reports result in playbook failures, but reported issues might indicate the presence of an error.

Note

Other useful tools are available that are not currently shipped in Red Hat Ansible Automation Platform 2; these tools might be included in Extra Packages for Enterprise Linux (EPEL), or can be obtained from upstream sources. Red Hat does not support these packages.

For example, the yamllint tool parses a YAML file and attempts to identify issues with the YAML syntax. This tool does not have direct knowledge of Ansible, but it can catch potential YAML syntax problems.

Revision: do374-2.2-82dc0d7