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.

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

  • Keeping things simple

  • Staying organized

  • Testing often

Keeping things simple

  • Keeping Your Playbooks Readable

    1. Add Comments: Ensure that playbooks and tasks include meaningful comments to improve readability and maintainability.

    2. Use Blank Lines to Separate Logic: Insert blank lines between logical sections to enhance clarity.

    3. Always Name Plays and Tasks Meaningfully: Every play and task should have a clear and descriptive name to convey its purpose.

    4. Maintain a Consistent Style: Use a uniform coding style, including consistent indentation, variable naming conventions, and structured comments.

    5. Leverage Roles for Efficiency: Utilize roles to modularize and organize automation tasks efficiently.

    6. Stick to Native YAML Syntax: Avoid writing parameters in a single line—use proper YAML formatting to ensure readability and maintainability.

    The following syntax is easier for most people to read:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    - 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 native YAML syntax, not the “folded” syntax. For example, the following example is not a recommended format:

    1
    2
    3
    4
    5
    6
    - 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
  • Use Existing Modules

    1. 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.

    2. Ansible playbooks are designed to be idempotent, meaning that running the same playbook multiple times should produce the same result without unintended changes.

      It is easier to make your playbook idempotent and easier to maintain if you use the modules designed for a specific task.

      However, using the following modules can break idempotency:

      • ansible.builtin.command
      • ansible.builtin.shell
      • ansible.builtin.raw
    3. 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.

  • Adhering to a Standard Style

    Consider having a standard “style” that your team follows when writing Ansible projects. For example:

    1. How many spaces do you indent?

    2. How do you want vertical white space used?

    3. How should tasks, plays, roles, and variables be named?

    4. What should get commented on and how?

    Having a consistent standard can help improve maintainability and readability.

Staying Organized

  • Following Conventions for Naming Variables

    1. Variable names should clarify contents.

    2. When defining role variables, it is best to prefix them with the role name to avoid conflicts and improve clarity. 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.

    3. 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.

  • 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:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    .
    ├── dbservers.yml
    ├── inventories/
    │ ├── prod/
    │ │ ├── group_vars/
    │ │ ├── host_vars/
    │ │ └── inventory/
    │ └── stage/
    │ ├── group_vars/
    │ ├── host_vars/
    │ └── inventory/
    ├── roles/
    │ └── std_server/
    ├── site.yml
    ├── storage.yml
    └── webservers.yml

    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

    1. Using Dynamic Inventory。

    2. Dynamic inventory is particularly powerful when integrated with cloud providers, container platforms, and virtual machine management systems.

    3. If dynamic inventory is not an option, other tools can be used to dynamically construct groups or gather additional information.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    - 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
  • Taking Advantage of Groups

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

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

    3. 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.

  • Reusing Content with Collections and Roles

    Roles and collections help keep playbooks simple and enable code reuse across different projects, reducing workload. When necessary, you can also create a custom automation execution environment.

    Common Sources for Collections and Roles:

    • Red Hat Official Website
    • Ansible Galaxy
    • GitHub
    • Internal Company Repository
  • Running Playbooks Centrally

    Consider using a dedicated control node to run all Ansible Playbooks from a single location. Ideally, an Automation Controller should be used.

    The reason is simple: Ansible requires control over the managed hosts. Frequently changing the control node may lead to Playbook execution failures or security issues.

Performing Regular Testing

  • Test Your Playbooks and Tasks Regularly During Development

    1. When confirming the success of a task, validate the task’s actual result rather than relying solely on the module’s return code.

    2. In addition to using the debug module, you can also utilise the uri module to check whether web-based services are functioning correctly.

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      - 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
  • Implement Recovery and Rollback with block/rescue

    The block directive is useful for grouping tasks. When combined with the rescue directive, it helps in recovering from errors or failures, making it an effective approach for implementing recovery and rollback mechanisms.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    - 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
  • Using Test Tools

    1
    [student@workstation ~]$ ansible-navigator run playbook --syntax-check -m stdout
  • Analyzing the Playbooks

    • ansible-lint

      1
      [student@workstation ~]$ ansible-lint xxx.yaml
    • yamllint

      1
      [student@workstation ~]$ yamllint xxx.yaml
  • Testing with New Versions

    Regularly test your playbooks and automation workflows against newer versions of Ansible Core. This ensures compatibility and helps identify potential issues before upgrading your production environment.

    If your playbook displays warnings or deprecation messages during execution, you should pay attention to them and make the necessary adjustments.

    When a feature in Ansible Core is deprecated or changed, a deprecation notice is provided four minor versions in advance before it is removed or modified.