Everything about Ansible Variables

Everything about Ansible Variables

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.

Create-set_vars.png

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:

Examples

variable_precedence_order-2.png

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 use msg 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 than vars_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.

variable_precedence_order.png

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!

Did you find this article valuable?

Support Learn Code Online by becoming a sponsor. Any amount is appreciated!