13 KiB
Вопросы: Ansible
Опираются на кейс: CASE.md.
Что такое идемпотентность и почему это важно в Ansible?
Идемпотентность — свойство операции давать один и тот же результат при повторном запуске, не ломая и не дублируя изменения. В Ansible это значит, что повторный прогон плейбука на уже настроенном хосте не должен ничего менять (задачи должны показывать ok, а не changed). Я это проверял на своей роли nginx_deploy через molecule test — там есть отдельный шаг idempotence, который прогоняет converge второй раз и падает, если что-то опять помечено как changed.
Чем роль (role) отличается от плейбука (playbook)?
Плейбук — это конкретный сценарий: какие хосты, какие роли/задачи применить и в каком порядке. Роль — переиспользуемый, самодостаточный блок автоматизации со своей структурой папок (tasks, handlers, templates, defaults, vars), который можно подключать в разные плейбуки. У меня playbook.yml просто вызывает роль nginx_deploy — так удобнее переиспользовать её в других плейбуках без копирования кода.
Зачем нужны handlers и чем они отличаются от обычных задач?
Handler — задача, которая выполняется только по уведомлению (notify) от другой задачи, и только один раз в конце плейбука, даже если её нотифицировали несколько раз. Типичный пример — перезагрузка сервиса только если поменялся конфиг: в моей роли задача template нотифицирует хендлер reload nginx, и nginx перезагружается только тогда, когда конфиг реально изменился, а не при каждом прогоне.
Что такое defaults/vars и в чём разница между ними по приоритету?
defaults/main.yml — переменные с самым низким приоритетом, задуманы как значения по умолчанию, которые легко переопределить снаружи роли (в inventory, playbook, group_vars). vars/main.yml — переменные роли с более высоким приоритетом, обычно используются для внутренних констант роли, которые не предполагается менять снаружи. В своей роли я вынес nginx_worker_connections и nginx_server_name в defaults именно потому, что это то, что вызывающий плейбук может захотеть переопределить.
Зачем нужен Molecule, если можно просто запустить playbook на тестовом сервере?
Molecule автоматизирует весь цикл проверки роли: поднимает изолированное окружение (у меня — Docker-контейнер), прогоняет роль, проверяет идемпотентность, гоняет verify-тесты и уничтожает окружение — и всё это одной командой, без ручного создания и удаления тестовых серверов. Это то, что можно встроить в CI (в моей практике — TeamCity), чтобы роль проверялась автоматически на каждый коммит до попадания в прод.
Чем отличается модуль command/shell от специализированных модулей вроде service, package, template?
Специализированные модули декларативны и идемпотентны из коробки — они сами проверяют текущее состояние и меняют его только при необходимости (например, service не будет перезапускать уже запущенный сервис). command/shell выполняют произвольную команду безусловно и по умолчанию всегда репортят changed, что ломает идемпотентность, если не добавлять вручную creates/changed_when. В своей роли я использовал package, template, service именно чтобы не терять идемпотентность.
Как Ansible понимает, какие хосты — цель для выполнения?
Через inventory — статический файл (INI/YAML со списком хостов и групп) или динамический inventory-скрипт/плагин, который генерирует список из внешнего источника (облако, CMDB и т.п.). В playbook секция hosts: указывает, на какую группу/хост из inventory применять роль. В лабе у меня Molecule сам генерирует временный inventory на Docker-инстанс, в проде — обычно статический или динамический inventory отдела.
Как безопасно хранить секреты (пароли, токены) в Ansible?
Через ansible-vault — шифрование файлов с чувствительными переменными, которые потом расшифровываются на лету при прогоне плейбука с паролем/ключом vault. Секреты не должны лежать в открытом виде в репозитории с ролями.
Что произойдёт, если задача в роли завершится с ошибкой на середине выполнения?
По умолчанию Ansible останавливает выполнение плейбука на этом хосте (fail fast) — дальнейшие задачи для этого хоста не выполняются, но выполнение на других хостах продолжается независимо (если запуск на несколько хостов). Это поведение можно переопределить через ignore_errors, block/rescue для обработки ошибок или any_errors_fatal для остановки всего прогона.
Как вы тестировали конкретно свою роль и что бы произошло, если бы шаблон был некорректным?
Я специально ломал nginx.conf.j2 (опечатка в директиве) и запускал molecule test — прогон падал на этапе converge, потому что Ansible применяет конфиг, а nginx не стартует/не резолвит синтаксис, что видно в выводе service модуля. Это и есть основная ценность прогона перед CI — ошибка находится сразу, а не после деплоя на реальный сервер.
Где в Ansible живут переменные — в ролях, плейбуках или инвентарях, и зачем это разделение?
Переменные могут жить на всех трёх уровнях, и у каждого — своя роль. В роли (defaults/main.yml, vars/main.yml) — значения, специфичные для самой роли (см. вопрос про defaults/vars выше). В плейбуке (vars: секция) — значения, специфичные для конкретного сценария применения роли. В inventory (group_vars/, host_vars/) — значения, специфичные для конкретной группы хостов или отдельного хоста (например, разный nginx_server_name для разных окружений). Разделение нужно, чтобы одна и та же роль оставалась переиспользуемой: логика роли не меняется, а конкретные значения приходят снаружи в зависимости от того, где и для чего роль применяется.
Как переопределить переменную роли?
Проще всего — передать её на более высоком приоритете, чем defaults/main.yml (самый низкий приоритет намеренно, чтобы легко переопределять): через group_vars/host_vars в inventory, через vars: в самом плейбуке при вызове роли, или через -e в командной строке (ansible-playbook playbook.yml -e "nginx_worker_connections=2048" — -e имеет наивысший приоритет из всех источников). В своей роли nginx_deploy я именно поэтому вынес nginx_worker_connections в defaults, а не в vars — чтобы вызывающий плейбук/inventory мог его свободно переопределить без правки самой роли.
Могут ли роль и плейбук существовать по отдельности, и что будет, если убрать один из них?
Плейбук без роли — вполне рабочий сценарий: можно писать задачи прямо в плейбуке (tasks: напрямую), просто без переиспользуемой структуры роли — так делают для одноразовых/простых сценариев. Роль без плейбука сама по себе не выполнится — роль не имеет собственного понятия «на каких хостах запускаться», это просто набор задач/шаблонов/дефолтов; ей обязательно нужен плейбук (или ansible-console/adhoc-вызов через include_role из другого плейбука), который укажет hosts: и подключит роль. То есть роль — переиспользуемый строительный блок, плейбук — то, что решает, где и когда этот блок применить.
Разбор команды: ansible -m shell -i inventory.yml {{ansible_hostname}} — что делает эта команда с ключами поэтапно?
По шагам: ansible — adhoc-режим (разовая команда без плейбука, в отличие от ansible-playbook); -i inventory.yml — указывает, какой inventory-файл использовать для определения хостов и их переменных; -m shell — указывает модуль shell (выполнить произвольную команду через шелл на целевом хосте, в отличие от специализированных идемпотентных модулей — см. вопрос про command/shell выше); {{ ansible_hostname }} в этом месте выглядит некорректно синтаксически как есть — Jinja2-конструкции {{ }} предназначены для использования внутри YAML/шаблонов и аргументов модулей, а не как позиционный аргумент-паттерн хостов в CLI-вызове ansible (туда обычно подставляется паттерн хостов из inventory, например all или имя группы). На такой вопрос честный ответ — указать на это несоответствие и уточнить у интервьюера, что именно должно было стоять на этом месте, а не пытаться выдумать интерпретацию.