October 13, 2024

magellan-rfid

More Computer Please

Ansible variables: choosing the right location

Defining variables for your Ansible playbooks and roles can become challenging as your project grows.

Browsing the Ansible documentation, the diversity of Ansible variables location is confusing, to say the least:

  1. command line values (for example, -u my_user, these are not variables)
  2. role defaults (defined in role/defaults/main.yml)
  3. inventory file or script group_vars
  4. inventory group_vars/all
  5. playbook group_vars/all
  6. inventory group_vars/*
  7. playbook group_vars/*
  8. inventory file or script host_vars
  9. inventory host_vars/*
  10. playbook host_vars/*
  11. host facts / cached set_facts
  12. play vars
  13. play vars_prompt
  14. play vars_files
  15. role vars (defined in role/vars/main.yml)
  16. block vars (only for tasks in block)
  17. task vars (only for the task)
  18. include_vars
  19. set_facts / registered vars
  20. role (and include_role) params
  21. include params
  22. extra vars (for example, -e "user=my_user")(always win precedence)

There are 22 different places where to store your variables! As your code evolve and become more complex, it can get messy.

Define your own subset of variables locations

The simple way

Whenever you think of a variable, it should be obvious where it is defined. If not, maybe you are spreading your variables in too many places. Start with something simple, for instance having only 2 places where your variables can be defined:

  1. role defaults (defined in role/defaults/main.yml)
  2. role (and include_role) params

roles/serviceA/default.yml: this file defines all variables required by the role, with some default values. Note how we can comment variables that are required, but for which we don’t want to provide any default value. By doing that, we are documenting every variable the role needs.

servicea_log_dir: /var/log/servicea

servicea_autorestart: yes

serviceA.yml: this file is the playbook in which the role is called. We put our custom configuration there.

- hosts: groupa
  roles:
  - role: serviceA
    vars:
      servicea_user: gollum
      servicea_autorestart: no

We chose 2 locations for our project: role defaults, et role params. It’s easy to know where to find our variable. It should work in most situations.

A more generic way

Let’s have another subset of locations.

  1. role defaults (defined in role/defaults/main.yml)
  2. inventory group_vars/*

roles/serviceA/default.yml: role defaults, defined in the same as the previous example.

servicea_log_dir: /var/log/servicea

servicea_autorestart: yes

inventory/group_vars/servicea.yml: these variables are going to be associated with the group called servicea

servicea_user: gollum
servicea_autorestart: no

It is then required to associate correctly the role to the hosts where you have defined the variables (reminder: at runtime, variables are in the end associated with a host: there is no groups or role default, only host variables). The playbook:

- hosts: servicea
  roles:
  - role: serviceA

Now let’s say we want to have multiple roles, servicea and serviceb, to run on one group called for example worker. We could write the custom configuration for both services (servicea and serviceb) in a single file: inventory/group_vars/worker.yml; but then it is really not obvious where to find your custom variables.

Instead, we can have 1 group per service, so 1 configuration file per service in the group_vars folder (servicea.yml and serviceb.yml). We then associate the worker hosts to the group of our different services. inventory/hosts:

[worker]
worker01.local
worker02.local
worker03.local
worker04.local

[servicea:children]
worker

[serviceb:children]
worker

A wrong way

Let’s just take the 2 previous examples and mix them. We choose 3 locations:

  1. role defaults (defined in role/defaults/main.yml)
  2. inventory group_vars/*
  3. role (and include_role) params

Let’s say we want to modify servicea_log_dir variable. We cannot change the role defaults variable, as we want our own custom value. Now do we change it in the group_vars or in the role params? Both work, and there is no way to figure out which location you would choose, unless there is a specific logic behind it. This is not correct and we want to keep things simple.

Think gitops

When choosing the few locations to use, think about what effect it will have on your code versionning.

The gitops methodology may help you out. In short, you want to split your code in 2 repositories: your code repository, and your ops repository.

Applying it to Ansible, your code repository is where you version your roles or your Ansible collections. Your ops repository is where you version everything specific to an instance of your code; namely your inventory and custom variables.

When using the simple way, you will have to version your playbooks in your ops repository, as they contain your custom variables. When using the second method we described, where every custom variable is in the inventory group vars, you can version only the inventory in your ops repository.

What matters is that it should be possible to split the generic code and the properties that are proper to an instance. For example, storing custom variables in the vars folder of a role (eg. roles/servicea/vars/main.yml) is not correct.

If you think of any variable and know for sure where it has been defined, then you are probaly doing things right.