group_vars и host_vars: где это лежит и как работает
Идея в одном предложении: вместо того чтобы писать переменные прямо в строках инвентаря, ты раскладываешь их по файлам, имена которых совпадают с именами групп и хостов. Ansible сам подхватывает их по совпадению имени.
Каталог group_vars/ - переменные для групп. Файл group_vars/webservers применяется ко всем хостам группы webservers. Особый файл group_vars/all - переменные для всех хостов вообще, без исключений. Каталог host_vars/ - переменные для конкретного хоста: host_vars/web1.example.com применяется только к этому хосту.
Расширение можно не писать, но на практике все ставят .yml - так редактор подсвечивает синтаксис. Минимальный проект выглядит так:
Код: Выделить всё
project/
inventory # сам файл инвентаря
group_vars/
all.yml # переменные для ВСЕХ хостов
webservers.yml # для группы webservers
dbservers.yml # для группы dbservers
host_vars/
web1.example.com.yml # лично для web1
site.yml # плейбук
Код: Выделить всё
# group_vars/webservers.yml
---
http_port: 8080
max_clients: 200
app_user: deploy
Код: Выделить всё
# host_vars/web1.example.com.yml
---
http_port: 8443 # этот хост слушает нестандартный порт
Если переменных в группе очень много, вместо одного файла можно сделать каталог с именем группы и накидать туда несколько файлов - Ansible склеит их все. Удобно держать секреты отдельно:
Код: Выделить всё
group_vars/
webservers/
main.yml # обычные настройки
vault.yml # зашифрованный ansible-vault

Группы групп: children и наследование переменных
Реальная инфраструктура не плоская. У тебя есть веб-серверы в Москве и в Питере, базы там же, и иногда хочется сказать "примени ко всему, что в Москве" или "ко всем продакшен-машинам". Для этого в ansible inventory группы умеют вкладываться друг в друга через ключевое слово children. Группа-родитель не содержит хостов напрямую - она объединяет другие группы.
Вот инвентарь в YAML-формате, где children ansible показан наглядно:
Код: Выделить всё
# inventory.yml
all:
children:
moscow:
children:
web_msk:
hosts:
web1.example.com:
web2.example.com:
db_msk:
hosts:
db1.example.com:
spb:
children:
web_spb:
hosts:
web3.example.com:
webservers:
children:
web_msk:
web_spb:
Теперь наследование. Хост получает переменные из ВСЕХ своих групп, и они сливаются. Файл group_vars/moscow.yml применится к web1, db1 и любому хосту внутри moscow, потому что они её потомки:
Код: Выделить всё
# group_vars/moscow.yml
---
ntp_server: ntp.msk.example.com
timezone: Europe/Moscow
Код: Выделить всё
[web_msk]
web1.example.com
web2.example.com
[moscow:children]
web_msk
db_msk
Самый частый вопрос новичка: "у меня переменная задана в двух группах, какая победит?". Запомни правила, на экзамене и в проде они одинаковые.
- host_vars сильнее group_vars. Если порт задан и в group_vars/webservers, и в host_vars/web1 - на web1 победит host_vars. Хост всегда конкретнее группы.
- Дочерняя группа сильнее родительской. Переменная из group_vars/web_msk перебьёт ту же переменную из group_vars/moscow, потому что web_msk - потомок (ребёнок) moscow и он ближе к хосту.
- group_vars/all - самый слабый из групповых. Это база, поверх которой ложится всё остальное.
- Группы одного уровня сливаются по алфавиту. Если хост в двух несвязанных группах с одинаковой переменной, побеждает та, чьё имя позже по алфавиту (последняя загруженная перетирает предыдущие). Звучит хрупко - так и есть.
Код: Выделить всё
# group_vars/critical_overrides.yml
---
ansible_group_priority: 10
http_port: 9999
Несколько файлов инвентаря и спецгруппы
Можно скормить Ansible не один файл, а целый каталог: -i inventory/. Тогда движок прочитает все файлы внутри и объединит их в один инвентарь. Удобно бить по смыслу - hosts.yml отдельно, динамические источники отдельно.
Две группы существуют всегда, даже если ты их не объявлял. all - вообще все хосты инвентаря. ungrouped - хосты, которые не попали ни в одну пользовательскую группу. Поэтому group_vars/all.yml и работает магически: группа all встроенная.
Про диапазоны хостов и шаблоны имён. Когда у тебя пятьдесят однотипных веб-нод, не пиши их в столбик - используй числовой диапазон [01:50]. Ведущий ноль важен: он включает дополнение нулями, и хосты будут web01, web02, ..., web50:
Код: Выделить всё
[webfarm]
web[01:50].example.com
Промышленная структура каталога проекта, к которой стоит стремиться, разделяет окружения и держит для каждого свой инвентарь и свои group_vars:
Код: Выделить всё
project/
inventories/
production/
hosts.yml
group_vars/
all.yml
webservers.yml
host_vars/
web1.prod.example.com.yml
staging/
hosts.yml
group_vars/
all.yml
webservers.yml
roles/
site.yml
Маленькая пометка про дистрибутивы: всё сказанное от дистрибутива не зависит, group_vars и children одинаковы хоть на RHEL/Fedora с dnf и firewalld, хоть на Ubuntu/Debian с apt, хоть на RED OS или Astra. Различия начинаются внутри задач (имена пакетов, сервисов), а структура инвентаря универсальна.
Типичные грабли
- Имя файла не совпадает с именем группы. group_vars/webserver.yml (без s) для группы webservers просто молча не применится. Ошибки не будет - переменные тихо проигнорируются.
- Каталог не там, где запускаешь. Если group_vars лежит рядом с инвентарём, а ты дал -i на отдельный файл из другого места - проверь, что каталог действительно соседствует с инвентарём.
- Точка в имени хоста. Файл host_vars/web1.example.com.yml должен содержать полное имя ровно как в инвентаре. Если в инвентаре web1, а файл web1.example.com.yml - не совпадёт.
- Надежда на алфавитный порядок. Если две равноуровневые группы спорят за переменную, не угадывай - задай ansible_group_priority явно.
- Секреты в открытом виде. Пароли в group_vars шифруй через ansible-vault, не коммить пароли в git как есть.
Собери руками каталог проекта с инвентарём в YAML. Создай группы web_msk и web_spb, объедини их в webservers через children, а web_msk и db_msk - в moscow. Задай http_port=80 в group_vars/all.yml, http_port=8080 в group_vars/webservers.yml и http_port=8443 в host_vars для одного конкретного веб-хоста. Проверь, кто победит, командой:
Код: Выделить всё
ansible -i inventory.yml web1.example.com -m ansible.builtin.debug -a "var=http_port"
ansible-inventory -i inventory.yml --graph
ansible-inventory -i inventory.yml --host web1.example.com
Контрольные вопросы
- Где должны лежать каталоги group_vars и host_vars и какой из двух валидных вариантов выигрывает при конфликте?
- Хост входит в группу moscow и в её дочернюю web_msk, переменная задана в обеих - какая применится и почему?
- Что делает ключевое слово children и может ли группа-родитель содержать хосты напрямую?
- Как сделать так, чтобы при споре двух равноуровневых групп переменная бралась из нужной, а не по алфавиту?
group_vars и host_vars - это не "ещё один способ", а стандарт оформления инвентаря, который экономит время и нервы. Раскладывай переменные по файлам с именами групп и хостов, объединяй группы через children, помни цепочку precedence all -> родитель -> ребёнок -> host_vars, а спорные случаи закрывай ansible_group_priority. Делай отдельные каталоги инвентаря под prod и staging - и один и тот же плейбук поедет в любое окружение без правок. На EX294 это та база, которая разгружает голову под остальные задачи.