When we hear term "Variable" we start thinking about some programming language. Ansible as such not a full-fledged programming language but it does support many programming features such as variables, loops and so on.
Ansible Variables
Variables as you know are used to store some values within the program. Ansible uses variables to differentiate between systems it manages. Variables can be set and overridden in different ways.
We run Ansible playbooks and ad hoc commands on multiple systems but on which machine they should run, whether to run or not certain plays/tasks on specific systems all this decision making happens with the help of Ansible variables.
Ways to create and pass variables in Ansible
Ansible allows us to set or create variables in various different ways and once declared we can import or pass them through different methods and to different places.
Declare a Valid Variable
As all other programming languages Ansible also follows some naming conventions to declare variables. Not all strings can be treated as a variable.
What is allowed:
- A variable name can only include letters, numbers, and underscores.
- Variable names can begin with an underscore.
What isn't allowed:
- Python keywords or playbook keywords are not valid variable names.
- Variable name starting with a number isn't allowed.
Examples
Defining Variables
As we have learnt there are multiple ways to define variables in Ansible. Have a look at them one by one.
Within Playbook
The simplest way to define a variable is using vars
keyword in the playbook itself.
Here is our example playbook.
- hosts: all
vars:
citation: Hello LearnCodeOnline!
tasks:
- name: Ansible Playbook variable definition example
debug:
msg: "{{ citation }}"
We have declared a variable named citation
under vars
section.
debug
module prints customized statement during playbook execution and can be useful for debugging variables or expressions without necessarily halting the playbook. if we don't usemsg
parameter it will print the default messages.
Let's run the playbook and see how it takes variable value to print message.
variables $ ansible-playbook -i myinventory playbook_vars.yml
PLAY [all] **************************************************************************************************************
TASK [Gathering Facts] **************************************************************************************************
ok: [worker1]
ok: [master]
ok: [worker2]
TASK [Ansible Playbook variable definition example] *********************************************************************
ok: [master] => {
"msg": "Hello LearnCodeOnline!"
}
ok: [worker1] => {
"msg": "Hello LearnCodeOnline!"
}
ok: [worker2] => {
"msg": "Hello LearnCodeOnline!"
}
PLAY RECAP **************************************************************************************************************
master : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
worker1 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
worker2 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
You can see it printed the message declared as part of variable.
Within one or more files outside of playbook
You can define variables in one or more files and then import them as part of you main playbook file.
Here we will take example of installing Apache web server on our worker machines.
Below is our variable file present in the same folder where our playbook is.
variables $ cat vars.yml
package_name: "httpd"
service_name: "httpd"
firewalld_service: "http"
We have defined all the required variables in the variable file which we will be referencing in playbook.
Here is our playbook referencing the vars.yml
file.
variables $ cat playbook_file_vars-pkg-install.yml
---
- name: Setting up Apache webserver
hosts: workers
become: true
tasks:
- include_vars: vars.yml
- name: Install apache packages
yum: name={{ package_name }} state=present
- name: ensure httpd service is running
service: name={{ service_name }} state=started
- name: Open port 80 for http access
firewalld: service={{ firewalld_service }} permanent=true state=enabled
include_vars
module loads variables from files, dynamically within a task.
The variable name needs to be referred within two squiggly or curly brackets {{ }}
.
Let's execute our playbook.
variables $ ansible-playbook -i myinventory playbook_file_vars-pkg-install.yml -kK
SSH password:
BECOME password[defaults to SSH password]:
PLAY [Setting up Apache webserver] **************************************************************************************
TASK [Gathering Facts] **************************************************************************************************
ok: [worker2]
ok: [worker1]
TASK [include_vars] *****************************************************************************************************
ok: [worker1]
ok: [worker2]
TASK [Install apache packages] ******************************************************************************************
changed: [worker1]
changed: [worker2]
TASK [ensure httpd service is running] **********************************************************************************
changed: [worker1]
changed: [worker2]
TASK [Open port 80 for http access] *************************************************************************************
ok: [worker1]
ok: [worker2]
PLAY RECAP **************************************************************************************************************
worker1 : ok=5 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
worker2 : ok=5 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Great! It has successfully ran.
Specifying multiple variable files in a playbook
You can specify multiple variable files in a playbook. Ansible will check for a variable in a bottom-to-top manner.
Let us create another variable file with same variable names but with different values
variables $ cat vars-2.yml
package_name: "vsftpd"
service_name: "vsftpd"
firewalld_service: "ftp"
Here is our playbook.
variables $ cat playbook_file_vars-pkg-install.yml
---
- name: Setting up A linux Apache or FTP server
hosts: workers
become: true
vars_files:
- vars.yml
- vars-2.yml
tasks:
- name: Install specified packages
yum: name={{ package_name }} state=present
- name: ensure that the service is running
service: name={{ service_name }} state=started
- name: Open the service port
firewalld: service={{ firewalld_service }} permanent=true state=enabled
If you notice we have included both the variable files in our playbook.
Here we have used another directive called vars_files
. This directive can only be used when defining a play to specify variable files. The variables from those files are included in the playbook.
include_vars
have higher priority thanvars_files
so, it can be used to override default configuration (vars).
FYI information I've removed httpd
packages from our worker nodes to have a clear idea what this playbook run does with multiple variable files getting referenced.
Let us run our playbook:
variables $ ansible-playbook -i myinventory playbook_file_vars-pkg-install.yml -kK
SSH password:
BECOME password[defaults to SSH password]:
PLAY [Setting up A linux server] ****************************************************************************************
TASK [Gathering Facts] **************************************************************************************************
ok: [worker2]
ok: [worker1]
TASK [Install specified packages] ***************************************************************************************
changed: [worker2]
changed: [worker1]
TASK [ensure that the service is running] *******************************************************************************
changed: [worker2]
changed: [worker1]
TASK [Open the service port] ********************************************************************************************
ok: [worker2]
ok: [worker1]
PLAY RECAP **************************************************************************************************************
worker1 : ok=4 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
worker2 : ok=4 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
It has installed something let's check what has been installed on worker nodes.
variables $ ansible -i myinventory workers -m shell -a "rpm -qa | grep httpd" -b -kK
SSH password:
BECOME password[defaults to SSH password]:
worker1 | FAILED | rc=1 >>
non-zero return code
worker2 | FAILED | rc=1 >>
non-zero return code
So httpd
packages are not installed, now verify the vsftpd
packages.
variables $ ansible -i myinventory workers -m shell -a "rpm -qa | grep vsftpd" -b -kK
SSH password:
BECOME password[defaults to SSH password]:
worker1 | CHANGED | rc=0 >>
vsftpd-3.0.2-28.el7.x86_64
worker2 | CHANGED | rc=0 >>
vsftpd-3.0.2-28.el7.x86_64
So the vars-2.yml
took preference while playbook execution because it found all the three variables with same name in vars-2.yml
file . If not found it will try searching for the variables in other files.
Using Variables at Ansible command line
We can override file or playbook variables with command line variables.
Lets do the practical. I have again removed the vsftpd
/httpd
packages from the worker nodes.
I will run the same playbook from last section and supply the variable values from command line.
variables $ ansible-playbook -i myinventory playbook_file_vars-pkg-install.yml -e "package_name=httpd" -e "service_name=httpd" -e "firewalld_service=http" -kK
SSH password:
BECOME password[defaults to SSH password]:
PLAY [Setting up A linux server] ****************************************************************************************
TASK [Gathering Facts] **************************************************************************************************
ok: [worker1]
ok: [worker2]
TASK [Install specified packages] ***************************************************************************************
changed: [worker2]
changed: [worker1]
TASK [ensure that the service is running] *******************************************************************************
changed: [worker1]
changed: [worker2]
TASK [Open the service port] ********************************************************************************************
ok: [worker2]
ok: [worker1]
PLAY RECAP **************************************************************************************************************
worker1 : ok=4 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
worker2 : ok=4 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
I've supplied three variables from command line using -e
parameter (it stands for extra vars
).
Verify the package installation.
variables $ ansible -i myinventory workers -m shell -a "rpm -qa | grep httpd" -b -kK SSH password:
BECOME password[defaults to SSH password]:
worker1 | CHANGED | rc=0 >>
httpd-2.4.6-97.el7.centos.x86_64
httpd-tools-2.4.6-97.el7.centos.x86_64
worker2 | CHANGED | rc=0 >>
httpd-2.4.6-97.el7.centos.x86_64
httpd-tools-2.4.6-97.el7.centos.x86_64
Using System Facts as Variables
In our "Ansible Ad Hoc commands" article you have learnt about gathering facts about a system. We can use those facts as a variable and make task execution decisions based on them. We sometimes call them system-related
variable.
For example we want to install vsftpd
package when the system OS family is Debian.
Here is our modified playbook.
variables $ cat playbook_file_vars-pkg-install_facts.yml
---
- name: Setting up A linux server
hosts: workers
become: true
vars_files:
- vars.yml
- vars-2.yml
tasks:
- name: Install specified packages
yum: name={{ package_name }} state=present
when: ansible_os_family == "Debian"
- name: ensure that the service is running
service: name={{ service_name }} state=started
- name: Open the service port
firewalld: service={{ firewalld_service }} permanent=true state=enabled
We have used when
condition and using ansible_os_family
fact variable to match the condition and take package installation decision based on that.
Now let's run the playbook.
variables $ ansible-playbook -i myinventory playbook_file_vars-pkg-install_facts.yml -kK
SSH password:
BECOME password[defaults to SSH password]:
PLAY [Setting up A linux server] ****************************************************************************************
TASK [Gathering Facts] **************************************************************************************************
ok: [worker1]
ok: [worker2]
TASK [Install specified packages] ***************************************************************************************
skipping: [worker1]
skipping: [worker2]
TASK [ensure that the service is running] *******************************************************************************
fatal: [worker2]: FAILED! => {"changed": false, "msg": "Could not find the requested service vsftpd: host"}
fatal: [worker1]: FAILED! => {"changed": false, "msg": "Could not find the requested service vsftpd: host"}
PLAY RECAP **************************************************************************************************************
worker1 : ok=1 changed=0 unreachable=0 failed=1 skipped=1 rescued=0 ignored=0
worker2 : ok=1 changed=0 unreachable=0 failed=1 skipped=1 rescued=0 ignored=0
It has skipped the package installation task because our target systems are not of Debian
OS family they belongs to RedHat
family.
Variable inclusion Dynamically
In Production environments you will have specific variable files based on the OS family, distribution versions and so on.
In that case instead of hard coding the file name in the playbook file we can make use of Ansible fact variables to dynamically pick the right variable file and import it.
For example:
variables $ cat playbook_file_vars-pkg-install_facts.yml
---
- name: Setting up A linux server
hosts: workers
become: true
tasks:
- include_vars: "vars-{{ ansible_os_family }}.yml"
- name: Install specified packages
yum: name={{ package_name }} state=present
when: ansible_os_family == "RedHat"
- name: ensure that the service is running
service: name={{ service_name }} state=started
- name: Open the service port
firewalld: service={{ _firewalld_service_ }} permanent=true state=enabled
Here we are calling include_vars
directive with a dynamic filename based on the ansible_os_family
variable.
And we have a variable file available named vars-RedHat.yml
.
variables $ cat vars-RedHat.yml
package_name: "vsftpd"
service_name: "vsftpd"
_firewalld_service_: "ftp"
Let's run the playbook and see whether it picks up the right variable file or not.
variables $ ansible-playbook -i myinventory playbook_file_vars-pkg-install_facts.yml -kK
SSH password:
BECOME password[defaults to SSH password]:
PLAY [Setting up A linux server] ****************************************************************************************
TASK [Gathering Facts] **************************************************************************************************
ok: [worker2]
ok: [worker1]
TASK [include_vars] *****************************************************************************************************
ok: [worker1]
ok: [worker2]
TASK [Install specified packages] ***************************************************************************************
changed: [worker1]
changed: [worker2]
TASK [ensure that the service is running] *******************************************************************************
changed: [worker1]
changed: [worker2]
TASK [Open the service port] ********************************************************************************************
ok: [worker1]
ok: [worker2]
PLAY RECAP **************************************************************************************************************
worker1 : ok=5 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
worker2 : ok=5 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
It does!
Variables in an host Inventory File
Sometimes we need to set some specific list of variable for a specific host. We can declare them within the inventory file itself.
In our Ansible Inventory article (sub section - Adding Host Variables to Ansible Inventory
) we have learned about them in detail.
Please refer that.
Variable Precedence Order
This is the most important part when we learn about Declaring Variables in Ansible.
One can always set variables with the same name in many different places. When you do this, Ansible uses the variable value based on the precedence. They will override each other in a certain order.
Here is the order of precedence from least to greatest (the last listed variables override all other variables).
Go through the below diagram to understand the precedence order clearly.
As we haven't yet covered Roles
you might not find few of above variable terms familiar. As we go along you will learn about it more.
That's all for now.
Hope you like the article. Stay Tuned for more.
Thank you. Happy learning!