Case study: Imagine that firewalld needs to be configured on
multiple servers with Ansible. Different servers might have
different ports and services are allowed through the firewall.
But at the same time some settings are same across all servers,
for example the default zone. This article will attempt to
provide the best way to configure host_vars
and group_vars
for firewalld configuration.
Since the main focus of this article is on variables, only localhost will be used in examples below.
Basic directory tree structure for all examples:
├── ansible.cfg
├── group_vars
│ └── all.yaml
├── host_vars
│ └── localhost.yaml
└── site.yaml
Configuration file for Ansible:
[defaults]
host_key_checking = False
stderr_callback = debug
Contents of variable files:
# group_vars/all.yaml
firewalld:
default_zone: internal
# host_vars/localhost.yaml
firewalld:
ports:
- 8080/tcp
services:
- http
- https
Contents of the main playbook:
---
- hosts: localhost
gather_facts: yes
become: true
tasks:
- name: Print 'firewalld' variable.
debug:
var: firewalld
When playbook is run, host_vars
will overwrite group_vars
and the default_zone: internal
variable will be erased.
Sample output:
"firewalld": {
"ports": [
"8080/tcp"
],
"services": [
"http",
"https"
]
}
Below are a couple of methods to go about this problem.
Method 1: Merge host_vars and group_vars
By default whenever Ansible encounters same variables it only
keep the last one it encountered (and overwrite the old one).
This can be changed by setting hash_behaviour
variable in the
config file:
[defaults]
host_key_checking = False
stderr_callback = debug
hash_behaviour = merge
Now playbook output will look like this:
"firewalld": {
"default_zone": "internal",
"ports": [
"8080/tcp"
],
"services": [
"http",
"https"
]
}
Drawbacks of this method:
- This method will merge all variables, not just the ones
that are in
host_vars
andgroup_vars
. This might not be a problem with a small number of variables, but it becomes a problem for larger playbooks. Furthermore, this option makes the playbook non portable. More info in documentation hash_behaviour
is deprecated (although it still works).
Due to the 2 main drawback Ansible developers recommend
using combine
filter instead.
Method 2: combine filter
combine
filter is one of the built-in
filter plugins in Ansible.
As the name suggests, it takes 2 (or more) different dictionaries and combines them
into 1. First, the firewalld
variable names has to be changed to something unique,
and after that combine
filter can be used to merge them:
# group_vars/all.yaml
firewalld_standard:
default_zone: internal
# host_vars/localhost.yaml
firewalld_specific:
ports:
- 8080/tcp
services:
- http
- https
firewalld: "{{ firewalld_specific | combine(firewalld_standard) }}"
Combined firewalld
variable is placed in host_vars
file
because it is read last.
Method 3: Read directly from group_vars
Perhaps the most straightforward way combine the two variables
is to read the group_vars
file directly in the host_vars
:
# group_vars/all.yaml
firewalld_standard:
default_zone: internal
# host_vars/localhost.yaml
firewalld:
default_zone: "{{ firewalld_standard['default_zone'] }}"
ports:
- 8080/tcp
services:
- http
- https
This way no configuration changes are made and no filters are used.