Ansible supports variables that can be used to store values that can be reused throughout files in an entire Ansible project. This can help simplify creation and maintenance of a project and reduce the incidence of errors.
- Places to define variables:
vars, vars_files
, andvars_prompt
- Command line:
1# ansible-playbook play.yml -e'{"myVar":"myValue","anotherVar":"anotherValue"}' - Roles, blocks, and inventories
- Essential variable use:
- debug: msg="Look! I'm using my variable ({ myVar )) !"
- A note on quotes:
name: "({ package ))"
Scope of variables
Variables can be defined in a bewildering variety of places in an Ansible project. However, this can be simplified to three basic scope levels:
- Global scope: Variables set from the command line or Ansible configuration
- Play scope: Variables set in the play and related structures
- Host scope: Variables set on host groups and individual hosts by the inventory, fact gathering, or registered tasks
If the same variable name is defined at more than one level, the higher wins. So variables defined by the inventory are overridden by variables defined by the playbook, which are overridden by variables defined on the command line.
Variables in playbooks
When writing playbooks, administrators can use their own variables and call them in a task. For example, a variable web_package
can be defined with a value of httpd
and called by the yum
module in order to install the httpd
package.
1 2 3 4 |
- hosts: all vars: user: joe home: /home/joe |
It is also possible to define playbook variables in external files. In this case, instead of using vars, the vars_files
directive may be used.
1 2 3 |
- hosts: all vars_files: - vars/users.yml |
The playbook variables are then defined in that file or those files in YAML format:
1 2 |
user: joe home: /home/joe |
Once variables have been declared, administrators can use the variables in tasks. Variables are referenced by placing the variable name in double curly braces.
1 2 3 4 5 6 7 8 |
vars: user: joe tasks: # This line will read: Creates the user joe - name: Creates the user {{ user }} user: # This line will create the user named Joe name: "{{ user }}" |
Important
When a variable is used as the first element to start a value, quotes are mandatory. This prevents Ansible from considering the variable as starting a YAML dictionary. The following message appears if quotes are missing:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
yum: name: {{ service}} ^ 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 }}" |
Host variables and group variables
Inventory variables that apply directly to hosts fall into two broad categories:
- host variables that apply to a specific host
- group variables that apply to all hosts in a host group or in a group of host groups.
Host variables take precedence over group variables, but variables defined by a playbook take precedence over both. One way to define host variables and group variables is to do it directly in the inventory file. This is an older approach and not preferred, but may be encountered by users:
• This is a host variable, ansible_user
, being defined for the host demo.example.com.
1 2 |
[servers] demo.example.com ansible_user=joe |
• In this example, a group variable user
is being defined for the group servers.
1 2 3 4 5 6 |
[servers] demo1.example.com demo2.example.com [servers:vars] user=joe |
• Finally, in this example a group variable user
is being defined for the group servers, which happens to consist of two host groups each with two servers.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
[servers1] demo1.example.com demo2.example.com [servers2] demo3.example.com demo4.example.com [servers:children] servers1 servers2 [servers:vars] user=joe |
Among the disadvantages of this approach, it makes the inventory file more difficult to work with, mixes information about hosts and variables in the same file, and uses an obsolete syntax.
Using group_vars and host_vars directories
The preferred approach is to create two directories in the same working directory as the inventory file or directory, group_vars
and host_vars
. These directories contain files defining group variables and host variables, respectively.
Important
The recommended practice is to define inventory variables using host_vars
and group_vars
directories, and not to define them directly in the inventory file or files.
To define group variables for the group servers, a YAML file named group_vars/servers
would be created, and then the contents of that file would set variables to values using the same syntax as a playbook:
1 |
user: joe |
Likewise, to define host variables for a particular host, a file with a name matching the host is created in host_vars
to contain the host variables.
The following examples illustrate this approach in more detail. Consider the following scenario where there are two data centers to manage that has the following inventory file in ~/project/inventory
:
1 2 3 4 5 6 7 8 9 10 |
[admin@station project]$ cat ~/project/inventory [datacenter1] demo1.example.com demo2.example.com [datacenter2] demo3.example.com demo4.example.com [datacenters:children] datacenter1 datacenter2 |
• If a general value needs to be defined for all servers in both datacenters, a group variable can be set for datacenters:
1 2 |
[admin@station project]$ cat ~/project/group_vars/datacenters package: httpd |
• If the value to define varies for each datacenter, a group variable can be set for each datacenter:
1 2 3 4 |
[admin@station project]$ cat ~/project/group_vars/datacenter1 package: httpd [admin@station project]$ cat ~/project/group_vars/datacenter2 package: apache |
• If the value to be defined varies for each host in every datacenter, using host variables is recommended:
1 2 3 4 5 6 7 8 |
[admin@station project]$ cat ~/project/host_vars/demo1.example.com package: httpd [admin@station project]$ cat ~/project/host_vars/demo2.example.com package: apache [admin@station project]$ cat ~/project/host_vars/demo3.example.com package: mariadb-server [admin@station project]$ cat ~/project/host_vars/demo4.example.com package: mysql-server |
The directory structure for project, if it contained all of the example files above, might look like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
project ├── ansible.cfg ├── group_vars │ ├── datacenters │ ├── datacenters1 │ └── datacenters2 ├── host_vars │ ├── demo1.example.com │ ├── demo2.example.com │ ├── demo3.example.com │ └── demo4.example.com ├── inventory └── playbook.yml |
Overriding variables from the command line
Inventory variables are overridden by variables set in a playbook, but both kinds of variables may be overridden through arguments passed to the ansible or ansible-playbook commands on the command line. This can be useful in a case where the defined value for a variable needs to be overridden for a single host for a one-off run of a playbook. For example:
1 |
[user@demo ~]$ ansible-playbook demo2.example.com main.yml -e "package=apache" |
Variables and arrays
Instead of assigning a piece of configuration data that relates to the same element (a list of packages, a list of services, a list of users, etc.) to multiple variables, administrators can use arrays. One interesting consequence of this is that an array can be browsed.
For instance, consider the following snippet:
1 2 3 4 5 6 |
user1_first_name: Bob user1_last_name: Jones user1_home_dir: /users/bjones user2_first_name: Anne user2_last_name: Cook user3_home_dir: /users/acook |
This could be rewritten as an array called users:
1 2 3 4 5 6 7 8 9 |
users: bjones: first_name: Bob last_name: Jones home_dir: /users/bjones acook: first_name: Anne last_name: Cook home_dir: /users/acook |
Users can then be accessed using the following variables:
1 2 3 4 |
# Returns 'Bob' users.bjones.first_name # Returns '/users/acook' users.acook.home_dir |
Because the variable is defined as a Python dictionary, an alternative syntax is available.
1 2 3 4 |
# Returns 'Bob' users['bjones']['first_name'] # Returns '/users/acook' users['acook']['home_dir'] |
Important
The dot notation can cause problems if the key names are the same as names of Python methods or attributes, such as discard, copy, add, and so on. Using the brackets notation can help avoid errors. Both syntaxes are valid, but to make troubleshooting easier, it is recommended that one syntax used consistently in all files throughout any given Ansible project.
Registered variables
Administrators can capture the output of a command by using the register
statement. The output is saved into a variable that could be used later for either debugging purposes or in order to achieve something else, such as a particular configuration based on a command’s output. The following playbook demonstrates how to capture the output of a command for debugging purposes:
1 2 3 4 5 6 7 8 9 10 |
--- - name: Installs a package and prints the result hosts: all tasks: - name: Install the package yum: name: httpd state: installed register: install_result - debug: var=install_result |
When the playbook is run, the debug module is used to dump the value of the install_result
registered variable to the terminal.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
[user@demo ~]$ ansible-playbook playbook.yml PLAY [Installs a package and prints the result] **************************** TASK [setup] *************************************************************** ok: [demo.example.com] TASK [Install the package] ************************************************* ok: [demo.example.com] TASK [debug] *************************************************************** ok: [demo.example.com] => { "install_result": { "changed": false, "msg": "", "rc": 0, "results": [ "httpd-2.4.6-40.el7.x86_64 providing httpd is already installed" ] } } PLAY RECAP ***************************************************************** demo.example.com : ok=3 changed=0 unreachable=0 failed=0 |
Dictionary Variables
1 2 3 |
employee: name: bob id: 42 |
- Yaml formatting allows for python style dictionaries to be used as variables
- There are two formats to access dictionary values:
- employee[‘name’]
- employee.name
- The bracket syntax is safer as the dot notation can have collisions with python in certain circumstances
Magic Variables and Filters
- Ansible defines several special variables knowns as magic variables
- You can use the variable
hostvars
to look at facts about other hosts in the inventory
{{ hostvars['node1']['ansible_distribution'] }}
- There is also a groups variable that provides inventory information
{{ groups['webservers'] }}
- Jinja2 filters can be useful in manipulating text format
{{ groups['webservers']|join(' ') }}
- See
http://jinja.pocoo.org/docs/2.10/templates/#builtin-filters
Example 1. of playboook with variables.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
[miro@controlnode playbooks]$ cat playbook9.yml --- - hosts: localhost vars: inv_file: /home/miro/ansible/vars/inv.txt tasks: - name: create file file: path: "{{inv_file}}" state: touch - name: generate inventory lineinfile: path: "{{inv_file}}" line: "{{ groups['labservers'] }}" [miro@controlnode playbooks]$ ansible-playbook playbook9.yml PLAY [localhost] ***************************************************************************************************************** TASK [Gathering Facts] *********************************************************************************************************** ok: [localhost] TASK [create file] *************************************************************************************************************** changed: [localhost] TASK [generate inventory] ******************************************************************************************************** changed: [localhost] PLAY RECAP *********************************************************************************************************************** localhost : ok=3 changed=2 unreachable=0 failed=0 [miro@controlnode playbooks]$ cat ../vars/inv.txt ['managedhost1.example.com', 'managedhost2.example.com'] |
The playbook writes to the inv.txt
list of host which belongs to the labservers groups.
We can modificate playbook9 to use jinja filter:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
[miro@controlnode playbooks]$ cat playbook9.yml --- - hosts: localhost vars: inv_file: /home/miro/ansible/vars/inv.txt tasks: - name: create file file: path: "{{inv_file}}" state: touch - name: generate inventory lineinfile: path: "{{inv_file}}" line: "{{ groups['labservers']|join(' ') }}" |
Now the the servers will be listed with spaces:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
[miro@controlnode playbooks]$ rm ../vars/inv.txt [miro@controlnode playbooks]$ ansible-playbook playbook9.yml PLAY [localhost] *********************************************************************************************************************************** TASK [Gathering Facts] ***************************************************************************************************************************** ok: [localhost] TASK [create file] ********************************************************************************************************************************* changed: [localhost] TASK [generate inventory] ************************************************************************************************************************** changed: [localhost] PLAY RECAP ***************************************************************************************************************************************** localhost : ok=3 changed=2 unreachable=0 failed=0 [miro@controlnode playbooks]$ cat ../vars/inv.txt managedhost1.example.com managedhost2.example.com |
Example 2.
We have a file users.lst
in the yaml format:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
[miro@controlnode playbooks]$ cat users.lst staff: - joe - john - bob - sam - mark faculty: - matt - alex - frank other: - will - jack |
The plabook below will read the yaml file and write the users to the list file:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
[miro@controlnode playbooks]$ cat playbook10.yml --- - hosts: localhost vars: userFile: /home/miro/ansible/vars/list tasks: - name: create file file: path: "{{ userFile }}" state: touch - name: list users lineinfile: path: "{{ userFile }}" line: "{{ item }}" with_items: - "{{ staff }}" - "{{ faculty }}" - "{{ other }}" |
We run the playbook ant put the variables from file by adding -e parameter:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
[miro@controlnode playbooks]$ ansible-playbook playbook10.yml -e "@users.lst" PLAY [localhost] *********************************************************************************************************************************** TASK [Gathering Facts] ***************************************************************************************************************************** ok: [localhost] TASK [create file] ********************************************************************************************************************************* changed: [localhost] TASK [list users] ********************************************************************************************************************************** changed: [localhost] => (item=joe) changed: [localhost] => (item=john) changed: [localhost] => (item=bob) changed: [localhost] => (item=sam) changed: [localhost] => (item=mark) changed: [localhost] => (item=matt) changed: [localhost] => (item=alex) changed: [localhost] => (item=frank) changed: [localhost] => (item=will) changed: [localhost] => (item=jack) PLAY RECAP ***************************************************************************************************************************************** localhost : ok=3 changed=2 unreachable=0 failed=0 |
After running the playbook10 the content of list file is:
1 2 3 4 5 6 7 8 9 10 11 |
[miro@controlnode playbooks]$ cat ../vars/list joe john bob sam mark matt alex frank will jack |
Example 3.
In this example you will create a playbook that installs the Apache web server
and opens the ports for the service to be reachable. The playbook queries the web server to ensure it is up and running.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
--- - name: Install Apache and start the service hosts: webserver vars: web_pkg: httpd firewall_pkg: firewalld web_service: httpd firewall_service: firewalld python_pkg: python-httplib2 rule: http tasks: - name: Install the required packages yum: name: - "{{ web_pkg }}" - "{{ firewall_pkg }}" - "{{ python_pkg }}" state: latest - name: Start and enable the {{ firewall_service }} service service: name: "{{ firewall_service }}" enabled: true state: started - name: Start and enable the {{ web_service }} service service: name: "{{ web_service }}" enabled: true state: started - name: Create web content to be served copy: content: "Example web content" dest: /var/www/html/index.html - name: Open the port for {{ rule }} firewalld: service: "{{ rule }}" permanent: true immediate: true state: enabled - name: Verify the Apache service hosts: localhost tasks: - name: Ensure the webserver is reachable uri: url: http://servera.lab.example.com status_code: 200 |
Example 4. – Scope of variables.
Look at the inventory file. Notice that there are two host groups, webservers and dbservers, which are children of the larger host group servers. The servers host group has variables set the old way, directly in the inventory file, including one that sets package to httpd.
1 2 3 4 5 6 7 8 9 10 11 |
[webservers] servera.lab.example.com [dbservers] servera.lab.example.com [servers:children] webservers dbservers [servers:vars] ansible_user=devops ansible_become=yes package=httpd |
Createing a new playbook, playbook.yml
, for all hosts. Using the yum module, install the package specified by the package variable.
1 2 3 4 5 6 7 |
--- - hosts: all tasks: - name: Installs the "{{ package }}" package yum: name: "{{ package }}" state: latest |
Run the playbook using the ansible-playbook command. Watch the output as Ansible installs the httpd package.
1 2 3 4 5 6 7 8 |
[student@workstation demo_variables-playbook]$ ansible-playbook playbook.yml PLAY *********************************************************************** TASK [setup] *************************************************************** ok: [servera.lab.example.com] TASK [Installs the "httpd" package] **************************************** changed: [servera.lab.example.com] PLAY RECAP ***************************************************************** servera.lab.example.com : ok=2 changed=1 unreachable=0 failed=0 |
Run an ad hoc command to ensure the httpd package has been successfully installed.
1 2 3 4 5 6 |
[student@workstation demo_variables-playbook]$ ansible servers \ > -a 'yum list installed httpd' servera.lab.example.com | SUCCESS | rc=0 >> Loaded plugins: langpacks, search-disabled-repos Installed Packages httpd.x86_64 2.4.6-40.el7 @rhel_dvd |
Create the group_vars
directory and a new file group_vars/dbservers
to set
the variable package to mariadb-server
for the dbservers host group in the
recommended way.
1 2 |
[student@workstation demo_variables-playbook]$ cat group_vars/dbservers package: mariadb-server |
Run the playbook again using the ansible-playbook
command. Watch Ansible install the mariadb-server
package. The package variable for the more specific host group dbservers took precedence over the one for its parent host group servers.
1 2 3 4 5 6 7 8 |
[student@workstation demo_variables-playbook]$ ansible-playbook playbook.yml PLAY *********************************************************************** TASK [setup] *************************************************************** ok: [servera.lab.example.com] TASK [Installs the "mariadb-server" package] ******************************* changed: [servera.lab.example.com] PLAY RECAP ***************************************************************** servera.lab.example.com : ok=2 changed=1 unreachable=0 failed=0 |
The output indicates the variable defined for the hosts group has been overridden.
Run an ad hoc command to confirm the mariadb-server package has been successfully installed.
1 2 3 4 5 6 |
[student@workstation demo_variables-playbook]$ ansible dbservers \ > -a 'yum list installed mariadb-server' servera.lab.example.com | SUCCESS | rc=0 >> Loaded plugins: langpacks, search-disabled-repos Installed Packages mariadb-server.x86_64 1:5.5.44-2.el7 @rhel_dvd |
For servera.lab.example.com, set the variable package to screen.
Do this by creating a new directory, host_vars
, and a file host_vars/
servera.lab.example.com
that sets the variable in the recommended way.
1 2 |
[student@workstation demo_variables-playbook]$ cat host_vars/servera.lab.example.com package: screen |
Run the playbook again. Watch the output as Ansible installs the screen package. The host-specific value of the package variable overrides any value set by the host’s host groups.
1 2 3 4 5 6 7 8 |
[student@workstation demo_variables-playbook]$ ansible-playbook playbook.yml PLAY *********************************************************************** TASK [setup] *************************************************************** ok: [servera.lab.example.com] TASK [Installs the "screen" package] *************************************** changed: [servera.lab.example.com] PLAY RECAP ***************************************************************** servera.lab.example.com : ok=2 changed=1 unreachable=0 failed=0 |
Run an ad hoc command to confirm the screen package has been successfully installed.
1 2 3 4 5 6 |
[student@workstation demo_variables-playbook]$ ansible servera.lab.example.com \ > -a 'yum list installed screen' servera.lab.example.com | SUCCESS | rc=0 >> Loaded plugins: langpacks, search-disabled-repos Installed Packages screen.x86_64 4.1.0-0.21.20120314git3c2946.el7 rhel_dvd |
Run the ansible-playbook command again, this time using the -e
option to override the package variable.
1 2 3 4 5 6 7 8 9 |
[student@workstation demo_variables-playbook]$ ansible-playbook playbook.yml \ > -e 'package=mutt' PLAY *************************************************************************** TASK [setup] ******************************************************************* ok: [servera.lab.example.com] TASK [Installs the "mutt" package] ********************************************* changed: [servera.lab.example.com] PLAY RECAP ********************************************************************* servera.lab.example.com : ok=2 changed=1 unreachable=0 failed=0 |
Run an ad hoc command to confirm the mutt package is installed.
1 2 3 4 5 6 |
[student@workstation demo_variables-playbook]$ ansible all \ > -a 'yum list installed mutt' servera.lab.example.com | SUCCESS | rc=0 >> Loaded plugins: langpacks, search-disabled-repos Installed Packages mutt.x86_64 5:1.5.21-26.el7 @rhel_dvd |