Когда серверов становится больше трёх, ручная настройка начинает подводить: на одной машине забыли параметр, на другой осталась старая строка конфигурации, и поиск расхождений съедает вечер. Ansible переворачивает подход: администратор один раз описывает желаемое состояние в текстовом виде, а инструмент приводит парк машин к этому состоянию, причём без агентов на целевых системах, через обычное SSH-соединение. Ниже разобраны инвентарь с группами и надгруппами, каталоги переменных и порядок их наложения, разделение окружений, структура сценария, повышение прав, обработчики, шаблоны конфигураций и проверки перед настоящим прогоном.

Структура инвентаря с машинами, группами и надгруппами

Инвентарь отвечает на главный вопрос любого прогона: какие машины затронет сценарий. Простейший файл перечисляет хосты построчно, но при парке больше пяти машин без структуры не обойтись. В формате INI группы задаются квадратными скобками, а надгруппы помечаются суффиксом :children:

[web]
web-01 ansible_host=192.168.10.11
web-02 ansible_host=192.168.10.12

[db]
db-01 ansible_host=192.168.10.21

[prod:children]
web
db

Хосты web-01 и web-02 попадают в группу web, db-01 входит в группу db, а надгруппа prod объединяет обе. Переменная ansible_host подменяет адрес подключения: Ansible обращается к адресу из переменной, а имя машины остаётся коротким и читаемым в журналах. Рядом часто задают ansible_user и ansible_port, чтобы не повторять их в каждой команде. Тот же инвентарь в YAML многословнее, зато вложенность групп читается без знания синтаксиса:

all:
  children:
    prod:
      children:
        web:
          hosts:
            web-01:
              ansible_host: 192.168.10.11
        db:
          hosts:
            db-01:
              ansible_host: 192.168.10.21

Группа all существует всегда, в неё автоматически попадает каждый хост, поэтому общие настройки удобнее всего прикреплять к ней. Проверить структуру после правки помогает команда ansible-inventory --graph с флагом -i: она печатает дерево групп и хостов, и потерянный отступ или лишний символ видны сразу. Выборка машин для прогона задаётся шаблоном: имя группы, звёздочка, объединение через двоеточие, пересечение через :& и исключение через :!. Шаблон web:&prod охватывает веб-узлы, входящие в рабочее окружение, и выручает, когда одна и та же группа встречается в разных инвентарях. Путь к инвентарю задают флагом -i при каждом запуске, и этот флаг принимает как отдельный файл, так и каталог со вложенными файлами.

Каталоги переменных групп и хостов и порядок наложения значений

Переменные живут рядом с инвентарём в двух каталогах: group_vars хранит значения групп, host_vars уточняет параметры отдельных машин. Имя файла внутри каталога совпадает с именем группы или хоста:

inventory/
  prod/
    hosts.ini
    group_vars/
      all.yml
      web.yml
    host_vars/
      web-01.yml

Файл all.yml применяется ко всем машинам, web.yml добавляет значения для группы web, а web-01.yml уточняет параметры одного хоста.

# group_vars/all.yml
ansible_user: admin
ansible_ssh_private_key_file: ~/.ssh/id_ed25519

# group_vars/web.yml
http_port: 8080
nginx_workers: 2

# host_vars/web-01.yml
nginx_workers: 4

Наложение идёт от общих значений к частным: сначала группа all, затем обычная группа, затем вложенная, в конце хост. Более специфичный слой переписывает совпавшие ключи, а не файл целиком, поэтому в host_vars достаточно держать только отличия: web-01 получил четыре рабочих процесса вместо двух, остальные веб-узлы остались при прежнем значении. Самый высокий приоритет у переменных из флагов -e командной строки: они переписывают и группы, и хосты, поэтому годятся для разовых прогонов. Посмотреть итоговую картину по машине выводит команда ansible-inventory --host web-01: она печатает уже слитые значения всех слоёв, и спор о том, откуда взялась цифра, решается за секунды. Каталоги переменных ищутся рядом с инвентарём и рядом со сценарием, поэтому лучше сразу выбрать одно место и не плодить копии, иначе одинаковые файлы однажды разойдутся в значениях.

Разделение окружений отдельными инвентарями при одном сценарии

Рабочее и тестовое окружения разводят по разным инвентарям, и один сценарий обслуживает оба. Наборы машин и значения переменных при этом различаются: тестовых узлов хватает двух-трёх, рабочих бывают десятки. Каталог каждого окружения хранит собственный hosts.ini и собственные group_vars, поэтому http_port спокойно равен 8080 в тесте и 80 в рабочем окружении без всяких условий внутри сценария. Выбор окружения сводится к пути инвентаря во флаге -i:

ansible-playbook -i inventory/test/hosts.ini site.yml
ansible-playbook -i inventory/prod/hosts.ini site.yml

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

Пьесы, задачи и идемпотентность модулей в сценарии

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

- name: Настройка веб-узлов
  hosts: web
  become: true
  tasks:
    - name: Установка nginx
      ansible.builtin.package:
        name: nginx
        state: present

    - name: Конфигурация nginx из шаблона
      ansible.builtin.template:
        src: templates/nginx.conf.j2
        dest: /etc/nginx/nginx.conf
        backup: true
      notify: перезапустить nginx

  handlers:
    - name: перезапустить nginx
      ansible.builtin.service:
        name: nginx
        state: restarted

Ключевое свойство модулей называется идемпотентностью: модуль сначала сверяет фактическое состояние с желаемым и меняет систему только при расхождении. Значение state: present не переустанавливает пакет при каждом запуске, а ставит его один раз, и повторный прогон по настроенной машине завершится без изменений, пометив задачи словом ok вместо changed. Поэтому сценарий запускают регулярно по расписанию, и лишний прогон безвреден. Перед задачами Ansible собирает факты о целевых системах, и факты работают как обычные переменные, а сбор отключается ключом gather_facts: false, когда они не нужны. Имена задач не украшение: по ним читается журнал прогона, и строка "Конфигурация nginx из шаблона" информативнее безымянного вызова модуля. Стандартный набор модулей закрывает типовые нужды: package ставит пакеты в разных семействах систем, service управляет службами, copy и template копируют файлы, user заводит учётные записи, file правит права и владельцев. Подсказку по параметрам модуля выводит команда ansible-doc.

Повышение прав через become и обработчики перезапуска служб

Большинство изменений требует прав администратора, и Ansible вынес это в отдельный переключатель become. На уровне пьесы достаточно строки become: true, при необходимости уточняют целевую учётную запись через become_user. Тот же переключатель работает и на уровне отдельной задачи, когда администратор нужен одной команде из десяти. В инвентаре переключатель задаётся переменной ansible_become для машин с нестандартной схемой доступа. В связке с задачами работают обработчики: задача меняет файл конфигурации и командой notify сообщает об изменении, а перезапуск службы происходит в конце пьесы и только если уведомление пришло от задачи, реально изменившей систему. Несколько задач могут уведомить один обработчик, и он всё равно выполнится один раз, а не трижды. Имена в notify и в секции handlers должны совпадать буква в букву, иначе уведомление уходит в пустоту. Если конфигурация не менялась, служба не дёргается вовсе, и соединения не рвутся. Когда перезапуск нужен немедленно, между задачами вставляют ansible.builtin.meta с параметром flush_handlers, и обработчики отрабатывают прямо в месте вызова. Такой порядок бережёт и время прогона, и спокойствие пользователей.

Шаблоны конфигураций с подстановкой переменных и секреты в хранилище

Модуль template берёт файл с расширением j2, подставляет вместо выражений в двойных фигурных скобках значения переменных и кладёт готовый результат на целевую машину. Значения приходят из инвентаря, из фактов о системе и из переменных, вычисленных предыдущими задачами, поэтому один шаблон обслуживает десятки машин. Шаблонизатор Jinja2 умеет циклы и условия, но для конфигурации веб-сервера хватает простой подстановки:

worker_processes {{ nginx_workers }};

server {
    listen {{ http_port }};
    server_name {{ inventory_hostname }};
}

Переменная inventory_hostname подставляет короткое имя машины из инвентаря, и каждая система получает собственный блок с собственной строкой server_name. Параметр validate прогоняет итоговый файл через проверку синтаксиса до записи на диск, например validate: "nginx -t -c %s", где %s заменяется на путь к временному файлу. Это бережёт от случая, когда служба отказывается стартовать из-за опечатки. Параметр backup сохраняет прежнюю версию рядом с новой, и откат сводится к копированию одного файла.

Секреты не хранят в открытом виде рядом с обычными переменными, для них есть зашифрованное хранилище Ansible Vault. Команда ansible-vault create group_vars/prod/vault.yml создаёт файл под паролем, правки идут через ansible-vault edit, и обратное шифрование происходит без ручных действий. Для одиночного значения удобна команда ansible-vault encrypt_string: она шифрует один секрет прямо в файле переменных, а остальной файл остаётся читаемым. При прогоне пароль спрашивают флагом --ask-vault-pass или передают файлом с паролем через --vault-password-file, права на который ограничены владельцем. Для сценария переменная из зашифрованного файла неотличима от обычной: она подставляется в шаблоны и участвует в наложении слоёв, но в отчётах выводится скрытым текстом. Задачи, работающие с паролями, помечают параметром no_log, чтобы значения не попадали в консольный журнал.

Проверки перед прогоном и одноразовые команды без сценария

Ansible позволяет провести репетицию без риска для машин. Синтаксис сценария проверяет --syntax-check, состав адресатов показывает --list-hosts, а связность проверяет модуль ping, который, несмотря на имя, не отправляет ICMP-пакеты, а выполняет тривиальный код на Python через SSH-соединение и годится как быстрая проверка ключей и учётной записи. Холостой прогон включается флагом --check: сценарий проходит все шаги, но изменения не записываются, а с --diff печатает, какие строки файлов изменились бы. Перед первым прогоном по новому окружению порядок проверок обычно такой:

  1. вывести список машин, которых коснётся сценарий, и сверить его с ожиданиями;
  2. запустить холостой прогон с показом различий и прочитать планируемые изменения построчно;
  3. проверить связь пинговым модулем и убедиться, что учётные записи и ключи работают на каждой машине;
  4. выполнить прогон с ограничением --limit на одном хосте группы и лишь после успеха повторить его на всей группе.

Для повторной отладки одного шага пригодны ключи --start-at-task и --tags: первый продолжает прогон с конкретной задачи, второй запускает только задачи с нужными метками. Одноразовые операции обходятся без сценария: утилита ansible запускает отдельный модуль по шаблону хостов прямо из командной строки.

ansible web -i inventory/prod/hosts.ini -m ping
ansible web -i inventory/prod/hosts.ini -m command -a "uptime" -b
ansible db-01 -i inventory/prod/hosts.ini -m apt -a "name=htop state=present" -b

Флаг -b добавляет повышение прав, -o сжимает вывод в одну строку на машину, --limit сужает выборку до части хостов. Так собирают версию ядра со всего парка, доустанавливают одну утилиту или перезапускают службу на конкретном узле, не трогая сценарий. Модули command и shell в холостом режиме пропускаются, потому что их результат предсказать заранее нельзя, и это честная плата за гибкость.

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