Ansible Best Practices: From the Very Beginning to 2020
In this article, take a look at Ansible best practices.
Join the DZone community and get the full member experience.Join For Free
The crucial part of Ansible is variables. One of the dubious things about Ansible is that it offers too much freedom. This has advantages and disadvantages. A disadvantage would be the complexity along with high responsibility. In turn, an advantage would be flexibility. Therefore, let's revise what we know about variables and try to arrange the information and make it simpler.
Variables could be grouped into two categories:
- Variables on filesystem.
- Variables in code.
Now, if we look at variables' precedence it suddenly makes perfect sense.
"File system" variables have lower precedence compared to "code" variables. Have you noticed that flexibility and excessive freedom mentioned above?
Keeping in mind what we know about variables we can start using this knowledge.
The best place for variables is the file system (Inventory, goup_vars, host_vars, role/defaults/main.yml and role/vars/main.yml). All "Permanent" variables have to be defined within the file system and must be explicit. Permanent means variables that affect the role or the playbook behavior. As opposed to "temporary" variables which are used as a buffer, a temporary place for values, usually they are scoped. For instance block vars. They exist inside the block only. Example:
In conclusion. We should define variables within file system. 100% of variables must be defined explicitly. In a role, you have a defaults/main.yml file. Values in this file will have the lowest precedence, so put variable here even if it is empty. It will make contributors life easier, in particular for those who see the code for the first time.
If the role uses many different variables, may be they even implement sophisticated logic, try to describe them in the README file. ansible-galaxy init generates a README template for your convenience. When you clone the code from git and it is something completely new for you, you probably expect to see sufficient information in order to understand what the role use as the input and what will be the output just by reading the README file and the code. An example of a bad practice would be when code and description are separated, for instance code is in git, description is on the wiki page. There is no guarantee that contributors update both, the code and the wiki page. Usually work ends in git PR.
All "permanent" ("permanent" variables mentioned in the first advice) variables have to be prefixed. The best practice is using name of the role as the prefix for variables. The reason why we do this could be the situation when we need to place variables for different roles in one place. Like playbook which calls multiple roles. What will happen if all roles have variable port for instance? By adding prefix we ensure that variables will not be overwritten by other variables. Example: role = consul. variable = url, var name = consul_url.
Tasks in ansible have names. Use meaningful names, otherwise the string fed to ‘action’ will be used for output. Remember. The output is your log. You may want to read the log in case something went wrong. Example:
DRY (Don't Repeat Yourself). Ansible resembles normal programming language. Like normal language, ansible has different mechanisms to help you be DRY. But it requires that you plan your code in advance. When writing the code think how you can make it reusable.
Blocks within a role. (include/import)_tasks, (include/import)_role. How it works. Imagine a situation you use uri module for sending API requests. Let's say it is POST requests. Instead of repeating uri 10 times with all settings we can create, sort of, method and use it anywhere. As with method in normal programming language, our method also accepts input parameters.
This is reusable code. Now we can call it any time we want.
URL and BODY_VAR are method parameters.
Use blocks. https://docs.ansible.com/ansible/latest/user_guide/playbooks_blocks.html Apply tasks options only once by grouping them. Also block can be used as try/catch block in traditional programming language.
block/rescue is the great alternative for
ignore_errors. An advanced error handling.
Apart from that, a scenario when it could be useful. At the end of execution you may need to run code no matter what. For example some files need to be deleted even if play fails.
The most common best practice is try not to use command and shell modules. It is because these modules are not idempotent. Several approaches help to mitigate the problem. Use:
creates (If it already exists, this step won't be run.)
removes (If it already exists, this step will be run.).
Nevertheless just stay away from command and shell IF it is possible.
Do not use tags. Tags could be a nightmare for people who are getting to know your code. Tag combination multiplies the number of possible playbook scenarios. But if you have no choice describe tags and its combination in the README file with great diligence. However not all tags so bad. There are exceptions. always, never - https://docs.ansible.com/ansible/latest/user_guide/playbooks_tags.html#sts=Special%20Tags%C2%B6
skip_ansible_lint - skip ansible-lint for the task.
Use least privileges rule. become must be no unless it is absolutely necessary. Always use become explicitly. For example:
Use YAML instead of inline syntax. Compare these two syntax types:
Add .gitignore to your roles if you are going to commit it to git repo. The most basic lines in the .gitignore file
Use advises regarding content organization from official ansible page https://docs.ansible.com/ansible/latest/user_guide/playbooks_best_practices.html#content-organization
Use dedicated directory for community roles within roles directory from previous advice
Use testing framework for ansible, known as molecule (https://molecule.readthedocs.io/en/latest/index.html). The framework allows you to test your code from different directions. Apart from traditional testing it also can run all kinds of linters and it tests you code for idempotence.
Item #12. Directory layout. We can use the layout, as a base for "versioning". What is versioning in the ansible world? It is an approach which uses git and allows you to run different versions of role, just by specifying version. The version could be a tag, a branch or a commit hash. Here is the link https://docs.ansible.com/ansible/latest/galaxy/user_guide.html#installing-roles-from-galaxy which describes a procedure. Our task is to adjust the procedure to meet our requirements for role versioning.
Opinions expressed by DZone contributors are their own.