Ansible variables & facts.
The same variable can be defined in a dozen places β what wins is decided by precedence. Watch one value climb a precedence ladder, then meet gathered facts and ansible_facts, register and set_fact, and custom facts.d.
Ansible variables and facts animated tutorial. Defining variables in play vars, vars_files, group_vars and host_vars, inventory, role defaults and vars, and CLI extra-vars; referencing them with Jinja2 templating; the variable precedence ladder where extra-vars win and role defaults lose; gathering facts with gather_facts and the setup module; reading ansible_facts; capturing runtime values with register and set_fact; magic variables; and custom facts in facts.d under ansible_local. RHCE EX294 ready.The same variable can be defined in a dozen places. Watch one value, http_port, climb a precedence ladder as more sources define it β then meet facts, register, set_fact, and custom facts.d.
- Β·Know an Ansible inventory, groups, and group_vars
- Β·Comfortable reading basic YAML and a simple playbook
- Β·Seen Jinja2 {{ }} templating before (helps, not required)
Define variables in every common location, reference them with Jinja2, predict which source wins by precedence, gather and read facts, and capture runtime values with register and set_fact.
pace: 10 minutes
variable: http_porthighest precedence on top βextra-vars -eβ not set βplay vars:β not set βhost_vars/serveraβ not set βgroup_vars/webserversβ not set βinventory group_vars/allhttp_port = 80β winsrole defaults (roles/web/defaults/main.yml)http_port = 8080Defining variables β many places
A variable can be set in a startling number of places: a play's vars: block, files pulled in with vars_files:, the group_vars/ and host_vars/ directories, the inventory itself, a role's defaults/ and vars/, captured at runtime with register, asserted with set_fact, and passed on the CLI with -e. Names use letters, digits, and underscores and cannot start with a digit. At this stage only the low rungs are set β role defaults give http_port its baseline.
$vars:\n http_port: 9000$# group_vars/webservers.yml\nhttp_port: 8000$ansible-playbook site.yml -e "http_port=9999"The same variable can be set in a dozen places β what actually matters is precedence. Extra-vars (-e) always win; role defaults always lose. Everything else sits on a ladder in between, and for scalars the highest rung wins outright β there is no merging. Know the ladder and "why is my variable not the value I set?" stops being a mystery.
$vars:$vars_files:$group_vars/NAME.yml$host_vars/HOST.yml$-e "k=v"${{ var }}${{ var | default(X) }}$var['key'] / var.key$gather_facts: true | false$ansible HOST -m setup$-a "filter=ansible_*"$ansible_facts['default_ipv4']['address']$register: r β r.stdout / r.rc / r.changed$set_fact: k: v$inventory_hostname, hostvars, groups$/etc/ansible/facts.d/*.fact β ansible_localhttp_port is set in group_vars/webservers (8000) and also passed as -e http_port=9999. Which value does the play use?
How do you populate ansible_facts for a host?
What is the difference between register and set_fact?
A variable's value starts with {{. What must you do in the YAML?
These aren't graded β they're just for active recall, which is what actually makes the lesson stick.
Ansible loops & conditionals
Now that a variable can hold a list or a dict, drive tasks with it: loop over items, gate work with when, and handle each host's data cleanly.
Read this lesson as text6 chapters Β· quiz answers
01Defining variables β many places
A variable can be set in a startling number of places: a play's vars: block, files pulled in with vars_files:, the group_vars/ and host_vars/ directories, the inventory itself, a role's defaults/ and vars/, captured at runtime with register, asserted with set_fact, and passed on the CLI with -e. Names use letters, digits, and underscores and cannot start with a digit. At this stage only the low rungs are set β role defaults give http_port its baseline.
$ vars:\n http_port: 9000β set in the play itself$ # group_vars/webservers.yml\nhttp_port: 8000β auto-loaded for the group$ ansible-playbook site.yml -e "http_port=9999"β CLI extra-var
02Using variables β Jinja2 templating
Reference a variable with double braces: {{ http_port }}. One YAML trap: if a value STARTS with {{, you must quote the whole string, or YAML reads the brace as the start of a dict and errors. Reach into a dict with myvar.key or myvar['key'], and into a list with mylist[0] β the bracket form is safest when keys collide with method names. Here group_vars and host_vars have also been set, so a higher rung now wins.
$ - name: template config\n ansible.builtin.template:\n dest: /etc/web.conf\n vars: { port: "{{ http_port }}" }β use it in a task$ {{ users['deploy'].uid }}β dict access, bracket + dot$ {{ http_port | default(80) }}β fallback if undefined
03Precedence β who wins
When the same variable is defined in multiple places, the highest-precedence source wins outright β for scalars the sources do NOT merge. Roughly, from strongest to weakest: command-line -e (always wins) > task vars > block vars > role/include vars > play vars > host_vars > group_vars > inventory > role defaults (always lose). Now every rung is set, so the very top of the ladder β extra-vars β takes the value and everything below it is ignored.
$ ansible-playbook site.yml -e "http_port=9999"β -e beats everything$ # host_vars/servera.yml beats group_vars/webserversβ more specific host wins$ # roles/web/defaults/main.ymlβ defaults are the floor β always lose
04Facts β what Ansible learns about a host
At the start of a play Ansible runs an implicit setup task (gather_facts: true) which populates ansible_facts with memory, IP addresses, distribution, mounts, and much more. Turn it off with gather_facts: false when you don't need it, or trim it with gather_subset to speed plays up. Access values as ansible_facts['default_ipv4']['address'] β the older flat names like ansible_default_ipv4 still work but the namespaced form is preferred.
$ ansible servera -m setupβ dump every gathered fact$ ansible servera -m setup -a "filter=ansible_distribution*"β just the distro facts$ {{ ansible_facts['default_ipv4']['address'] }}β use a fact in a task
05register & set_fact
register captures a task's return value β stdout, rc, changed, and more β into a variable you can test or print in later tasks. set_fact defines an arbitrary variable at runtime that then persists for the rest of the play, handy for computed values. Both create host-scoped vars. Magic vars round it out: inventory_hostname, hostvars (every host's facts/vars), groups, and group_names are always available.
$ - shell: id -u deploy\n register: result\n- debug: var=result.stdoutβ capture + reuse$ - set_fact:\n web_port: "{{ http_port }}"β define at runtime, persists$ {{ hostvars['serverb']['ansible_facts'] }}β read another host's facts
06Custom facts (facts.d)
You can teach a managed host to report your own metadata. Drop an executable script or a static INI .fact file into /etc/ansible/facts.d on the target; the setup module reads it and exposes the data under ansible_local. This is the clean way to ship site-specific facts β environment, tier, owner β without baking them into playbooks. The key on the ladder is unchanged here; custom facts are read-only data, not a precedence source.
$ # /etc/ansible/facts.d/env.fact\n[web]\ntier = frontendβ INI custom fact$ {{ ansible_local.env.web.tier }}β read it back$ ansible servera -m setup -a "filter=ansible_local"β see all local facts
Quiz β answers
- http_port is set in group_vars/webservers (8000) and also passed as -e http_port=9999. Which value does the play use?
- β9999 β extra-vars passed with -e have the highest precedence and win over everything.Command-line extra-vars (-e) sit at the very top of the precedence ladder β nothing overrides them. For scalars there is no merging; the highest rung simply wins, so 9999 is used.
- How do you populate ansible_facts for a host?
- βgather_facts runs the setup module at play start (on by default), or you run the setup module ad-hoc β that fills ansible_facts.Facts come from the setup module. A play gathers them implicitly via gather_facts: true (the default), and you can also collect them on demand with
ansible HOST -m setup. - What is the difference between register and set_fact?
- βregister captures a task's return value (stdout, rc, changed); set_fact assigns an arbitrary value at runtime β both persist for the rest of the play.register stores whatever a task returned so later tasks can inspect it. set_fact lets you compute and name a value yourself. Both create host-scoped variables that live for the remainder of the play.
- A variable's value starts with {{. What must you do in the YAML?
- βQuote the whole value as a string, otherwise YAML treats the leading { as the start of a dict and errors.A bare leading {{ looks like inline-dict syntax to the YAML parser. Wrapping the whole value in quotes β port: "{{ http_port }}" β tells YAML it is a string, and Jinja2 then templates it normally.
keep going β these pair well with what you just learned.
Ansible control node & inventory
Ansible is agentless β one control node pushes work to a fleet over SSH. Install ansible-core, write an inventory with groups, ping the fleet, and fire ad-hoc commands. RHCE-ready.
Ansible playbooks & YAML
Plays, tasks, and idempotency. Watch a playbook run task-by-task β ok, changed, skipped β then run it again and watch everything go green with 0 changed. RHCE-ready.
Ansible core modules
file, user, copy, dnf, service β the modules that do the real work. Declare the desired state and let Ansible converge to it, idempotently. RHCE-ready.