Ansible Variable Usage
Ansible can improve the efficiency of writing Playbooks through the use of variables. For example, when installing packages with Ansible, you can write the package names into a variable and then call that variable in the Playbook. This way, when you need to modify the packages to be installed, you only need to change the variable’s value without modifying the Playbook itself.
Ansible also has some built-in variables, which can be found in the later sections on facts and magic variables.
Setting Variables
There are several ways to set variables, the simplest being to set them directly in the Playbook:
- name: var
hosts: all
vars:
list_var:
- servera
- serverb
dict_var:
servera_info:
username: root
password: redhat
serverb_info:
username: admin
password: redhat
<span>list_var</span>is a list variable, while<span>dict_var</span>is a dictionary variable.
The above sets global variables, but you can also set them under individual tasks:
- name: print var
ansible.builtin.debug:
var: test_var
vars:
test_var: "Hello World!"
You can also set variables using a file. Assume there is a file <span>vars/vars.yml</span> in the directory where the Playbook is located:
- name: var
hosts: all
vars_files:
- vars/vars.yml
<span>vars/vars.yml</span> file content:
list_var2:
- var1
- var2
dict_var2:
ssh_username: root
ssh_password: redhat
You can also set variables via the command line:
[root@study ansible]# ansible localhost -m debug -a 'var=username' \
--extra-vars '{"username":"root","password":"redhat"}'
localhost | SUCCESS => {
"username": "root"
}
# If the variable involves many special characters, it is recommended to set it via a file and reference it
[root@study ansible]# cat vars/vars.yml
list_var2:
- var1
- var2
dict_var2:
ssh_username: root
ssh_password: redhat
[root@study ansible]# ansible localhost -m debug -a 'var=list_var2' \
-e '@vars/vars.yml'
localhost | SUCCESS => {
"list_var2": [
"var1",
"var2"
]
}
Referencing Variables
Using Variables in Playbooks
To reference a variable, simply wrap it with <span>{{ }}</span>. Below is a Playbook that uses existing variables to set a new variable:
vars:
list_var:
- servera
- serverb
dict_var:
servera_info:
username: root
password: redhat
serverb_info:
username: admin
password: redhat
servera_ssh_info: "username: {{ dict_var.servera_info.username }}, password: {{ dict_var.servera_info.password }}"
Printing Variables
You can print variables using <span>ansible.builtin.debug</span>:
tasks:
- name: print var
ansible.builtin.debug:
var: list_var[1]
- name: print var
ansible.builtin.debug:
var: dict_var['servera_info']
<span>list_var[1]</span> prints the second item of the list variable, while <span>dict_var['servera_info']</span> prints the dictionary variable where the KEY is <span>servera_info</span>.
Dictionary variables support nested variable references. The single quotes in
<span>dict_var['servera_info']</span>indicate that<span>server_info</span>is a regular string. If you omit the single quotes (<span>dict_var[servera_info]</span>),<span>servera_info</span>would be treated as a variable name, leading to different references. See the example below.
Here is an example of variable nesting:
- name: var
hosts: all
gather_facts: false
vars:
servera_info: servera
dict_var:
servera:
username: redhat
password: redhat
tasks:
- name: print var
ansible.builtin.debug:
var: dict_var[servera_info]
<span>servera_info</span> has the value <span>servera</span>, and then through <span>dict_var[servera_info]</span> you can access the value of <span>dict_var['servera']</span><code><span>.</span>
Registering Variables
Unlike setting variables, registering variables involves capturing the results of module execution into a variable for later reference.
Registering a variable uses <span>register</span>.
Below is an example of registering a variable:
- name: register
hosts: localhost
gather_facts: false
tasks:
- name: register
ansible.builtin.command: ls /tmp
register: get_tmp
changed_when: false
- name: print register var
ansible.builtin.debug:
var: get_tmp
- name: if testfile not in /tmp, create /tmp/testfile
ansible.builtin.file:
path: /tmp/testfile
state: touch
when: "'testfile' not in get_tmp.stdout_lines"
The first tasks in this Playbook will execute <span>ls /tmp</span> on the managed node and register the result into a variable named <span>get_tmp</span>. The second tasks can output the content of the <span>get_tmp</span> variable, and the third tasks checks whether the <span>/tmp</span> directory contains a <span>testfile</span>. If it does not, it creates it; otherwise, it skips this tasks.
Referencing Variables via Jinja2
Jinja2 is a Python template engine used to insert dynamic content into text, commonly used for generating HTML, configuration files, YAML, Shell scripts, etc. It is often used in conjunction with <span>ansible.builtin.template</span>.
For example, configuring an HTTP server. If the HTTP server is a single node, the listening port is 80. If it is a multi-node load balancer, the listening port is set to 8080. If neither applies, use 8443. Using Jinja2, you can write a conditional statement as follows (snippet):
{% if NODE_TYPE == 'SINGLE' %}
Listen 80
{% elif NODE_TYPE == 'LOAD_BALANCING' %}
Listen 8080
{% else %}
Listen 8443
{% endif %}
Here,
<span>NODE_TYPE</span>is a variable that needs to be set in the inventory or Playbook.
The above example uses a conditional statement, but Jinja2 also supports loops and variables. Below is an example of a loop:
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4
::1 localhost localhost.localdomain localhost6 localhost6.localdomain6
{% for host in groups['all'] %}
{{ hostvars[host]['ansible_ssh_host'] }} {{ hostvars[host]['ansible_facts']['fqdn'] }} {{ hostvars[host]['ansible_facts']['hostname'] }}
{% endfor %}
This example not only uses a loop but also variables. Jinja2 supports using Ansible’s facts, magic variables, and custom variables.
The above Jinja2 file, when copied, produces the following effect:
[root@ansible-controller ansible-navigator]# cat templates/hosts.j2
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4
::1 localhost localhost.localdomain localhost6 localhost6.localdomain6
{% for host in groups['all'] %}
{{ hostvars[host]['ansible_ssh_host'] }} {{ hostvars[host]['ansible_facts']['fqdn'] }} {{ hostvars[host]['ansible_facts']['hostname'] }}
{% endfor %}
[root@ansible-controller ansible-navigator]# cat template.yml
---
- name: template
hosts: all
tasks:
- name: template
ansible.builtin.template:
src: ./templates/hosts.j2
dest: /etc/hosts
[root@ansible-controller ansible-navigator]# ansible-navigator run --ep template.yml -- -b -K
BECOME password:
...output omitted...
[root@ansible-controller ansible-navigator]# ansible-navigator exec -- ansible all -m command -a 'cat /etc/hosts'
master1 | CHANGED | rc=0 >>
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4
::1 localhost localhost.localdomain localhost6 localhost6.localdomain6
192.168.221.142 master1.example.com master1
192.168.221.143 worker1.example.com worker1
worker1 | CHANGED | rc=0 >>
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4
::1 localhost localhost.localdomain localhost6 localhost6.localdomain6
192.168.221.142 master1.example.com master1
192.168.221.221 worker1.example.com worker1
When creating a Jinja2 file, it is recommended to use the
<span>.j2</span>suffix (e.g.,<span>hosts.j2</span>).
Ansible Facts Variables
Viewing Ansible Facts Variables
Ansible can collect information from managed hosts and register it as facts variables, with the variable name <span>ansible_facts</span>. You can print the facts variables of the managed host using the <span>ansible.builtin.debug</span> module:
- name: Print all available facts
ansible.builtin.debug:
var: ansible_facts
Ansible facts variables can provide a lot of information:
- • Hardware: CPU, memory, motherboard, etc.
- • Software: addresses, hostnames, filesystem mounts, etc.
For reference on Ansible facts variable output, see: https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_vars_facts.html#ansible-facts.
In addition to obtaining facts variables through
<span>ansible_facts</span>, you can also access specific host facts variables using<span>hostvars['servera']['ansible_facts']</span>. This method can only be used within a Playbook.
Caching Ansible Facts Variables
By default, Ansible collects facts variables during Playbook execution and stores them in memory. After the Playbook execution ends, the collected facts variables are cleared. You can cache the variables to a file or a specific database. Here are two caching methods:
- • Cache to file
- • Cache to Redis
Caching to File
Modify the following lines in the Ansible configuration file:
# (string) Chooses which cache plugin to use, the default 'memory' is ephemeral.
fact_caching=jsonfile
# (string) Defines connection or path information for the cache plugin.
fact_caching_connection=./cache
# (string) Prefix to use for cache plugin files/tables.
fact_caching_prefix=ansible_facts_
# (integer) Expiration timeout for the cache plugin data.
fact_caching_timeout=86400
These lines indicate that facts variables will be cached in JSON format to a file in the <span>./cache</span> directory, with filenames prefixed by <span>ansible_facts_</span> and a cache validity of 86400 seconds.
Regarding cache expiration, if cached to a file, Ansible will check whether the cache is within the validity period during Playbook execution. If it is not within the validity period, it will not use the cache.
Testing the cache:
[root@study ansible]# ls cache/
ls: cannot access 'cache/': No such file or directory
[root@study ansible]# ansible localhost -m setup &> /dev/null
[root@study ansible]# ls cache/
ansible_facts_localhost
[root@study ansible]# cat ansible_facts_cache.yml
- name: test ansible facts cache
hosts: localhost
gather_facts: false
tasks:
- name: Print ansible facts
ansible.builtin.debug:
var: ansible_facts['hostname']
[root@study ansible]# ansible-playbook ansible_facts_cache.yml
PLAY [test ansible facts cache] ***********************************************************************************************************************************
TASK [Print ansible facts] ****************************************************************************************************************************************
ok: [localhost] => {
"ansible_facts['hostname']": "study"
}
PLAY RECAP ********************************************************************************************************************************************************
localhost : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Caching to Redis
To cache facts variables to Redis, you need to install the <span>community.general</span> collection by executing <span>ansible-galaxy collection install community.general</span>.
Check the newly installed cache plugins:
[root@study ansible]# ansible-doc -t cache -l
ansible.builtin.jsonfile JSON formatted files
ansible.builtin.memory RAM backed, non persistent
community.general.memcached Use memcached DB for cache
community.general.pickle Pickle formatted files
community.general.redis Use Redis DB for cache
community.general.yaml YAML formatted files
Install and start Redis:
# Install Redis
[root@study ansible]# dnf install -y redis
# Start Redis
[root@study ansible]# systemctl start redis
[root@study ansible]# ss -ntlp | grep 6379
LISTEN 0 511 127.0.0.1:6379 0.0.0.0:* users:(("redis-server",pid=45313,fd=6))
# Check Redis data
[root@study ansible]# redis-cli
127.0.0.1:6379> KEYS *
(empty list or set)
127.0.0.1:6379> exit
Be cautious when using
<span>KEYS *</span>.
Modify the Ansible configuration file:
# (string) Chooses which cache plugin to use, the default 'memory' is ephemeral.
fact_caching=redis
# (string) Defines connection or path information for the cache plugin.
fact_caching_connection=127.0.0.1:6379:0
# (string) Prefix to use for cache plugin files/tables.
fact_caching_prefix=ansible_facts_
# (integer) Expiration timeout for the cache plugin data.
fact_caching_timeout=86400
When expired, the information in Redis will be deleted.
Collect facts variables and check the cache:
[root@study ansible]# ansible localhost -m setup &> /dev/null
[root@study ansible]# redis-cli
127.0.0.1:6379> KEYS *
1) "ansible_facts_localhost"
2) "ansible_cache_keys"
127.0.0.1:6379> exit
[root@study ansible]#
Additionally, since Redis can be password-protected and supports encryption, the configuration for <span>fact_caching_connection</span> can be found in the documentation for <span>community.general.redis</span>:
[root@study ansible]# ansible-doc -t cache community.general.redis
...output omitted...
= _uri
A colon separated string of connection information for Redis.
The format is `host:port:db:password', for example `localhost:6379:0:changeme'.
To use encryption in transit, prefix the connection with `tls://', as in `tls://localhost:6379:0:changeme'.
To use redis sentinel, use separator `;', for example `localhost:26379;localhost:26379;0:changeme'. Requires redis>=2.9.0.
set_via:
env:
- name: ANSIBLE_CACHE_PLUGIN_CONNECTION
ini:
- key: fact_caching_connection
section: defaults
type: string
Disabling Ansible Facts Variables
By default, Ansible collects facts variables during Playbook execution. If you explicitly do not want to use facts variables, you can disable the collection of facts variables by setting <span>gather_facts: false</span> to improve execution efficiency.
- hosts: whatever
gather_facts: false
Adding Custom Facts
You can add custom facts variables in the <span>/etc/ansible/facts.d</span> directory on the managed nodes.
Below is the definition and viewing of custom variables:
[root@study ansible]# cat /etc/ansible/facts.d/local_var.fact
[webserver_vars]
linux = true
webserver_port = 80
dbserver_port = 3306
[root@study ansible]# ansible localhost -m setup -a 'filter=ansible_local'
localhost | SUCCESS => {
"ansible_facts": {
"ansible_local": {
"local_var": {
"webserver_vars": {
"dbserver_port": "3306",
"linux": "true",
"webserver_port": "80"
}
}
}
},
"changed": false
}
<span>local_var</span>, <span>webserver_vars</span>, and <span>webserver_port</span> correspond to different KEY names.
Below is how to call local facts:
- name: Print custom facts
ansible.builtin.debug:
var: ansible_facts.ansible_local
The KEY of facts variables will convert uppercase KEY names to lowercase. For example,
<span>WebServer_Port = 80</span>will convert to<span>webserver_port = 80</span>.
Ansible Magic Variables
Calling Magic Variables
Unlike facts variables, magic variables do not require information to be collected from managed hosts. They mainly come from two sources:
- • Variables set through the inventory, which can be new variables added in the inventory or built-in Ansible variables (connection variables:
<span>ansible_ssh_user</span>and<span>ansible_ssh_password</span>) - • Variables added during Playbook execution
Here are some commonly used magic variables:
- •
<span>hostvars</span> - •
<span>groups</span> - •
<span>group_names</span> - •
<span>inventory_hostname</span>
These variables are all included in <span>hostvars</span>, which can be viewed using the <span>ansible.builtin.debug</span> module (<span>ansible localhost -m debug -a 'var=hostvars'</span>).
Here are some examples of using magic variables:
Getting Variable Information for a Specific Host
{{ hostvars['test.example.com']['ansible_facts']['distribution'] }}
Looping Through Host Groups
{% for host in groups['app_servers'] %}
{{ hostvars[host]['ansible_facts']['eth0']['ipv4']['address'] }}
{% endfor %}
Checking if Host servera is in the webserver Group
{% if 'servera' in groups['webserver'] %}
{{ hostvars[host]['ansible_facts']['eth0']['ipv4']['address'] }}
{% endif %}
In addition to the variables seen in <span>hostvars</span>, there are also the following variables:
- •
<span>ansible_play_hosts</span> - •
<span>ansible_play_batch</span> - •
<span>role_path</span>(contains the path name of the current role and only works within the role).
Examples of Calling Magic Variables
Now assume there is a batch of hosts (hostnames and IP addresses configured, IP addresses uniformly configured on the eth0 network card), you can configure the <span>/etc/hosts</span> file using the following Playbook:
[root@study ansible]# cat set_hosts.yml
- name: set all hosts
hosts: all
gather_facts: true
tasks:
- name: Configure the hosts file with the template
ansible.builtin.template:
src: ./templates/hosts.j2
dest: /etc/hosts
owner: root
group: root
mode: '0644'
[root@study ansible]# cat templates/hosts.j2
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4
::1 localhost localhost.localdomain localhost6 localhost6.localdomain6
{% for host in groups['all'] %}
{{ hostvars[host]['ansible_facts']['eth0']['ipv4']['address'] }} {{ hostvars[host]['ansible_facts']['hostname'] }}
{% endfor %}
This is done using the <span>Jinja2</span> template and the <span>ansible.builtin.template</span> module to configure the <span>/etc/hosts</span> file.
- 1. The
<span>./templates/hosts.j2</span>file is a<span>Jinja2</span>template that loops through the<span>all</span>host group, printing the IP addresses and hostnames of all hosts in the group line by line. - 2. Ansible then uses the
<span>ansible.builtin.template</span>module to execute this<span>Jinja2</span>template on the<span>all</span>host group.