Первый playbook: структура YAML

Урок 7 - пишем первый playbook и разбираем его анатомию.

«Ad-hoc команды живут в истории терминала. Playbook живёт в git и переживёт тебя».

Playbook - это YAML-файл, описывающий желаемое состояние серверов в виде последовательности задач. Это сердце Ansible: всё серьёзное оформляется именно как playbook. Разберём минимальный пример.

---
- name: Настроить веб-серверы
  hosts: web
  become: true
  tasks:
    - name: Установить nginx
      ansible.builtin.apt:
        name: nginx
        state: present

    - name: Запустить и включить nginx
      ansible.builtin.service:
        name: nginx
        state: started
        enabled: true

Разберём по слоям. Файл - это список плеев (play); у нас один. Плей привязывает набор задач к группе хостов: hosts: web. become: true повышает права для всего плея. tasks - список задач. Каждая задача имеет name (человекочитаемое описание) и вызов модуля с параметрами.

Запуск

ansible-playbook -i inventory.ini site.yml

# В выводе по каждой задаче и хосту будет статус:
# ok       - всё уже как надо, ничего не делали
# changed  - состояние изменено
# failed   - ошибка

Как работает под капотом

Ansible читает YAML, строит список плеев. Для каждого плея раскрывает hosts в конкретные хосты и идёт по задачам сверху вниз. Важно: порядок выполнения - задача за задачей, но каждая задача прогоняется по всем хостам, прежде чем начнётся следующая. То есть сначала «установить nginx» на web1 и web2, потом «запустить nginx» на web1 и web2.

   PLAYBOOK
   +-------------------------------+
   |  play: hosts = web            |
   |    task1: install nginx       |
   |    task2: start nginx         |
   +-------------------------------+
            |
            v   task1 -> [web1, web2]   (по всем хостам)
            v   task2 -> [web1, web2]   (потом следующая)
# Модель исполнения: task за task, каждый по всем хостам
hosts = ["web1", "web2"]
tasks = ["install nginx", "start nginx"]

for task in tasks:
    print(f"TASK [{task}]")
    for host in hosts:
        print(f"    ok: [{host}]")
    print()

Попробуй сам ▶ Видишь, что вывод сгруппирован по задачам, а не по хостам? Именно так выглядит реальный лог ansible-playbook: блок TASK, под ним строки по каждому хосту.

Частые ошибки

  • Сломанный YAML-отступ. YAML критичен к пробелам (только пробелы, не табы!). Сдвиг на пробел - и playbook не парсится.
  • Забыть become. Установка пакетов без повышения прав упадёт.
  • Думать, что задачи идут по хостам, а не по задачам. Ansible проходит задачи по очереди, каждую - по всем хостам.

Best practices

  • Всегда давай задачам осмысленные name - они станут логом твоей автоматизации.
  • Используй полные имена модулей (ansible.builtin.apt) - это требование современных практик (FQCN).
  • Один плей - одна логическая роль группы хостов. Не сваливай всё в один гигантский плей.

В реальной работе

Опытные инженеры строят playbook'и по принципу «читается как инструкция для человека». Имена задач пишут так, чтобы лог выполнения сам объяснял, что происходит: не «task1», а «Установить и запустить nginx». Тогда вывод ansible-playbook становится понятным отчётом даже для того, кто не писал этот код. Ещё одна привычка - держать playbook'и тонкими: основная логика живёт в ролях, а сам site.yml лишь оркестрирует, какие роли применить к каким группам. Такой playbook на десять строк понятнее и надёжнее, чем монолит на триста, и его проще ревьюить в pull request'ах.

Итоги

Playbook - это YAML-описание задач, привязанных к группам хостов. Ansible исполняет задачи по очереди, прогоняя каждую по всем хостам. Статусы ok/changed/failed показывают результат. Дальше - подробнее про модули и идемпотентность.

Проверьте себя
1. В каком порядке Ansible исполняет playbook с двумя задачами на двух хостах?
AСначала все задачи на host1, потом все на host2
BКаждую задачу по очереди прогоняет по всем хостам, потом переходит к следующей задаче
CСлучайно
DВсе задачи параллельно на всех хостах сразу
2. Почему в YAML-playbook так важны отступы?
AДля красоты
BYAML определяет структуру через отступы пробелами; ошибка в отступе ломает парсинг
CОтступы не важны в YAML
DОни влияют только на скорость