Первый playbook
Структура yaml-плейбука: hosts, become, tasks, handlers, notify, vars, when, loop, Jinja2-шаблоны и block/rescue/always. Запуск ansible-playbook.
Playbook: что это
Playbook — это yaml-файл, который описывает что и на каких хостах делать. Внутри — список плеев (plays). Каждый плей говорит: «возьми эти хосты, стань суперпользователем (если нужно) и выполни задачи по порядку».
Один playbook — обычно один файл, один плей. Когда стек растёт, плеев становится больше, но начнём с одного.
Полный рабочий пример
---
- name: configure web server
hosts: webservers
become: true
vars:
nginx_port: 80
app_root: /var/www/html
packages:
- nginx
- curl
- vim
tasks:
- name: install packages
apt:
name: "{{ packages }}"
state: present
update_cache: true
- name: create site directory
file:
path: "{{ app_root }}"
state: directory
owner: www-data
group: www-data
mode: "0755"
- name: deploy nginx config
template:
src: templates/nginx.conf.j2
dest: /etc/nginx/sites-available/app.conf
owner: root
group: root
mode: "0644"
notify: restart nginx
- name: enable config
file:
src: /etc/nginx/sites-available/app.conf
dest: /etc/nginx/sites-enabled/app.conf
state: link
notify: restart nginx
- name: start and enable nginx
service:
name: nginx
state: started
enabled: true
handlers:
- name: restart nginx
service:
name: nginx
state: restarted
Разберём каждую часть.
Анатомия плея
hosts: — к каким хостам применяется. Значение берётся из инвентаря.
Можно указать группу (webservers), конкретный хост (web-1), несколько
через запятую (web-1,web-2) или all.
become: true — выполнять задачи с правами суперпользователя (sudo).
Без этого модули, которые меняют системные файлы или устанавливают пакеты,
упадут с ошибкой прав доступа.
vars: — переменные плея. Можно использовать в задачах как {{ имя }}.
В примере packages — список, его можно передать прямо в apt: name: "{{ packages }}".
tasks: — список задач. Выполняются сверху вниз, по одной. Каждая задача
состоит из name: (человекочитаемое описание) и модуля с аргументами.
handlers: — особые задачи, которые выполняются по уведомлению.
Handlers и notify
Handler — задача, которая запускается, только если кто-то её уведомил (notify),
и только если уведомившая задача вернула changed.
Классический пример: перезапустить nginx, только если конфиг реально менялся.
tasks:
- name: deploy config
template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
notify: restart nginx # уведомление по имени handler'а
handlers:
- name: restart nginx
service:
name: nginx
state: restarted
Если template вернул ok (файл не изменился), handler не запустится.
Если несколько задач уведомили одного handler'а, он всё равно запустится
один раз, в конце плея. Это защита от лишних рестартов.
when: условие
when: позволяет выполнить задачу только при определённом условии.
- name: configure firewall (Debian only)
ufw:
rule: allow
port: "{{ nginx_port }}"
when: ansible_os_family == "Debian"
Ansible собирает факты о хостах (ansible_os_family, ansible_distribution,
ansible_architecture и десятки других) автоматически перед выполнением задач.
Условие может опираться и на результат предыдущей задачи. Тогда этот
результат сначала надо куда-то положить. Для этого есть register:
- name: check if reboot is required
ansible.builtin.stat:
path: /var/run/reboot-required
register: reboot_required
- name: reboot after kernel update
ansible.builtin.reboot:
when: reboot_required.stat.exists
Первая задача проверяет файл и складывает результат в переменную
reboot_required, вторая читает из неё поле stat.exists. Без первой
задачи вторая упадёт с ошибкой про undefined variable: сама по себе
reboot_required ниоткуда не появляется. Частая ошибка при копировании
готовых сниппетов — забрать when: и оставить register: в
оригинальной статье.
loop: цикл
loop: повторяет задачу для каждого элемента списка. Текущий элемент
доступен как {{ item }}.
- name: create users
user:
name: "{{ item }}"
state: present
shell: /bin/bash
loop:
- deploy
- monitoring
- backup
Для установки пакетов лучше передавать список прямо в apt, так быстрее
(один вызов apt вместо нескольких):
- name: install packages
apt:
name:
- nginx
- curl
- vim
state: present
Но loop: незаменим, когда нужно создать несколько файлов, пользователей или
ресурсов с разными параметрами.
Шаблоны Jinja2: что внутри .j2
Выше задача template разворачивала nginx.conf.j2, но мы не заглянули
внутрь. Шаблон — это обычный конфиг, в который вставлены подстановки и
управляющие конструкции Jinja2. На хост попадает уже отрендеренный файл,
без всякого Jinja.
# {{ ansible_managed }}
server {
listen {{ nginx_port }};
server_name {{ inventory_hostname }};
root {{ app_root }};
{% if enable_gzip | default(false) %}
gzip on;
{% endif %}
{% for path in deny_paths | default([]) %}
location {{ path }} {
deny all;
}
{% endfor %}
}
Что здесь работает:
{{ nginx_port }}— подстановка переменной (из vars, inventory, facts).{% if %}— условный блок: строка попадёт в файл, только если условие истинно.{% for %}— цикл: для каждого элемента списка генерируется кусок конфига.| default(...)— фильтр: если переменная не задана, берётся значение по умолчанию, без ошибки.{{ ansible_managed }}— служебная пометка «файл сгенерирован Ansible, руками не править».
Типичная ошибка — значение, начинающееся с {{, в YAML без кавычек:
парсер примет его за начало словаря и упадёт. Пиши name: "{{ var }}".
block / rescue / always: обработка ошибок
block группирует задачи, rescue ловит ошибку внутри блока, always
выполняется в любом случае. Это аналог try / except / finally, удобный
для деплоя с откатом.
- name: deploy with rollback
block:
- name: roll out new version
copy:
src: "app-{{ version }}.jar"
dest: /opt/app/app.jar
notify: restart app
- name: wait until service is healthy
uri:
url: http://localhost:8080/health
status_code: 200
register: health
retries: 5
delay: 3
until: health.status == 200
rescue:
- name: roll back to previous version
command: /opt/app/rollback.sh
always:
- name: remove lock file
file:
path: /tmp/deploy.lock
state: absent
Если любая задача в block упала, Ansible переходит в rescue (там
доступен результат через ansible_failed_result). always отрабатывает
всегда: и при успехе, и при провале. Поэтому в него кладут очистку.
Запуск playbook
Базовая команда:
ansible-playbook -i inventory.ini site.yml
Полезные флаги:
| Флаг | Что делает |
|---|---|
-i inventory.ini | указать файл инвентаря |
--check | dry-run: показать, что изменится, без применения |
--diff | показать построчный diff файлов и шаблонов |
--limit web-1 | применить только к этому хосту/группе |
-K | спросить sudo-пароль (become_password) |
--tags deploy | выполнить только задачи с тегом deploy |
--skip-tags slow | пропустить задачи с тегом slow |
-v / -vv / -vvv | увеличить детальность вывода |
Пример с несколькими флагами — посмотреть, что изменится только на web-1:
ansible-playbook -i inventory.ini site.yml --limit web-1 --check --diff
Идемпотентность в контексте playbook
Второй прогон полного playbook'а должен показать changed=0. Nginx уже
стоит, конфиг совпадает, сервис запущен: все задачи вернут ok. Handler
не вызовется ни разу.
Это и есть проверка качества playbook'а: запусти дважды, посмотри на
PLAY RECAP. Если changed > 0 на втором прогоне, значит, что-то
нарушает идемпотентность.
Что важно запомнить
Playbook = yaml-файл с плеями. Плей = hosts + tasks + (опционально) handlers, vars, become.
Handlers запускаются только при changed и только один раз в конце плея.
Это идеальный инструмент для рестартов сервисов при изменении конфига.
when: и loop: — основные инструменты управления потоком. Для пакетов
предпочитай список прямо в модуле, а не loop:.
ansible-playbook --check --diff перед реальным применением — хорошая привычка.
В следующем уроке — роли и структура их папок.
Самопроверка
Впишите команду / ключевую строку. Регистр и пробелы не важны.
Дрилл · вопросы как на собесе
Проверь себя без подсказок
Сформулируй ответ вслух или письменно, потом раскрой и честно отметь: знал или нет.
Как использовать `loop`, `when`, `block`/`rescue`/`always` для условной и повторяющейся логики?
ANS-012Что такое playbook и из каких частей он состоит (plays, tasks, handlers)?
ANS-002Playbook отрабатывает с разным результатом на разных хостах одной группы. Алгоритм отладки по шагам.
ANS-016Что такое handlers и зачем они нужны?
ANS-007Шаблоны Jinja2 в Ansible: фильтры, циклы, условия, типичные ошибки экранирования.
ANS-015Прошли материал до конца? Отметьте урок пройденным.
Прогресс сохранён в этом браузере
Почистишь историю или откроешь с телефона, и отметки пропадут. Оставь почту, они переедут в аккаунт. Пароль придумывать не нужно.
Готово. теперь в аккаунте: прогресс виден с любого устройства. Мой прогресс