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:
- command line values (for example,
-u my_user
, these are not variables) - role defaults (defined in
role/defaults/main.yml
) - inventory file or script
group_vars
- inventory
group_vars/all
- playbook
group_vars/all
- inventory
group_vars/*
- playbook
group_vars/*
- inventory file or script
host_vars
- inventory
host_vars/*
- playbook
host_vars/*
- host
facts
/ cachedset_facts
- play vars
- play
vars_prompt
- play
vars_files
- role vars (defined in
role/vars/main.yml
) - block vars (only for tasks in block)
- task vars (only for the task)
include_vars
set_facts
/ registered vars- role (and
include_role
) params - include params
- 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:
- role defaults (defined in role/defaults/main.yml)
- 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.
- role defaults (defined in role/defaults/main.yml)
- 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:
- role defaults (defined in role/defaults/main.yml)
- inventory group_vars/*
- 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.
More Stories
Framework laptop with NixOS, a user feedback
Elon Musk booed on-stage at SF Dave Chappelle gig • The Register
Only a cyborg deserves to live for ever