Expert · Ansible in depth · topic 49 of 57 · 35 min
Variables, facts and registered results
Explanation
Why variables?
Hard-coding "httpd" or "10.0.5.20" in tasks works for one case. Variables let the same playbook install nginx in one environment and httpd in another, or point test servers at the test database. Variables are referenced with Jinja2 syntax: {{ variable_name }}. Names use letters, numbers and underscores, and start with a letter.
Where to define them
- name: Web servers
hosts: webservers
vars:
web_package: httpd
web_port: 8080
vars_files:
- vars/common.yml
tasks:
- name: Install the web server
ansible.builtin.dnf:
name: "{{ web_package }}"
state: presentproject/
├── ansible.cfg
├── inventory
├── site.yml
├── group_vars/
│ ├── all.yml # every host
│ └── webservers.yml # hosts in the webservers group
└── host_vars/
└── web01.example.com.yml # one hostWho wins? Precedence, simplified
If the same variable is set in several places, Ansible uses the one with the highest precedence. The full list has over twenty levels; this simplified order (lowest to highest) covers daily work:
- Role defaults (roles/x/defaults/main.yml): meant to be overridden.
- group_vars/all
- group_vars/<group>
- host_vars/<host>
- Facts gathered from the host
- Play vars, vars_files
- Role vars (roles/x/vars/main.yml), task vars
- Extra vars on the command line (-e "web_port=9090"): ALWAYS win.
Rule of thumb: put sensible defaults low (role defaults, group_vars/all), put differences per group or host, and use -e only for one-off overrides.
Facts: what Ansible knows about each host
$ ansible web01.example.com -m ansible.builtin.setup -a "filter=ansible_distribution*"
web01.example.com | SUCCESS => {
"ansible_facts": {
"ansible_distribution": "RedHat",
"ansible_distribution_file_variety": "RedHat",
"ansible_distribution_major_version": "9",
"ansible_distribution_version": "9.4"
},
"changed": false
}- name: Show some facts
ansible.builtin.debug:
msg: >-
{{ ansible_facts['hostname'] }} runs {{ ansible_facts['distribution'] }}
{{ ansible_facts['distribution_version'] }} with
{{ ansible_facts['memtotal_mb'] }} MB RAM at
{{ ansible_facts['default_ipv4']['address'] }}Magic variables are always available: inventory_hostname (the host's name in the inventory), groups (all groups and their hosts), hostvars (every host's variables and facts, so one host can read another's IP), group_names (the groups this host is in).
register: keep a task's result
- name: Check if the app config exists
ansible.builtin.stat:
path: /etc/notes/app.conf
register: conf
- name: Report
ansible.builtin.debug:
msg: "Config present: {{ conf.stat.exists }}"
- name: Get the kernel version
ansible.builtin.command: uname -r
register: kernel
changed_when: false # reading is not a change
- name: Print it
ansible.builtin.debug:
var: kernel.stdoutCommon mistakes
- Variable names with dashes (web-port): not allowed. Use underscores.
- Expecting a group_vars file to load when it is in the wrong folder or named differently from the group.
- Comparing a version fact to a number: ansible_facts['distribution_major_version'] == 9 is false, because the fact is the string "9". Compare with "9", or use | int.
- Turning off gather_facts and then using facts in the play.
Need help?
Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.
Try:
Watch the lesson
Variables, facts and registered results · 1/6
Variables let one playbook fit many hosts. Reference them with double curly braces.
Prefer a video by a teacher? Find free videos on “ansible variables facts precedence group_vars host_vars register” on YouTube ↗
In real life: One playbook for test and production at a fintech
A Mumbai fintech keeps one deploy playbook for both its UAT and production servers. group_vars/uat.yml points the app at the UAT database and sets debug logging; group_vars/prod.yml points at production and sets warning-level logs. The tasks never change; only the inventory group decides which values apply. In an emergency, -e app_maintenance=true puts the site in maintenance mode on one run.
# group_vars/prod.yml
db_host: pgsql-prod.internal.example.com
log_level: warning
app_replicas: 4
# group_vars/uat.yml
db_host: pgsql-uat.internal.example.com
log_level: debug
app_replicas: 1Practice (0/3 done)
web_port is 80 in group_vars/all.yml, 8080 in host_vars/web01.yml, and you run with -e web_port=9090. What does web01 use?
Model simplified variable precedence. Write effective_vars(layers) where layers is a list of dicts from LOWEST to HIGHEST precedence (for example [role_defaults, group_all, group, host, play_vars, extra_vars]). Return one dict where a key from a higher layer replaces the same key from a lower one. Do not modify the input dicts.
In your Ansible project, create group_vars/all.yml (ntp_server, admin_email), group_vars/webservers.yml (web_package: httpd) and host_vars for one host overriding admin_email. Write facts.yml that prints, for each host, its inventory name, distribution and version, IP address, and the effective admin_email.
Quick quiz
Read more (free)
Related help
Before this, learn
This topic builds on these. If something here feels new, open them first.
Related topics
- Loops, conditions and error handlingAnsible in depth · same module
- Jinja2 templatesAnsible in depth · same module
- Roles, collections and ansible-galaxyAnsible in depth · same module
- Ansible playbooksDevOps: Git, Docker, Kubernetes and CI/CD · Configuration management with Ansible, and YAML
- Ansible: inventory and ad-hoc commandsDevOps: Git, Docker, Kubernetes and CI/CD · Configuration management with Ansible, and YAML
- Roles, templates, Galaxy and VaultDevOps: Git, Docker, Kubernetes and CI/CD · Configuration management with Ansible, and YAML
Questions people ask about this
Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.
Which has the LOWEST precedence?
Answer: Role defaults
Role defaults are designed to be overridden.
Which magic variable holds the host's name as written in the inventory?
Answer: inventory_hostname
inventory_hostname; ansible_facts['hostname'] is what the host reports itself.
What does register do?
Answer: Saves the result of a task in a variable
Use the variable in later tasks.
Why add changed_when: false to a command that only reads data?
Answer: So it does not report "changed" every run
Reading is not a change; this keeps the recap honest.
Which module gathers facts manually?
Answer: ansible.builtin.setup
setup is what Gathering Facts runs.
Where do you put variables that apply to every host in the group "web"?Related
Answer: group_vars/web.yml
group_vars/GROUPNAME.yml applies to all hosts in that group.
From Ansible playbooks
A task fails on one host out of ten. What happens by default?Related
Answer: That host stops; the others continue
Failed hosts are removed from the rest of the play.
What does Ansible show for a task whose when is false?Related
Which group contains every host?Related
Pass the quiz (60% or more) or mark this topic as done when you are ready.
Your progress is saved on this device only.
Next steps
- 4 more topics in “Ansible in depth” before its exam: Loops, conditions and error handling, Jinja2 templates, Roles, collections and ansible-galaxy, RHEL System Roles and Ansible Vault.
- Module 10 exam: Ansible in depth · 14 questions · 25 min, without help. 5 topics of this module still to finish first.
- Then the next module, Certifications and career. 57 topics left in the course before the final exam.