Ansible naming conventions
Written by Emmanuel BENOÎT - Created on 2022-11-20
I wanted to write a little something about how I name various elements when using Ansible; however, because it is in parts very dependant on how I organize my inventories and playbooks, I ended up having to explain some of that as well.
Inventory
Host names
I tend to disregard the commonly accepted best practice of using short names for hosts, and use FQDNs instead. In my opinion, it has many advantages.
- It makes it impossible for host names and group names to collide.
- Creating an additional variable to carry the host's expected FQDN becomes unnecessary.
- It is immediately obvious which (actual) host caused some failure.
Group names
I organize my inventory into relatively shallow hierarchies which correspond to
various aspects. All group names in such a hierarchy, with the exception of the
top-level group, are prefixed with something indicative of which hierarchy
they're a part of. For example, if I have to group hosts by location, I will use
a group named by_location
containing subgroups using a loc_
prefix.
All group names use snake case.
Roles and tags
I use kebab-case for role names. In addition, I usually ensure that each role can be controlled using a single tag, which is identical to the role's name.
I organize most of my roles' tasks in a set of 5 phases.
- Preparations, which will validate configuration variables against host facts, pre-cache data...
- Installation, which will install packages, create directories, copy files from repositories...
- Configuration, which will create or update the necessary files for whatever is being installed by the role.
- Runtime, which will configure an already-running service through itself; this includes for example creating database users or performing API calls to the application that was just installed.
- Post-installation will perform any clean-up that may be required.
These phases are not always present in a given role. If they are, the
installation, configuration and runtime phases will bear the install
, config
and runtime
tags, respectively, while preparations and post-installation do
not bear any tags. This allows phases of a role to be executed selectively in
a relatively predictable manner.
I very rarely use host variables. When I do, they are exceptions to items usually carried by groups, so the variable names are determined by the latter.
Variables
Prefix
All variables should be prefixed, either by a role name or a group name. While
the commonly accepted practice consists in using a single _
to separate the
prefix and the variable's actual name, I prefer using two of them, for example:
my_role__variable_name: value
The reasoning is that, when using a single one, a name such as a_b_c
is
ambiguous: it could be variable b_c
of group/role a
, but it could also be
variable c
of group/role a_b
. While I never had a case of collision when I
used a single separator, I saw instances of ambiguity a few times.
When the variable is related to a role, the role name in snake case will be used
as the prefix, as seen above with a role called my-role
.
For variables provided by groups, I will use the group name as the variable
prefix. For example, if I wanted to define a vlan_tag
variable that depended
on the network a host is on, I would have the following type of structure:
all:
children:
by_network:
children:
net_dmz:
vars:
by_network__vlan_tag: 32
net_app1:
vars:
by_network__vlan_tag: 33
net_dev:
vars:
by_network__vlan_tag: 34
Private variables
When a variable should be considered "private" to a role or group, I will prefix
its whole name with an additional _
. This will be the case for almost all
variables in a role's vars/main.yml
file, as most of these are not meant to
be used outside of a role. I also use this convention for "registered"
variables and for loop iterators if item
is insufficient (e.g. loops that
include other tasks or roles).
Decoupling
A role should in my opinion never use a variable that isn't explicitly meant for it, as it introduces strong coupling between that role and the current structure of the inventory.
Because of this, I will use group-level variables to carry common information
and assign their contents to role variables. For example, if I had a db_name
variable in both role-a
and role-b
that needed to be assigned the same
value, I would end up with something like this in my group variables:
srv_service__db_name: thing
role_a__db_name: "{{ srv_service__db_name }}"
role_b__db_name: "{{ srv_service__db_name }}"
Handlers
I always use the listen
directive for my handlers. I use a name that follows
the same convention as the variable names above. For example, a handler that
restart the service for a role named apache
would be called apache__restart
.
However, I always couple that with a human-readable name which will appear in
the logs.
- listen: ntp__restart
name: Restart NTP
ansible.builtin.service:
name: ntp
state: restarted