Введение в Ansible: инвентари, плейбуки и идемпотентность¶
Главное ценностное предложение Ansible по сравнению с shell-скриптом — идемпотентность: повторный запуск того же плейбука против уже настроенного хоста должен сообщить «ничего не изменилось» во второй раз, а не упасть с ошибкой или задублировать работу. Эта статья — о том, что именно делает это правдой.
Как это устроено¶
Инвентарь: какие хосты вы администрируете¶
# inventory.ini
[webservers]
web1.example.com
web2.example.com ansible_host=10.0.0.12
[databases]
db1.example.com ansible_user=deploy
[production:children]
webservers
databases
Группы ([webservers]) можно вкладывать через :children. ansible_host/ansible_user — переменные соединения — они говорят Ansible, как реально достучаться до этого хоста, отдельно от его отображаемого имени в инвентаре.
-m ping — собственный модуль Ansible (не ICMP) — он подтверждает, что Python доступен и работает по SSH на цели, самая базовая проверка связности перед запуском чего-то реального.
Плейбуки: декларативные, не процедурные¶
# site.yml
- name: Configure web servers
hosts: webservers
become: true
tasks:
- name: Install nginx
ansible.builtin.package:
name: nginx
state: present
- name: Deploy site configuration
ansible.builtin.template:
src: templates/site.conf.j2
dest: /etc/nginx/conf.d/site.conf
mode: "0644"
notify: Reload nginx
- name: Ensure nginx is enabled and running
ansible.builtin.systemd:
name: nginx
enabled: true
state: started
handlers:
- name: Reload nginx
ansible.builtin.systemd:
name: nginx
state: reloaded
Это декларативно, а не shell-скрипт: state: present означает «убедиться, что пакет установлен» — если он уже установлен, ничего не происходит (отчитывается как ok, не changed); если нет — Ansible его устанавливает. Тот же плейбук, запущенный против уже полностью настроенного хоста, должен показать все задачи как ok с нулём changed — именно так проверяется реальная идемпотентность плейбука.
Хендлеры запускаются только при изменении, и только один раз¶
notify: Reload nginx срабатывает только если задача template реально изменила файл — и даже если десять разных задач в одном play уведомляют один и тот же хендлер, он всё равно запустится один раз, в конце play. Это избегает, например, десятикратного reload nginx только потому, что десять отдельных конфигов были сгенерированы в одном плейбуке.
Переменные и шаблоны¶
{# templates/site.conf.j2 #}
server {
listen {{ http_port | default(80) }};
server_name {{ inventory_hostname }};
}
{{ inventory_hostname }} — встроенная переменная — имя хоста, как оно указано в инвентаре. | default(80) даёт запасное значение, если http_port нигде не определена для этого хоста, избегая жёсткого падения на отсутствующей переменной.
Проверка, что изменилось бы, без реального изменения¶
--check («dry run») сообщает, что было бы изменено, не применяя это на самом деле; --diff показывает реальные различия содержимого для задач с файлами (template, copy, lineinfile). Это самая полезная привычка, которую стоит выработать перед запуском незнакомого или изменённого плейбука против продакшна — всегда сначала --check --diff.
Различия Ubuntu/Debian и RHEL-подобных систем¶
Сам Ansible не зависит от дистрибутива (это Python, работающий с управляющей машины, подключаясь по SSH — на целевой машине Ansible вообще не нужен, только Python). Практическая разница — в используемых модулях: ansible.builtin.apt против ansible.builtin.dnf/yum, либо независимый от дистрибутива модуль ansible.builtin.package (как в примере выше), который сам выбирает нужный бэкенд по определённому семейству ОС цели. Пример разворачивает файл в /etc/nginx/conf.d/, который пакеты nginx обеих семейств подключают по умолчанию; для каталога sites-available/ в стиле Debian дополнительно понадобилась бы символьная ссылка в sites-enabled/.
Практическое задание¶
- Напишите инвентарь с единственной группой
[test], содержащейlocalhost ansible_connection=local(работает локально, SSH не нужен — безопасно для практики). - Напишите плейбук, гарантирующий существование файла
/tmp/ansible-test.txtс конкретным содержимым, через модульcopyсcontent:. - Запустите его дважды подряд и убедитесь, что второй запуск отчитывается
changed=0— доказывая идемпотентность. - Измените содержимое файла вручную (
echo "tampered" > /tmp/ansible-test.txt), перезапустите плейбук и убедитесь, что он обнаруживает и исправляет расхождение (changed=1).
Как проверить, что всё сделано правильно¶
- Второй запуск шага 3: итоговая строка
PLAY RECAPвыводаansible-playbookдолжна показатьchanged=0,failed=0. - Шаг 4: recap должен показать
changed=1, аcat /tmp/ansible-test.txt— снова ваше исходное содержимое, не «tampered».
Типичные ошибки¶
- Написание плейбуков как последовательности задач
shell/commandвместо использования специализированных модулей (package,template,systemd,copy, ...). Сырые shell-команды по умолчанию не идемпотентны — задачаshell: useradd deployпадает при втором запуске, потому что пользователь уже существует, тогда как модульansible.builtin.userкорректно обрабатывает «убедиться, что этот пользователь существует» независимо от текущего состояния. - Никогда не запускать
--check --diffперед применением незнакомого плейбука к продакшн- хостам. - Жёстко прописанные значения, которые должны быть переменными (хосты, порты, пути), делая один и тот же плейбук непригодным для второго, чуть отличающегося окружения без правки самого плейбука.
⚠ Опасные команды¶
- Любой плейбук с
ansible.builtin.file: state=absentилиcommand: rm -rf <path>против пути, выведенного из переменной, запущенный на большом инвентаре без--checkсначала — одна неверная переменная разрешается в одно и то же разрушительное действие одновременно на каждом целевом хосте, что и есть одновременно сила и опасность управления конфигурацией в масштабе. ansible all -i inventory.ini -m <module>(группаall) запускается против каждого хоста в инвентаре — опечатанный или слишком широкий файл инвентаря превращает тестовую команду в действие на весь парк.
Связанные статьи¶
Контрольные вопросы¶
- Почему задача
shell: useradd deployне идемпотентна, а модульansible.builtin.userдостигает той же цели безопасно? - Что на самом деле делает
--check --diff, и почему это должно стать привычкой перед запуском любого незнакомого плейбука? - Почему хендлеры запускаются только один раз за play, даже если уведомлены несколькими задачами?
- В чём практический риск запуска плейбука против группы инвентаря
allбез предварительного сужения до меньшей тестовой группы?