Инвентарь - это сердце любого Ansible-проекта. Плейбуки описывают, что нужно сделать, а инвентарь - с кем именно. Без понятного списка хостов автоматизация превращается в лотерею, где одна и та же команда случайно прилетает то на продакшен-базу, то на тестовый стенд разработчика. Правильно выстроенный инвентарь даёт обратное - предсказуемость, повторяемость и уверенность, что playbook bounce-all-webservers затронет именно те сервера, которые задумывались.
Ansible поддерживает несколько форматов описания инвентаря, и выбор между ними редко случайный. INI подойдёт для небольшого набора машин и быстрых экспериментов. YAML раскрывается на сложных структурах с вложенными группами и переменными. Динамический инвентарь через API снимает вопрос актуальности списка в средах, где серверы появляются и исчезают сами по себе - облака, автоскейлинг, IaaS-панели, CMDB-системы.
INI как простой и быстрый способ описать инвентарь
INI-формат - исторический дефолт Ansible, и до сих пор самый быстрый способ описать десяток-другой хостов. Минимальный файл выглядит почти без синтаксиса:
mail.example.com
[webservers]
web01.example.com
web02.example.com
web03.example.com
[databases]
db01.example.com
db02.example.com
[webservers:vars]
http_port=80
deploy_user=www-data
[all:vars]
ansible_python_interpreter=/usr/bin/python3
Заголовки в квадратных скобках - имена групп. Одиночные хосты вне групп попадают в неявную группу ungrouped. Блок group:vars задаёт переменные для всех участников группы, all:vars - переменные уровня всего инвентаря. Конструкция group:children объединяет несколько групп в мета-группу:
[production:children]
webservers
databases
INI умеет и диапазоны, что сильно экономит руки при описании серверной фермы:
[webservers]
web[01:50].example.com
[dbservers]
db-[a:f].example.com
Формат получается компактный, читаемый за секунду, и отлично подходит для проектов на пять-двадцать хостов без сложной иерархии. Проблемы начинаются, когда нужно описать сложную переменную вроде списка или словаря - INI плоский по природе и такие вещи не умеет. Там, где требуется вложенность, вступает YAML.
YAML как формат для серьёзных инвентарей с вложенной структурой
YAML-инвентарь решает ровно те задачи, где INI спотыкается. Словари, списки, иерархия групп произвольной глубины, переменные со структурой. Тот же самый пример, но в YAML:
all:
children:
webservers:
hosts:
web01.example.com:
ansible_host: 10.0.1.10
http_port: 80
web02.example.com:
ansible_host: 10.0.1.11
http_port: 8080
vars:
deploy_user: www-data
nginx_workers: auto
databases:
hosts:
db01.example.com:
ansible_host: 10.0.2.10
replication_role: primary
db02.example.com:
ansible_host: 10.0.2.11
replication_role: replica
vars:
postgres_version: "16"
production:
children:
webservers:
hosts: {}
databases:
hosts: {}
vars:
ansible_python_interpreter: /usr/bin/python3
ansible_ssh_private_key_file: ~/.ssh/deploy_key
Отдельные хостовые переменные (ansible_host, http_port) прописываются прямо рядом с именем хоста. Групповые живут в секции vars. Иерархия production.children содержит webservers и databases, при этом одна и та же группа webservers может быть дочерней сразу у нескольких родителей - это нормально и работает.
На практике YAML-инвентарь редко умещается в один файл. Канонический подход - использовать директорию inventory/ со структурой файлов group_vars и host_vars:
inventory/
├── hosts.yml
├── group_vars/
│ ├── all.yml
│ ├── webservers.yml
│ ├── databases.yml
│ └── production.yml
└── host_vars/
├── web01.example.com.yml
└── db01.example.com.yml
Ansible автоматически подтягивает файлы из group_vars и host_vars, сопоставляя их имена с именами групп и хостов. Это решает проблему раздутого hosts.yml - переменные разъезжаются по тематическим файлам, каждый из которых остаётся обозримым. В git-репозитории такие файлы прекрасно просматриваются в diff'ах, отдельные правки не приводят к мерж-конфликтам.
Секретные переменные шифруются через ansible-vault, который умеет работать как с отдельными значениями через !vault, так и с целыми файлами. База паролей и API-токенов живёт в group_vars/all/vault.yml, зашифрованном мастер-паролем, и коммитится в git без риска утечки.
Проверка инвентаря до первого playbook-запуска
Опытный администратор не запускает ansible-playbook на непроверенном инвентаре. Команда ansible-inventory показывает, что именно Ansible понял из файлов:
ansible-inventory -i inventory/ --list
ansible-inventory -i inventory/ --graph
ansible-inventory -i inventory/ --host web01.example.com
Флаг --list выводит всё как JSON, полезно для автоматики. --graph рисует дерево групп и хостов в читаемом виде. --host показывает итоговый набор переменных для конкретного хоста, включая те, что пришли из group_vars.
Ping-тест - самая быстрая проверка связности:
ansible -i inventory/ all -m ping
ansible -i inventory/ webservers -m ping
Если часть хостов не отвечает, лучше узнать об этом до, а не во время прогона многочасового плейбука.
Когда статики становится мало и на сцену выходит динамический инвентарь
Есть сценарии, где ведение списка машин вручную быстро превращается в кошмар. Автоскейлинг в AWS создаёт и убивает EC2-инстансы по метрикам нагрузки. Kubernetes-нода в облачном кластере может переехать на другой физический сервер в любой момент. В NetBox или ServiceNow CMDB список устройств - единый источник правды для всей организации, и копирование его в YAML-файл Ansible означает гарантированное расхождение уже через неделю.
Динамический инвентарь решает эту проблему принципиально. Вместо статического файла Ansible при запуске каждый раз обращается к внешнему источнику - API облачного провайдера, базе данных, CMDB - и строит актуальный список хостов прямо перед выполнением плейбука.
Исторически эту задачу решали скриптами, которые возвращали JSON с описанием инвентаря (старый механизм ec2.py, nsible.py и тому подобные). Современный подход - inventory plugins, которые пишутся на Python, распространяются через коллекции и настраиваются через YAML-файл. Плагин парсит конфиг, обращается к API и отдаёт Ansible готовый набор хостов с переменными.
Список встроенных и комьюнити-плагинов впечатляет:
- amazon.aws.aws_ec2 для инстансов EC2
- azure.azcollection.azure_rm для виртуалок в Azure
- google.cloud.gcp_compute для Compute Engine
- hetzner.hcloud.hcloud для Hetzner Cloud
- netbox.netbox.nb_inventory для NetBox
- community.general.proxmox для Proxmox VE
- community.docker.docker_swarm для Docker Swarm
- kubernetes.core.k8s для Kubernetes-нод
- community.general.nmap для обнаружения хостов сетевым сканом
Каждый плагин живёт в своей коллекции, устанавливается через ansible-galaxy collection install и настраивается по собственному YAML-шаблону.
Плагин aws_ec2 как образцовый пример работы с облачным API
AWS EC2 - классический пример среды, где динамический инвентарь раскрывается в полную силу. Инстансы появляются и исчезают через автоскейлинг, спот-флоты и ручные запуски, держать список в хэдкоде бессмысленно.
Установка коллекции:
ansible-galaxy collection install amazon.aws
Базовый конфиг aws_ec2.yml выглядит так:
plugin: amazon.aws.aws_ec2
regions:
- eu-central-1
- us-east-1
filters:
instance-state-name: running
tag:Environment:
- production
- staging
keyed_groups:
- key: tags.Environment
prefix: env
- key: tags.Role
prefix: role
- key: placement.availability_zone
prefix: az
- key: instance_type
prefix: type
hostnames:
- tag:Name
- dns-name
- private-ip-address
compose:
ansible_host: public_ip_address | default(private_ip_address)
Строка plugin обязательна и указывает полное имя плагина. Regions перечисляет регионы для опроса. Filters сужает выборку только нужными инстансами - фильтр instance-state-name отсекает остановленные машины, теги задают бизнес-контекст. Keyed_groups автоматически собирает группы по значениям тегов: инстансы с тегом Environment=production попадут в группу env_production, с Role=webserver - в role_webserver. Hostnames задаёт приоритетный порядок выбора имени хоста, compose добавляет вычисляемые переменные.
Имя файла значимо - плагин aws_ec2 ожидает шаблон *.aws_ec2.yml или *.aws_ec2.yaml. Файл не с таким окончанием Ansible не поймёт как конфиг плагина и попытается распарсить его как INI или YAML-инвентарь.
Аутентификация в AWS обычно идёт через переменные окружения AWS_ACCESS_KEY_ID и AWS_SECRET_ACCESS_KEY, через профили в ~/.aws/credentials или через IAM-роль, если контроллер Ansible сам крутится в EC2. Вписывать ключи в файл инвентаря категорически не стоит - он коммитится в git, а значит секреты окажутся в истории.
Проверка результата:
ansible-inventory -i aws_ec2.yml --graph
ansible -i aws_ec2.yml env_production -m ping
Первая команда покажет все автоматически созданные группы, вторая пропингует только машины из env_production. Удобство, которое превращает управление сотней серверов из хаоса в рутину.
Плагин nb_inventory для NetBox и интеграция с внешним источником правды
NetBox - популярная IPAM/DCIM-система, где многие компании ведут учёт всех устройств, от коммутаторов до виртуалок. Для такой организации NetBox становится единой точкой правды, и тянуть инвентарь для Ansible из него - естественное решение.
Установка коллекции:
ansible-galaxy collection install netbox.netbox
Конфиг netbox.yml:
plugin: netbox.netbox.nb_inventory
api_endpoint: https://netbox.example.com
token: "{{ lookup('env', 'NETBOX_TOKEN') }}"
validate_certs: true
config_context: false
group_by:
- sites
- tenants
- device_roles
- platforms
- tags
query_filters:
- status: active
- has_primary_ip: "true"
device_query_filters:
- manufacturer: cisco
compose:
ansible_host: primary_ip4.address | regex_replace('/.*', '')
Токен идёт из переменной окружения, а не пишется в файле. group_by автоматически строит иерархию групп по атрибутам устройств в NetBox. query_filters отсекает неактивные и непригодные для автоматизации устройства. compose превращает primary_ip4 (который приходит в формате x.x.x.x/24) в чистый IP, пригодный для SSH-подключения.
После настройки любое изменение в NetBox автоматически отражается в инвентаре Ansible - без ручной синхронизации, без устаревших записей, без драмы "мы же убрали этот сервер ещё в прошлом месяце".
Комбинирование источников и как собрать инвентарь из кусочков
Реальный проект редко живёт на чистом статическом или чистом динамическом инвентаре. Типичная картина - смесь: несколько физических серверов описаны в YAML руками, облачные инстансы приходят из aws_ec2, сетевое оборудование - из NetBox. Ansible умеет это комбинировать.
Директория inventory/ может содержать любое количество источников разных форматов одновременно:
inventory/
├── on-prem.yml # статический YAML для физики
├── aws_ec2.yml # динамический плагин AWS
├── netbox.yml # динамический плагин NetBox
├── group_vars/
│ ├── all.yml
│ └── webservers.yml
└── host_vars/
Указание -i inventory/ говорит Ansible прочитать всю директорию и объединить результаты. Порядок парсинга - алфавитный, и это иногда важно, потому что плагин constructed (создающий группы на лету по переменным существующих хостов) должен выполниться последним. Чтобы управлять порядком, файлы именуют с префиксами: 01-on-prem.yml, 02-aws_ec2.yml, 99-constructed.yml.
Альтернатива - передать несколько -i флагов при запуске:
ansible-playbook -i inventory/on-prem.yml -i inventory/aws_ec2.yml site.yml
Такой синтаксис даёт явный контроль над порядком и используется, когда автоматическая алфавитная сортировка неудобна.
Специальные переменные и тонкости, о которых стоит знать
Ansible резервирует набор переменных с префиксом ansible_, которые управляют подключением и поведением контроллера. Самые важные:
ansible_host # реальный адрес для подключения, если отличается от inventory_hostname
ansible_port # SSH-порт, по умолчанию 22
ansible_user # пользователь для SSH
ansible_connection # тип подключения: ssh, local, winrm, docker
ansible_python_interpreter # путь к Python на целевой машине
ansible_become # использовать sudo
ansible_ssh_private_key_file # путь к приватному ключу
Разница между inventory_hostname и ansible_host тонкая, но принципиальная. Inventory_hostname - это имя, под которым хост фигурирует в инвентаре (и в логах, выводе kubectl-подобных команд). Ansible_host - фактический адрес для подключения. Их часто имеет смысл разделять: в инвентаре хост называется осмысленно (web-prod-frankfurt-01), а подключаемся по внутреннему IP 10.0.1.47.
Windows-хосты требуют отдельной настройки подключения через WinRM:
[windows:vars]
ansible_connection=winrm
ansible_winrm_transport=ntlm
ansible_port=5986
ansible_winrm_server_cert_validation=ignore
Сетевое оборудование подключается через специализированные драйверы. Для Cisco IOS - ansible_connection=network_cli, ansible_network_os=ios. Инвентарь Ansible отлично справляется не только с серверами, но и с коммутаторами, маршрутизаторами и файрволлами.
Привычки, которые превращают инвентарь в стабильный фундамент
Опыт эксплуатации накапливает простые правила, которые спасают нервы. Имена хостов в инвентаре должны быть осмысленными и следовать конвенции - окружение, роль, регион, порядковый номер. Секреты шифруются через ansible-vault и никогда не лежат в открытом виде. Каждая среда (dev, staging, prod) имеет свой инвентарь или чётко обозначенные группы - случайный прогон миграции базы на production вместо staging дорого обходится.
Статический YAML-инвентарь ревьюится через pull-request наравне с кодом. Любое изменение списка хостов - коммит с описанием, что и зачем добавлено. Динамические источники документируются: где лежит конфиг плагина, какие переменные окружения нужны, как обновлять токены. Через полгода никто не вспомнит, зачем в aws_ec2.yml стоит именно такой фильтр - описание в комментарии рядом экономит часы расследования.
Тестирование изменений в инвентаре идёт через --check и --diff в связке с ansible-inventory. Перед любым серьёзным рефакторингом структуры групп стоит прогнать ansible-inventory --graph и сравнить до и после. Глазами видно, если группа вдруг опустела или хост случайно попал не в ту категорию.
Инвентарь Ansible - это не просто список IP-адресов. Это модель инфраструктуры, отражающая её структуру, роли, окружения и зависимости. Вложенные группы превращаются в абстракции, переменные - в конфигурационную базу, динамические плагины - в мост между автоматизацией и реальным положением дел в облаке или CMDB. Потраченное на продуманную структуру время многократно возвращается потом, когда прогон плейбука на сотне машин проходит ровно за счёт того, что фундамент под ним аккуратный и предсказуемый.