В этом уроке разберём, как устроен сбор фактов ansible, как достать конкретное значение, как ускорить плейбук отключением сбора, и как добавить свои собственные факты - и системные через файлы, и вычисляемые прямо на лету.
Что такое ansible facts и откуда они берутся
Когда ты запускаешь плейбук, в самом начале каждого play Ansible незаметно выполняет задачу Gathering Facts. Под капотом это вызов модуля ansible.builtin.setup, который заходит на управляемый узел и снимает с него слепок системы. Результат складывается в одну большую переменную ansible_facts - это словарь с сотнями ключей.
Чтобы своими глазами увидеть, что там собирается, не нужен плейбук. Достаточно одной команды ad-hoc, и это самый быстрый способ понять, какие данные доступны:
Код: Выделить всё
ansible localhost -m ansible.builtin.setup
- ansible_facts['os_family'] - семейство ОS. Для RHEL, CentOS, Rocky, AlmaLinux, Fedora это RedHat; для Ubuntu и Debian - Debian. Главный ключ для кросс-дистрибутивных плейбуков.
- ansible_facts['distribution'] - конкретный дистрибутив: RedHat, Fedora, Ubuntu, а для российских - тут окажется значение вроде RED OS или Astra.
- ansible_facts['distribution_version'] - версия, например 9.4.
- ansible_facts['default_ipv4'] - словарь про основной интерфейс с ключами address, interface, gateway.
- ansible_facts['memtotal_mb'] - всего оперативки в мегабайтах.
- ansible_facts['hostname'] и ansible_facts['fqdn'] - короткое и полное имя.

Сбор фактов ansible на практике: условия и фильтрация
Факты сами по себе бесполезны - ценность в том, чтобы строить на них условия. Связка факт плюс when - это базовый приём, и именно его любят гонять на экзамене EX294. Классический пример: установить нужный веб-сервер в зависимости от семейства ОС.
Код: Выделить всё
- name: Поставить веб-сервер под нужный дистрибутив
hosts: all
become: true
tasks:
- name: RHEL-семейство - ставим httpd
ansible.builtin.dnf:
name: httpd
state: present
when: ansible_facts['os_family'] == "RedHat"
- name: Debian-семейство - ставим apache2
ansible.builtin.apt:
name: apache2
state: present
when: ansible_facts['os_family'] == "Debian"
Полный слепок системы огромный, и каждый раз листать его глазами утомительно. У setup есть фильтр по подмножеству. Хочешь только сетевые факты - не тащи всё:
Код: Выделить всё
ansible web1 -m ansible.builtin.setup -a "filter=ansible_default_ipv4"
ansible web1 -m ansible.builtin.setup -a "gather_subset=network"
gather_facts: false - когда сбор фактов мешает
Сбор фактов ansible не бесплатный. На каждый узел уходит лишний заход по SSH и работа модуля setup, а на сотне-другой хостов это складывается в ощутимые секунды старта. Если плейбук вообще не использует факты - скажем, просто перезапускает сервис или копирует файл по фиксированному пути - сбор можно отключить целиком:
Код: Выделить всё
- name: Быстрый плейбук без фактов
hosts: all
gather_facts: false
tasks:
- name: Перезапустить сервис
ansible.builtin.service:
name: chronyd
state: restarted
Кастомные факты: set_fact и локальные .fact файлы
Встроенных фактов не всегда хватает. Бывает, нужно вычислить значение в процессе работы плейбука или хранить на самом узле какие-то метаданные - роль сервера, окружение, версию приложения. Для этого есть два механизма.
Первый - модуль ansible set_fact. Он создаёт переменную (по сути тот же факт) прямо во время выполнения, на основе других фактов или результатов предыдущих задач:
Код: Выделить всё
- name: Вычислить размер кеша как четверть памяти
ansible.builtin.set_fact:
cache_size_mb: "{{ (ansible_facts['memtotal_mb'] * 0.25) | int }}"
- name: Показать вычисленное значение
ansible.builtin.debug:
msg: "Под кеш выделим {{ cache_size_mb }} МБ"
Второй механизм - локальные (кастомные) факты, они же local facts. Это данные, которые лежат на самом управляемом узле в каталоге /etc/ansible/facts.d/. Любой файл там с расширением .fact Ansible подхватит при сборе фактов. Формат файла - либо INI, либо JSON, либо исполняемый скрипт, печатающий JSON в stdout. Заведём статический факт в формате INI:
Код: Выделить всё
# файл /etc/ansible/facts.d/server.fact на управляемом узле
[role]
type=database
environment=production
Код: Выделить всё
ansible db1 -m ansible.builtin.setup -a "filter=ansible_local"
Типичные грабли
- Статический .fact файл случайно сделали исполняемым. Если на INI- или JSON-файле стоит бит +x, Ansible попытается запустить его как скрипт, получит мусор вместо JSON и сломает сбор. Статические файлы должны быть БЕЗ права на исполнение, а вот скрипты - наоборот, с ним.
- Обращение к плоским ansible_* там, где их отключили. Если inject_facts_as_vars = false, переменная ansible_os_family не существует, и плейбук падает с undefined. Пиши через ansible_facts['os_family'].
- gather_facts: false и тут же обращение к факту. Отключил сбор - не удивляйся, что ansible_facts['distribution'] пустой. Либо включай сбор, либо собирай факты явной задачей.
- set_fact не сохраняется между запусками. Его факты живут только в текущем прогоне. Нужно постоянное хранилище на узле - используй facts.d.
- Кеш фактов и устаревшие данные. Для ускорения можно включить кеширование фактов (fact_caching в ansible.cfg, например на jsonfile или redis). Но если узел изменился - добавили память, сменили IP - кеш отдаст старое значение, пока не истечёт срок. На отладке держи это в голове.
Руками, на тестовой RHEL/Fedora-машине (или в контейнере):
- Выполни ansible localhost -m ansible.builtin.setup и найди в выводе os_family, distribution_version, default_ipv4 и memtotal_mb.
- Прогони сбор с фильтром: filter=ansible_distribution* и отдельно gather_subset=network. Сравни объём вывода.
- Напиши плейбук, который через when и ansible_facts['os_family'] ставит httpd на RedHat и apache2 на Debian.
- Через set_fact вычисли половину памяти узла и выведи debug-ом.
- Положи на узел /etc/ansible/facts.d/server.fact с секцией [role], собери факты и достань значение из ansible_local. Убедись, что файл не исполняемый.
- Какой модуль отвечает за сбор фактов и в какой переменной лежит результат?
- Чем ansible_facts['os_family'] отличается от ansible_facts['distribution'] и когда какой использовать?
- Что делает gather_facts: false и в каком случае это оправдано?
- Где лежат локальные факты, под каким ключом они доступны и почему статический .fact файл нельзя делать исполняемым?
Факты - это глаза Ansible. Модуль setup собирает слепок узла в ansible_facts, ты строишь на нём условия through when, при необходимости фильтруешь сбор или вовсе глушишь его gather_facts: false ради скорости. Свои значения добавляешь двумя путями: set_fact для вычислений на лету и .fact файлы в /etc/ansible/facts.d для постоянных метаданных на узле. На экзамене RHCE связка факт плюс условие всплывает постоянно - доведи её до автоматизма, и половина заданий по идемпотентной настройке решается без раздумий.