G
GuideDevOps
Lesson 7 of 14

Variables & Facts

Part of the Ansible tutorial series.

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: started

This 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: present

2. 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: 8080

playbook.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.yml

Variable 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.

  1. Command line flags (e.g., executing ansible-playbook -e "package_name=apache2") override everything.
  2. Playbook vars override inventory variables.
  3. Host (host_vars) variables override Group variables.
  4. Group (group_vars) variables override all group 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 setup

Using 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 true

Now, 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).