Why Use Variables?
If you hardcode values in your playbooks, they become brittle. Consider a playbook that installs Apache.
# Hardcoded playbook
tasks:
- name: Install Apache
apt:
name: apache2
state: present
- name: Start Apache
service:
name: apache2
state: startedThis works perfectly on Ubuntu. But what if you need to run it on CentOS? On CentOS, the Apache package is named httpd, not apache2. If the package name is hardcoded, your playbook is tied exclusively to Debian-based systems.
Variables allow you to inject data into your playbooks dynamically.
Defining Variables
Ansible uses Jinja2 templating syntax to reference variables: {{ variable_name }}.
1. Variables in Playbooks (vars block)
The simplest place to define variables is directly inside your playbook under the vars block.
---
- name: Install generic package
hosts: all
vars:
package_name: nginx
listen_port: 8080
tasks:
- name: Ensure package is installed
apt:
name: "{{ package_name }}" # Replacing the hardcoded string
state: present2. Variables in separate files (vars_files)
Storing variables directly in the playbook clutters the code. It is better to extract them into dedicated YAML files.
vars/web_vars.yml:
package_name: nginx
listen_port: 8080playbook.yml:
---
- name: Install generic package
hosts: all
vars_files:
- vars/web_vars.yml # Loads the external file
tasks:
# ...3. Inventory Variables
We discussed this in the Inventory tutorial. You can assign variables based on the group a server belongs to (group_vars) or specifically to individual hosts (host_vars).
Ansible automatically looks for directories named group_vars and host_vars in the same folder as your inventory file.
├── inventory.ini
├── group_vars/
│ ├── webservers.yml ← Applied only to [webservers]
│ └── databases.yml ← Applied only to [databases]
└── playbook.ymlVariable Precedence
Ansible allows you to define the same variable in 22 different places! So, if package_name is defined in an inventory file, in group_vars, in the playbook, and via the command line, which one wins?
The rule of thumb: The most specific definition wins.
- Command line flags (e.g., executing
ansible-playbook -e "package_name=apache2") override everything. - Playbook
varsoverride inventory variables. - Host (
host_vars) variables override Group variables. - Group (
group_vars) variables overrideallgroup variables.
Ansible Facts (System Discovery)
Ansible provides a massive, built-in dictionary of variables completely automatically. These are called Facts.
Every time a playbook starts executing, you'll see a task called TASK [Gathering Facts].
In this step, Ansible runs the setup module on the remote host, determining hundreds of details about the system: IP addresses, OS distributions, disk space, CPU cores, MAC addresses, and more.
You can view the facts of a local machine by running this ad-hoc command:
ansible localhost -m setupUsing Facts in Playbooks
Facts are injected into the playbook and are prefixed with ansible_. You can use them to make your playbooks intelligent and conditional.
Example 1: Conditional OS Packages
Let's solve the Apache httpd vs apache2 problem from the beginning of the tutorial:
---
- name: Install Apache dynamically
hosts: all
tasks:
- name: Install Apache on RedHat/CentOS
yum:
name: httpd
state: present
when: ansible_os_family == "RedHat" # Executes ONLY if this Fact is true
- name: Install Apache on Ubuntu/Debian
apt:
name: apache2
state: present
when: ansible_os_family == "Debian" # Executes ONLY if this Fact is trueNow, ONE playbook perfectly handles both Ubuntu and CentOS servers!
Example 2: Dynamic Configuration If you want a database configuration file to automatically use 50% of whatever memory is available on the target machine, you can leverage Facts:
- name: Configure database memory limit
lineinfile:
path: /etc/db.conf
line: "max_memory = {{ (ansible_memtotal_mb * 0.5) | int }}M"(Note: | int is a Jinja2 filter that ensures the float calculation rounds to a clean integer).