Инвентарь - это сердце любого 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 готовый набор хостов с переменными.

Список встроенных и комьюнити-плагинов впечатляет:

  1. amazon.aws.aws_ec2 для инстансов EC2
  2. azure.azcollection.azure_rm для виртуалок в Azure
  3. google.cloud.gcp_compute для Compute Engine
  4. hetzner.hcloud.hcloud для Hetzner Cloud
  5. netbox.netbox.nb_inventory для NetBox
  6. community.general.proxmox для Proxmox VE
  7. community.docker.docker_swarm для Docker Swarm
  8. kubernetes.core.k8s для Kubernetes-нод
  9. 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. Потраченное на продуманную структуру время многократно возвращается потом, когда прогон плейбука на сотне машин проходит ровно за счёт того, что фундамент под ним аккуратный и предсказуемый.