Abstract
| Goal |
Write playbooks that use variables to simplify management of the playbook and facts to reference information about managed nodes. |
| Objectives |
|
| Sections |
|
| Lab |
|
Create and reference variables that affect particular managed nodes or groups, the play, or the global environment, and describe how variable precedence works.
Ansible supports the use of variables to store values that can then be reused throughout files in an Ansible project. Using Ansible variables simplifies the creation and maintenance of a project and reduces errors.
Examples of values that Ansible variables might contain include:
Users to connect to managed nodes
Networks to define in a project
A network port to use
A firewall zone
Variable names must start with a letter, and they can only contain letters, numbers, and underscores.
The following table illustrates the difference between invalid and valid variable names.
| Invalid variable names | Valid variable names |
|---|---|
vlan id
|
vlan_id
|
device.family
|
device_family
|
firewall$zone#1
|
|
2nd-banner-message
|
|
You can define variables in various places in an Ansible project. If you set a variable using the same name in two places, and those settings have different values, Ansible uses the value that has the highest precedence.
The following simplified list shows ways to define an Ansible variable, ordered from the lowest to the highest precedence:
Group variables defined in the inventory.
Group variables defined in files in a subdirectory named group_vars that is in the same directory as the inventory or the playbook.
Host variables defined in the inventory.
Host variables defined in files in a subdirectory named host_vars that is in the same directory as the inventory or the playbook.
Facts gathered from the managed nodes.
Play variables in the playbook (the vars and vars_files sections of a play).
Task variables (set by a vars section on tasks or included with the ansible.builtin.include_tasks module).
Extra variables set by using the --extra-vars (or -e) option of the ansible-navigator run command.
These variables always have the highest precedence.
For example, a variable that is set to affect the all inventory group of managed nodes is overridden by a variable that has the same name and is set to affect a single managed node.
One recommended practice is to choose globally unique variable names, so that you do not have to consider precedence rules. However, sometimes you might want to use precedence to cause different managed nodes or managed node groups to get different settings from your defaults.
Precedence order is covered in the Variable Precedence: Where Should I Put a Variable? documentation at https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_variables.html#variable-precedence-where-should-i-put-a-variable.
After you have declared variables, you can use them in tasks. Place the variable name in double braces ({{ }}) to reference the variables. Ansible substitutes the variable with its value when the task is executed.
--- - name: Configuring hostname for IOS managed node hosts: iosxe.example.com gather_facts: falsevars:router_hostname:rtr01.example.comtasks: # This line reads: Set the rtr01.example.com hostname - name: Set the {{router_hostname}} hostname cisco.ios.ios_hostname: config: # This line sets the rtr01.example.com hostname for IOS hostname: "{{router_hostname}}" state: merged
When you start a value using the {{ expression, it is mandatory to use quotes.
This prevents Ansible from interpreting the variable reference as starting a YAML dictionary.
The following message appears if quotes are missing:variable }}
cisco.ios.ios_interfaces:
config:
name: {{ interface_name }}
^ here
We could be wrong, but this one looks like it might be an issue with
missing quotes. Always quote template expression brackets when they
start a value. For instance:
with_items:
- {{ foo }}
Should be written as:
with_items:
- "{{ foo }}"You can use the register keyword to define a temporary variable (with a lifetime limited to the run of your playbook) that stores the output of a task.
The output is saved into a variable that can be used later in the playbook for either debugging or other purposes, such as applying a particular configuration setting based on the value of the variable.
The following tasks in a playbook show how to capture the output of the first task and use the other task to show the value of the variable, possibly for debugging purposes:
- name: Gather IOS vlan
cisco.ios.ios_vlans:
state: gathered
register: vlans_data
- name: Show the vlan data
ansible.builtin.debug:
var: vlans_dataWhen you run the play, the ansible.builtin.debug module dumps the value of the vlans_data registered variable to the terminal:
...output omitted... TASK [Show the vlan data] ******************************************************** ok: [iosxe.lab.example.com] => { "vlans_data": { "changed": false, "failed": false, "gathered": [ { "mtu": 1500, "name": "internal", "shutdown": "disabled", "state": "active", "vlan_id": 10 }, { "mtu": 1500, "name": "dmz", "shutdown": "enabled", "state": "active", "vlan_id": 20 } ] } } ...output omitted...
Instead of assigning configuration data that associates the same element to multiple variables, you can use dictionaries. A dictionary is a data structure containing key-value pairs, where the values can also be dictionaries.
For example, consider the following snippet:
# Hypervisors vlan1_id: 1000 vlan1_mtu:9000 vlan1_name: physical-servers # Virtual machines vlan2_id: 2000 vlan2_mtu:1500 vlan2_name: virtual-servers # Servers in DMZ vlan3_id: 3000 vlan3_mtu:1500 vlan3_name: dmz-servers
This could be rewritten as a dictionary called vlan:
vlan:
hypervisors:
name: physical-servers
mtu: 9000
id: 1000
vms:
name: virtual-servers
mtu: 1500
id: 2000
dmz:
name: dmz
mtu: 1500
id: 3000You can then use the following variables to access vlan data:
- name: Returns 'dmz'
ansible.builtin.debug:
var: vlan['dmz']['name']
- name: Returns '9000'
ansible.builtin.debug:
var: vlan['hypervisors']['mtu']
- name: Returns '2000'
ansible.builtin.debug:
var: vlan['vms']['id']The var parameter in the ansible.builtin.debug module runs in Jinja2 context and implicitly uses double braces.
Specify the variable without wrapping the variable in double braces.
A number of variables are available that you can use to change how Ansible connects to a managed node listed in the inventory. Some of these are most useful as variables specific to one managed node, but others might be relevant to all managed nodes in a group in the inventory.
ansible_connection
The ansible_connection variable defines the connection plug-in to use to access the managed node.
The following table shows some possible values for the ansible_connection variable.
| Value | Description |
|---|---|
ansible.netcommon.network_cli
| Command-line connection over the Secure Shell (SSH) network protocol. |
ansible.netcommon.netconf
| Using eXtensible Markup Language (XML) over the SSH protocol. |
ansible.netcommon.httpapi
| Connection over the Hypertext Transfer Protocol Secure (HTTPS) protocol. |
ansible_network_os
The ansible_network_os variable defines the managed node operating system.
If you do not specify the value for the variable, Ansible tries to automatically detect the value to use.
As a best practice, always define the ansible_network_os variable for managed network nodes.
The following table shows some possible values for the ansible_network_os variable.
| Value | Managed Node OS |
|---|---|
arista.eos.eos
| Arista EOS |
cisco.ios.ios
|
Cisco IOS Cisco IOS-XE |
cisco.iosxr.iosxr
| Cisco IOS-XR |
cisco.nxos.nxos
| Cisco NX-OS |
junipernetworks.junos.junos
| Juniper Junos |
For more information on the ansible_connection and ansible_network_os variables, see https://docs.ansible.com/ansible/latest/network/user_guide/platform_index.html#settings-by-platform.
ansible_host
The ansible_host variable defines the actual IP address or fully qualified domain name to use when connecting to the managed node, instead of using the name from the inventory file.
By default, this variable has the same value as the inventory hostname.
ansible_user
The ansible_user variable defines the user that Ansible uses to connect to the managed node.
The default Ansible behavior is to connect to the managed node as the user running the Ansible Playbook on the control node.
ansible_become
Set this variable to true to enable privilege escalation mode.
The privilege escalation method is supported only for the ansible.netcommon.network_cli and ansible.netcommon.httpapi Ansible connections.
ansible_method
When the privilege escalation mode is supported for a managed node, set this variable to enable.
If you are managing your inventory as a static file, then you can define variables in the inventory, either for a specific managed node or for a group of managed nodes. However, this is not the best practice. As your environment grows in both size and variety, the inventory file becomes large and difficult to read. A better approach is to move variable definitions from the inventory file into separate variable files.
You can create a subdirectory named group_vars in the same directory as your Ansible project.
In the group_vars subdirectory, create a file for each group in your inventory and then add variables specific to that group of managed nodes:
[user@host project]$ tree -F group_vars/
group_vars/
├── arista.yml
├── ios.yml
└── junos.ymlThe above organizational structure makes it easier to locate configuration variables for any of the arista, ios, or junos inventory groups.
That structure is sufficient when each inventory group does not contain too many variable definitions.
As playbook complexity increases, however, even these files can become long and difficult to understand.
An even better approach for large, diverse environments is to create subdirectories for each inventory group of managed nodes under the group_vars subdirectory, such as the groups_vars/ios/ subdirectory, in your project directory.
Ansible parses any YAML or JSON files in these subdirectories and associates the variables with an inventory group based on the parent directory.
For example, you might initially define the ansible_network_os and ansible_connection variables for the ios inventory group in the inventory file:
[ios:vars] ansible_network_os = cisco.ios.ios ansible_connection = ansible.netcommon.network_cli
Instead of defining those variables in your inventory file, create a YAML file in the group_vars/ios path in your project directory and the ansible_network_os and ansible_connection variables in the new file:
ansible_network_os: cisco.ios.ios ansible_connection: ansible.netcommon.network_cli
If you use this organizational structure for variables, then place group variables with a common theme into the same file, and use a file name that indicates that common theme. A project organized according to this convention might appear as follows:
[user@host project]$ tree -F group_vars/
group_vars/
├── all/
│ └── common.yml
├── arista/
│ ├── acl.yml
│ ├── connection.yml
│ └── vlan.yml
├── ios/
│ ├── acl.yml
│ ├── connection.yml
│ ├── security.yml
| └── vlan.yml
└── junos/
├── acl.yml
├── connection.yml
├── snmp.yml
└── vlan.ymlWith this organizational structure for a project, you can quickly see the variables that are defined for each group of managed nodes.
Ansible merges all the variables present in files in directories under the group_vars directory with the rest of the variables.
Separating variables into files grouped by functionality makes the project easier to understand and maintain.
Likewise with inventory groups, you can create a subdirectory named host_vars in your Ansible project directory.
Then, create a file named after an inventory managed node with the variable definitions for that managed node.
Variables defined for a managed node have a higher precedence than variables defined for the managed node's inventory group.
The group_vars and host_vars subdirectories can exist not only in your project directory, which is where your playbook is located, but in the directory where your inventory file is located.
These two locations might not be the same directory.
For the group_vars and host_vars subdirectories relative to the inventory, the following is true:
If you have group_vars and host_vars subdirectories in the same directory as your playbook, then Ansible automatically includes those variables for managed nodes and groups of managed nodes.
If you use a flat inventory file in a directory other than the one the playbook is in, then Ansible also automatically includes the group_vars and host_vars directories in the inventory file directory.
If you use an inventory directory in your project directory, and that inventory file contains multiple inventory files, then Ansible includes the group_vars and host_vars subdirectories of your inventory directory.
The variables that Ansible includes from the playbook directory override the variables from the inventory directory if there is a conflict.
Inventory variables are overridden by variables set in a playbook, but both kinds of variables can be overridden by arguments passed to the ansible-navigator run command on the command line.
Variables set on the command line are called extra variables.
Extra variables can be useful when you need to override the defined value for a variable for a one-off run of a playbook. For example:
[user@host ~]$ ansible-navigator run snmp_configuration.yml -e state=merged