Ansible Series Chapter3: Implementing Recommended Ansible Practices
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
Add Comments: Ensure that playbooks and tasks include meaningful comments to improve readability and maintainability.
Use Blank Lines to Separate Logic: Insert blank lines between logical sections to enhance clarity.
Always Name Plays and Tasks Meaningfully: Every play and task should have a clear and descriptive name to convey its purpose.
Maintain a Consistent Style: Use a uniform coding style, including consistent indentation, variable naming conventions, and structured comments.
Leverage Roles for Efficiency: Utilize roles to modularize and organize automation tasks efficiently.
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: startedUse 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=startedUse Existing Modules
When writing a new playbook, start with a
basic playbook
and, if possible, astatic inventory
. Useansible.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 usingimports
andincludes
.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
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:
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
Following Conventions for Naming Variables
Variable names should
clarify contents
.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.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.ymlOne 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
Using Dynamic Inventory。
Dynamic inventory is particularly powerful when integrated with cloud providers, container platforms, and virtual machine management systems.
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 configurationTaking Advantage of Groups
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.
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 AutomationController
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
When confirming the success of a task, validate the task’s actual result rather than relying solely on the module’s return code.
In addition to using the
debug
module, you can also utilise theuri
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: restartedUsing 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.