Ansible guidelines
Variables are used extensively in Ansible. But one of the frustrating things about Ansible is that it offers too much freedom. This has both advantages and disadvantages. The disadvantage is complexity along with high responsibility, and the advantage is flexibility. Let’s recap and organize what we know about Ansible variables.
Variables can be divided into two categories:
Variables in separate files (in the table below “Filesystem”).
Variables in the code (in the table below “Code”).
Now, if you look at their priority, then everything falls into place.
Filesystem variables have a lower precedence than Code variables. Have you noticed the flexibility and excessive freedom mentioned above? Let’s continue further with this knowledge in mind.
1. Declare variables in separate files
It is better to place variables in separate files (Inventory
, group_vars
, host_vars
, role/defaults/main.yml
and role/vars/main.yml
). All “constant” variables must be explicitly defined. Constant variables are variables that affect the role or behavior of the playbook. Unlike “temporary” variables, used as a buffer to temporarily store values, often with limited scope. For example, variables declared in vars
exist only inside block
… For example:
- name: Variables scope
hosts: localhost
connection: local
vars:
MY_VAR: "I am global var"
tasks:
- block:
- name: Print variable inside the block.
debug:
var: MY_VAR
vars:
MY_VAR: "I am local var"
- name: Print variable outside the block.
debug:
var: MY_VAR
PLAY [Variables scope]
TASK [Gathering Facts]
ok: [localhost]
TASK [Print variable inside the block.]
ok: [localhost] => {
"MY_VAR": "I am local var"
}
TASK [Print variable outside the block.]
ok: [localhost] => {
"MY_VAR": "I am global var"
}
Thus, we have to define variables in files. And all variables must be explicitly defined. There is a file for the role defaults/main.yml
… The values in this file have the lowest precedence, so empty variables can also be placed here. This will make life easier for contributors, especially those who see the code for the first time.
2. Use the README
If the role uses many different variables, perhaps all of them are even necessary and useful, then describe them in the README file. The ansible-galaxy init command will help you with this by generating a README template. Probably, you yourself, in an unfamiliar repository, will be pleased to see a README with information about what the role expects to see in and out. A bad example would be the separation of code and description. For example, the code is in git, and the description is on the wiki page. There is no guarantee that contributors will update both the code and the wiki page. Usually the work ends after the pull request.
3. Use prefixes
All “constant” variables (mentioned in the first tip) must be prefixed. It is best to use the role name as a prefix. This is very useful when variables for different roles need to be placed in the same place. For example, what happens in a multi-role playbook if all roles use the port variable? By adding a prefix, we guarantee that some variables will not be overwritten by others. Example: role is consul. variable is url, variable name is consul_url.
4. Name tasks with meaningful names
Ansible tasks have names. Use meaningful names for them as they appear in the output. Remember: this is your log, by which in case of errors you can understand what went wrong.
For example:
# No name/description
- copy: dest=/tmp/text.txt, content="bla-bla"
- name: Print variable global var.
debug:
var: MY_VAR
TASK [copy]
changed: [localhost]
TASK [Print variable global var.] *
ok: [localhost] => {
"MY_VAR": "I am global var"
}
5. DRY (Don’t Repeat Yourself)
Ansible is like a regular programming language. And just like a regular language, Ansible has various mechanisms to help you follow the DRY (Don’t Repeat Yourself) principle. But this requires planning ahead for the organization of your code. When writing code, think about reusability.
Large blocks:
Blocks within a role: (include/import)tasks
, (include/import)role
… How can it be used? For example, you are using the uri module to send API requests. Let’s say these are POST requests. Instead of repeating the uri 10 times with all settings, you can create something like a method and use it anywhere. Similar to methods in conventional programming languages, our method also accepts input parameters.
For example: send_post.yml
- name: .::::::::::::. [ Sent POST request ] .::::::::::::.
uri:
url: "{{ URL }}"
method: POST
status_code: 200
body: "{{ BODY_VAR | to_nice_json }}"
body_format: json
validate_certs: yes
client_cert: tls.crt
client_key: tls.key
register: return_values
when: BODY_VAR is defined
This code can be reused.
- name: Bla-bla
include_tasks: send_post.yml
vars:
URL: "{{ main_url }}/{{ item }}"
BODY_VAR: "{{ item }}"
URL and BODY_VAR Are the parameters of the method.
6. Use blocks
Use block.
Describe task parameters only once, grouping them. Also, block can be used similarly to try / catch block in traditional programming languages.
- block:
...
rescue:
...
block/rescue
Is a great alternative ignore_errors
… Essentially advanced error handling. This can be useful, for example, when you need to execute some code in a playbook, even if it fails. For example, delete some files.
- block:
- name: .....
- name: .....
- name: .....
always:
file:
path: /tmp/xxxx
state: absent
7. Don’t use the command and shell modules
Try not to use modules command
and shell
because they are not idempotent. Although there are a number of tricks that can help alleviate this problem. Use:
when
creates
(if the file exists, then this step is not performed).removes
(if the file exists, then this step is performed).changedwhen
…
However, if possible, stay away from command
and shell
…
8. Don’t use tags
Don’t use tags. Tags can be a nightmare for people who see your code for the first time. Combinations of tags increase the number of possible options for performing a playbook. But if you have no choice, then detail the tags and their combinations in the README. However, not all tags are bad. There are exceptions. For example, always
, never
– read more…
skip_ansible_lint
– skip ansible-lint
for the task.
9. Principle of least privilege
Use the principle of least privilege. Parameter become
it should be no
until you really need it. Always use become
clearly. For example:
---
- hosts: wordpress
become: no
...
role:
- role: wordpress
tasks/main.yml
---
- name: Install mysql-server pkg
apt:
name: mysql-server
state: present
become: yes
10.Use YAML Syntax for Parameters
Use YAML instead of inline syntax. Compare these two options:
YAML
- name: Install apache httpd
apt:
name: apache2
state: present
Recessed
- name: Install apache httpd
apt: pkg=apache2 state=pesent
11. Use gitignore
Add .gitignore
to your roles if you store the code in a git repository. Plain .gitignore
might look like this:
*.retry
*/__pycache__
*.pyc
*.log
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
12. Use advice from the Ansible documentation
Use guidelines for organizing content with official ansible page
13. Use a separate directory for community roles
Use a separate directory for community roles, along with the guidelines from the previous tip.
14. Test your Ansible code
Use frameworks to test your Ansible code. For example, molecule… This framework allows you to test your code from different angles. Besides traditional testing, it can also run all kinds of linters and check the code for idempotency.
15. Role versioning
What is versioning in the Ansible world? This is the approach used by git and allows you to run different versions of roles just by specifying the version. A version can be a tag, a branch, or a specific commit. More about this you can read here… Your task is to customize the procedure according to your role versioning requirements.
requirements.yaml:
---
- src: git@gitlab.company.com:mygroup/ansible.git
scm: git
version: "0.1"
...
Valid attributes:
src
scm
version
name
Translation of the article was prepared on the eve of the start of the course DevOps Practices and Tools…
We invite everyone to sign up for a free demo lesson of the course on the topic: “Prometheus: quick start”… During the lesson, the participants, together with an expert, will consider the Prometheus architecture and its way of working with metrics, as well as analyze how to generate alerts and events in the system.