Выберите продукт

Ansible inventory и group_vars для staging и production: структура проекта без хаоса

Когда Ansible-проект растёт, хаос обычно начинается с inventory и переменных: staging и production смешиваются, а итоговое значение параметра приходится искать по всему репозиторию. Разберём удобную структуру без дублирования и сюрпризов.
Ansible inventory и group_vars для staging и production: структура проекта без хаоса

В небольшом Ansible-проекте можно долго жить с одним файлом hosts, парой переменных в playbook и договорённостями в голове. Но как только появляются отдельные окружения, например staging и production, всё быстро усложняется. Одни серверы должны подключаться к тестовой базе, другие — к боевой. Где-то включён debug, где-то жёсткие лимиты. У одного хоста нестандартный SSH-порт, у другого другой PHP-FPM socket, а у третьего вообще отдельный upstream.

Если в этот момент не привести проект к нормальной структуре, через пару месяцев вы получите набор YAML-файлов, где переменные дублируются, значения конфликтуют, а любое изменение в production страшно запускать. Хорошая новость в том, что Ansible давно даёт для этого штатные механизмы: inventory, group_vars, host_vars и понятную иерархию окружений.

Ниже разберём практичную схему: как организовать inventory для нескольких сред, где хранить общие и специфичные переменные, как не запутаться в group_vars и host_vars, и почему понимание приоритета переменных важнее, чем кажется на старте.

Сразу практический тезис: если у вас есть staging и production, не пытайтесь различать их только одной переменной внутри общего inventory, если инфраструктура уже заметно расходится. В большинстве случаев удобнее явно оформить среды как отдельные группы или даже как отдельные inventory-источники.

И ещё один тезис: структура проекта должна отвечать на три вопроса без поиска по репозиторию. Какие есть хосты? Какие есть среды? Откуда берётся конкретное значение переменной на конкретной машине? Если структура на это не отвечает, её стоит упростить.

Что именно ломается без нормальной структуры

Проблемы обычно повторяются от проекта к проекту. Вроде бы всё работает, но любое изменение становится рискованным. Типичный сценарий выглядит так: сначала в inventory лежит список серверов, потом рядом появляются переменные группы, потом кто-то добавляет исключения на уровне playbook, а через время уже непонятно, почему на staging nginx слушает один порт, а на production другой.

Главная причина — переменные распределены случайно. Часть живёт в inventory, часть в group_vars/all.yml, часть в ролях, часть в host_vars, а критичные значения вообще передаются через -e в CI. Технически Ansible это переварит, но сопровождать такую схему комфортно не получится.

Хорошая Ansible-структура — это не «красивый репозиторий», а предсказуемость. Вы должны заранее понимать, что применится к хосту ещё до запуска playbook.

Отсюда и простое правило: общие параметры кладём в общее место, отличия сред — на уровень среды, уникальные исключения — только в host_vars и только когда они действительно нужны.

Базовая структура Ansible-проекта для нескольких окружений

Для большинства команд удобна структура, где inventory разделён по средам, а переменные лежат рядом с ними. Это делает проект понятнее и уменьшает вероятность случайно применить staging-настройки к production.

ansible/
├── ansible.cfg
├── inventories/
│   ├── staging/
│   │   ├── hosts.yml
│   │   ├── group_vars/
│   │   │   ├── all.yml
│   │   │   ├── web.yml
│   │   │   ├── db.yml
│   │   │   └── app.yml
│   │   └── host_vars/
│   │       └── stage-web-01.yml
│   └── production/
│       ├── hosts.yml
│       ├── group_vars/
│       │   ├── all.yml
│       │   ├── web.yml
│       │   ├── db.yml
│       │   └── app.yml
│       └── host_vars/
│           └── prod-web-01.yml
├── playbooks/
│   ├── site.yml
│   ├── web.yml
│   └── db.yml
└── roles/
    ├── common/
    ├── nginx/
    ├── php/
    └── postgresql/

Это не единственно верный вариант, но он хорошо масштабируется. У staging и production свои inventory-файлы, свои group_vars и свои host_vars. Общие playbook и роли при этом переиспользуются.

Плюс такого подхода в том, что окружения изолированы логически. Минус — если у вас много одинаковых значений, придётся решить, где хранить действительно глобальные переменные, чтобы не копировать их между средами. Обычно для этого используют defaults ролей и аккуратно организованные переменные уровня all.

Если staging и production разворачиваются на отдельных серверах, для такой схемы чаще всего удобнее использовать VDS: проще повторять топологию, держать изоляцию окружений и не упираться в ограничения общего сервера.

Пример inventory в YAML

Если вы используете YAML inventory, структура читается заметно лучше, чем старый INI-формат, особенно когда есть вложенные группы.

all:
  children:
    web:
      hosts:
        stage-web-01:
          ansible_host: 10.10.10.11
        stage-web-02:
          ansible_host: 10.10.10.12
    app:
      hosts:
        stage-app-01:
          ansible_host: 10.10.20.11
    db:
      hosts:
        stage-db-01:
          ansible_host: 10.10.30.11

Для production будет такой же файл, но со своими адресами, составом узлов и, возможно, иной топологией. Например, на staging может быть один узел БД, а на production — primary и replica. Это нормально: inventory должен отражать реальность, а не пытаться сделать среды искусственно одинаковыми.

Пример структуры Ansible-проекта с inventories, group_vars и host_vars

Когда базовая структура уже определена, можно отдельно подумать о стандартах именования, хранении секретов и правилах ревью. Но сначала важно добиться того, чтобы любая среда читалась как самостоятельная и понятная часть проекта.

FastFox VDS
Облачный VDS-сервер в России
Аренда виртуальных серверов с моментальным развертыванием инфраструктуры от 195₽ / мес

Как распределять group_vars и host_vars без дублирования

Частый вопрос — что класть в group_vars, а что в host_vars. Ответ простой: всё, что относится к роли группы серверов, храните в group_vars. Всё, что уникально для конкретного узла, — в host_vars.

Например, если все web-серверы в production должны иметь одинаковые параметры nginx, имя системного пользователя приложения, директорию релиза и настройки healthcheck — это кандидат в group_vars/web.yml. Если только один хост слушает нестандартный порт SSH или имеет дополнительный диск, это уже host_vars.

app_name: myapp
app_user: myapp
app_group: myapp
deploy_path: /var/www/myapp
nginx_vhost_template: app.conf.j2
healthcheck_path: /healthz

Пример для production group_vars/all.yml:

env_name: production
app_env: prod
debug_enabled: false
backup_enabled: true
monitoring_enabled: true

Пример для staging group_vars/all.yml:

env_name: staging
app_env: stage
debug_enabled: true
backup_enabled: false
monitoring_enabled: true

Идея здесь в том, что у вас есть одна и та же логика ролей, но среда меняет поведение через переменные. Роль nginx не должна знать, staging это или production. Она должна читать уже подготовленные значения.

Когда host_vars действительно нужны

У многих проектов host_vars со временем превращаются в свалку. Это тревожный сигнал. Если у вас половина бизнес-логики конфигурации лежит на уровне отдельных хостов, значит, вы недоопределили группы.

Например, если два web-сервера отличаются от третьего не случайно, а постоянно, возможно, нужна дополнительная группа: web_canary, web_legacy, queue, cron. Тогда отличия будут выражены структурно, а не через набор частных исключений.

  • Используйте host_vars для адресов, индивидуальных override и редких особенностей узла.

  • Используйте group_vars для роли сервера в инфраструктуре.

  • Если host-specific переменных слишком много, пересмотрите модель групп.

Staging и production: одна схема, разные цели

В теме разделения сред часто допускают две крайности. Первая — делают среды абсолютно идентичными, хотя staging для этого слишком дорогой. Вторая — staging настолько отличается от production, что перестаёт быть полезным для проверки изменений. Истина обычно посередине.

Staging не обязан повторять production по мощности, но должен повторять его по архитектурным принципам. Если в production приложение работает за reverse proxy, с отдельным app-слоем и внешней БД, staging желательно держать по той же схеме, даже если там меньше узлов и скромнее ресурсы.

С точки зрения Ansible это значит, что структура ролей и логика deploy должны быть общими, а отличаться должны только переменные среды. Например, число воркеров, размеры пулов, флаги debug, доменные имена, backup policy, адреса мониторинга и параметры интеграций.

php_fpm_pm_max_children: 20
nginx_worker_connections: 1024
app_domain: stage.example.internal

Для production:

php_fpm_pm_max_children: 80
nginx_worker_connections: 4096
app_domain: example.com

Если разница между средами выражена только значениями, а не переписанными ролями, вы движетесь в правильную сторону.

Если у вас в staging и production используются разные домены и поддомены, полезно заранее продумать не только inventory, но и схему переездов, редиректов и сертификатов. По этой теме пригодится материал про миграцию домена, 301, HSTS и SSL.

Приоритет переменных: почему одно значение «побеждает» другое

Тема приоритета переменных пугает новичков не зря. Большая часть странных эффектов в Ansible возникает не из-за багов, а из-за того, что одно и то же имя переменной объявлено в нескольких местах. Например, в defaults роли, в group_vars/all, в group_vars/web, в host_vars и ещё передано через -e в extra vars.

Полную официальную таблицу помнить наизусть не обязательно, но базовую практическую иерархию понимать нужно. В обычной жизни мыслить можно так: defaults роли — самое слабое значение; групповые и хостовые переменные — сильнее; переменные, переданные напрямую через -e, почти всегда самые приоритетные.

Из-за этого есть простое эксплуатационное правило: не используйте одни и те же имена переменных на всех уровнях без необходимости. Если переменная действительно задаётся пользователем роли, оставьте её в defaults/main.yml. Если это внутренняя служебная переменная роли, лучше дать ей специфичное имя и не переопределять снаружи без крайней нужды.

Если вам приходится открывать пять файлов, чтобы понять итоговое значение переменной, проблема уже не в Ansible, а в дизайне проекта.

Полезный приём — разделять «внешние» и «внутренние» переменные роли. Например, снаружи задаётся app_domain, а внутри роли из неё вычисляется путь к конфигу, имя upstream и прочие производные параметры. Это уменьшает хаос и количество случайных конфликтов.

Практика именования переменных

Одна из лучших привычек — использовать префиксы по контексту. Не просто user или port, а nginx_listen_port, app_user, postgresql_max_connections. Тогда шанс коллизии сильно ниже, особенно в больших проектах с несколькими ролями.

Это особенно важно, когда над инфраструктурой работает не один человек. Через полгода никто не вспомнит, к чему относился абстрактный timeout, а вот nginx_proxy_read_timeout и php_fpm_request_terminate_timeout уже не перепутать.

Секреты здесь тоже лучше отделять от обычных переменных. Если вы храните чувствительные данные в репозитории, делайте это в зашифрованном виде. Для командной работы может быть полезен и отдельный разбор про SOPS, age и хранение секретов в GitOps-пайплайнах.

Если инфраструктура публикует сервисы по HTTPS, не откладывайте управление сертификатами на потом: для production это такая же часть предсказуемой конфигурации, как inventory и переменные. В этом контексте могут пригодиться SSL-сертификаты.

FastFox SSL
Надежные SSL-сертификаты
Мы предлагаем широкий спектр SSL-сертификатов от GlobalSign по самым низким ценам. Поможем с покупкой и установкой SSL бесплатно!

Рабочая модель project structure для команды

Если нужен не просто учебный пример, а схема, с которой удобно жить в команде, я бы рекомендовал следующее разделение ответственности.

  • inventories/staging и inventories/production отвечают за состав окружений и их переменные.

  • roles содержат только переиспользуемую логику и разумные defaults.

  • playbooks описывают сценарии применения ролей к группам.

  • Секреты не смешиваются с обычными vars и хранятся отдельно в зашифрованном виде.

Пример site.yml:

- name: Configure web servers
  hosts: web
  become: true
  roles:
    - common
    - nginx
    - php

- name: Configure app servers
  hosts: app
  become: true
  roles:
    - common

- name: Configure database servers
  hosts: db
  become: true
  roles:
    - common
    - postgresql

Запуск тогда становится предсказуемым:

ansible-playbook -i inventories/staging/hosts.yml playbooks/site.yml
ansible-playbook -i inventories/production/hosts.yml playbooks/site.yml

На уровне CI это тоже удобно: одна и та же команда, только с разным inventory. Минимум магии, максимум повторяемости.

Проверка Ansible inventory командами ansible-inventory в терминале

Частые ошибки в inventory и переменных

Самая частая ошибка — пытаться хранить слишком много логики в inventory. Inventory должен описывать инфраструктуру: хосты, группы, адреса, connection details и средовые отличия. Но как только туда начинают складывать пол-логики приложения, сопровождать это становится больно.

Вторая ошибка — копировать целиком group_vars между staging и production, а потом годами поддерживать два почти одинаковых дерева. В результате любое изменение надо не забыть внести в оба места. Лучше выносить общие значения в defaults ролей или в общую переменную уровня проекта, а в средах держать только отличия.

Третья ошибка — не проверять итоговую картину inventory перед прогоном. В Ansible есть штатные способы посмотреть, как он видит группы и хосты. Это полезно делать после любого заметного рефакторинга.

ansible-inventory -i inventories/staging/hosts.yml --graph
ansible-inventory -i inventories/staging/hosts.yml --list
ansible-inventory -i inventories/production/hosts.yml --graph

Четвёртая ошибка — использовать host_vars как средство срочного «быстрого фикса». Один раз это спасает, но потом там накапливаются исключения, которые никто не ревизует. Если override стал постоянным, его нужно либо перенести в группу, либо переосмыслить модель ролей.

Как выбрать между одним inventory и несколькими

Технически можно держать staging и production в одном inventory, оформив их как дочерние группы. Для маленьких проектов это бывает удобно. Но когда среды начинают заметно различаться по составу серверов, ролям узлов и перечню переменных, отдельные inventory обычно проще поддерживать.

Один inventory удобен, если:

  • среды очень похожи по топологии;

  • команда небольшая;

  • важно быстро видеть всё дерево хостов в одном месте.

Раздельные inventory удобнее, если:

  • production существенно сложнее staging;

  • есть разные источники хостов или разные правила доступа;

  • вы хотите минимизировать риск случайного запуска не по той среде.

На практике я чаще советую отдельные inventory-каталоги по средам. Это чуть более многословно, зато сильно понятнее в эксплуатации.

Минимальный набор best practices, который реально окупается

Есть много красивых рекомендаций, но если оставить только то, что даёт максимальную пользу в админской реальности, получится довольно компактный набор.

  1. Используйте YAML inventory и человекочитаемые имена групп.

  2. Разделяйте окружения явно: staging, production, при необходимости dev.

  3. Храните общую логику в ролях, а различия сред — в group_vars.

  4. Используйте host_vars только для настоящих исключений.

  5. Не злоупотребляйте -e для постоянных значений.

  6. Давайте переменным контекстные имена.

  7. Проверяйте inventory командами ansible-inventory после изменений.

  8. Документируйте, где должен жить каждый класс переменных.

Последний пункт особенно недооценивают. Даже короткий README в корне проекта с правилами наподобие «средовые переменные — только в inventories/*/group_vars/all.yml» экономит массу времени и споров.

Итоговая схема, которую не стыдно развивать дальше

Если свести всё к одному рабочему шаблону, то для большинства команд удобен такой подход: отдельные inventory для staging и production, общие playbook, переиспользуемые роли, group_vars для групп и сред, host_vars только для исключений, плюс аккуратные имена переменных и понимание их приоритета.

Именно такая структура лучше всего отвечает на вопросы эксплуатации: что изменится при запуске, откуда берётся значение, как безопасно расширить проект и куда положить новую настройку без создания очередного слоя хаоса.

Если вы только приводите в порядок существующий репозиторий, не пытайтесь переписать всё сразу. Начните с разделения inventory по средам, затем вынесите повторяющиеся значения в group_vars, после этого ревизуйте host_vars и уже потом полируйте роли. Пошаговая миграция почти всегда безопаснее большого «идеального» рефакторинга.

У Ansible хватает нюансов, но именно структура проекта определяет, станет он надёжным рабочим инструментом или источником постоянных сюрпризов. Если inventory и переменные устроены понятно, staging и production перестают мешать друг другу, а изменения становятся предсказуемыми. Для админа это и есть главный признак хорошей автоматизации.

Поделиться статьей

Вам будет интересно

Linux: /etc/fstab и emergency mode (rescue) — как быстро поднять систему после ошибки монтирования OpenAI Статья написана AI (GPT 5)

Linux: /etc/fstab и emergency mode (rescue) — как быстро поднять систему после ошибки монтирования

Если после перезагрузки Linux падает в emergency mode или rescue из‑за /etc/fstab, чаще всего виноваты неверный UUID, опции монтир ...
Linux: когда зависает PID 1 (systemd) — D-state, stop-jobs и hung_task OpenAI Статья написана AI (GPT 5)

Linux: когда зависает PID 1 (systemd) — D-state, stop-jobs и hung_task

Если растёт load average, сервисы не останавливаются, а перезагрузка висит на stop jobs — часто виноваты D-state и блокировки I/O. ...
systemd hardening: DynamicUser, ProtectSystem и практичный sandboxing для сервисов OpenAI Статья написана AI (GPT 5)

systemd hardening: DynamicUser, ProtectSystem и практичный sandboxing для сервисов

Пошагово усиливаем безопасность systemd-сервисов без контейнеров: включаем DynamicUser, ограничиваем файловую систему через Protec ...