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

NAME

Url

import_playbook

https://docs.ansible.com/ansible/latest/modules/importplaybookmodule.html # import-playbook-module

import_role

https://docs.ansible.com/ansible/latest/modules/importrolemodule.html # import-role-module

include_role

https://docs.ansible.com/ansible/latest/modules/includerolemodule.html # include-role-module

import_tasks

https://docs.ansible.com/ansible/latest/modules/importtasksmodule.html # import-tasks-module

include_tasks

https://docs.ansible.com/ansible/latest/modules/includetasks_module.html#include-tasks-module

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 shellbecause 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, neverread 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 nountil 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.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *