Идемпотентность и почему bashible плохо
changed vs ok, почему command/shell не идемпотентны, creates/removes, changed_when, --check и --diff.
Что такое идемпотентность
Идемпотентность — свойство операции, при котором повторное выполнение с теми же параметрами не меняет результат.
Запусти playbook один раз или пять, состояние системы на выходе одинаковое. Это не магия. Это философия Ansible: каждый модуль проверяет текущее состояние и делает что-то, только если оно отличается от желаемого.
Статусы задач: ok, changed, failed
После каждой задачи Ansible выводит один из трёх статусов.
- ok — состояние уже такое, как нужно. Ansible ничего не делал.
- changed — привёл к нужному состоянию. Было иначе, теперь исправлено.
- failed — что-то пошло не так. Прогон остановился (если не настроено иначе).
В конце каждого прогона выводится PLAY RECAP:
PLAY RECAP *****
web-1 : ok=5 changed=2 unreachable=0 failed=0
Хорошо написанный playbook на втором прогоне показывает changed=0.
Всё ok: система уже в нужном состоянии, менять нечего.
Нормальные модули идемпотентны по умолчанию
Модуль apt проверяет: пакет уже установлен? Тогда ok. Нет? Ставит и
возвращает changed.
- name: install nginx
apt:
name: nginx
state: present
Первый прогон возвращает changed: nginx не стоял, поставил.
На втором уже ok, пакет на месте и делать нечего.
То же самое с другими модулями:
copy— сравнивает хеш файла. Если файл одинаковый,ok.file state=directory— создаёт папку, если нет. Если есть —ok.template— рендерит шаблон и сравнивает с файлом на хосте. Если совпало —ok.service state=started— проверяет, запущен ли сервис.
Башибл: антипаттерн
«Башибл» — это когда вместо нормальных модулей весь playbook написан через
command и shell.
# ПЛОХО - антипаттерн
- name: install nginx
shell: apt-get install -y nginx
- name: create directory
command: mkdir -p /var/www/html
- name: copy config
shell: cp /tmp/nginx.conf /etc/nginx/nginx.conf
Модули command и shell не идемпотентны. Ansible не знает, что делает
твоя команда и какой у неё эффект. Поэтому он:
- Выполняет её при каждом прогоне, без исключений (если не задан
creates/removes). - По умолчанию ставит статус
changed, независимо от того, менялось ли что-то (это поведение можно переопределить черезchanged_when:).
Что теряешь:
- Нет проверки состояния.
apt-get install -y nginxвыполнится даже если nginx уже стоит. changed=0на втором прогоне ты не увидишь никогда.- Побочные эффекты на каждый прогон.
cpперезапишет конфиг, даже если он не менялся. Тогда nginx получит ненужныйchanged, и если к нему привязан notify, он перезапустится без причины.
Когда command/shell всё же нужны
Иногда специализированного модуля нет. Но тогда сделай command/shell
идемпотентным вручную.
creates= — не выполнять, если файл/путь уже существует:
- name: init database
command: /opt/app/bin/init-db
args:
creates: /var/lib/app/.db_initialized
Если /var/lib/app/.db_initialized уже есть, задача пропускается (ok).
Сама команда должна создать этот файл при первом выполнении.
removes= — выполнять, только если путь существует:
- name: clean up temp archive
command: rm /tmp/setup.tar.gz
args:
removes: /tmp/setup.tar.gz
changed_when: — явно сказать, когда считать задачу changed:
- name: check app version
command: /opt/app/bin/app --version
register: app_version
changed_when: false # только читаем - никогда не changed
- name: update cache
shell: update-cache.sh
register: cache_result
changed_when: "'updated' in cache_result.stdout"
when: — вообще не выполнять, если условие не совпало:
- name: configure firewall
command: ufw allow 80
when: ansible_os_family == "Debian"
Главный совет: прежде чем писать shell, поищи модуль.
С файлами работают copy, template, file. Пакеты ставят apt, yum и
dnf, сервисами управляют service и systemd. Для git есть git, для
docker — community.docker.*.
Dry-run: --check и --diff
--check запускает playbook без реальных изменений. Ansible показывает, что
бы произошло, но ничего не трогает.
ansible-playbook -i inventory.ini site.yml --check
--diff добавляет построчный diff для файлов и шаблонов: видно, что именно
изменилось бы в конфиге.
ansible-playbook -i inventory.ini site.yml --check --diff
Нормальные модули уважают --check. Модули command и shell в check-mode
по умолчанию пропускаются (skipped). Ansible не может предсказать,
что сделает произвольная команда. Исключение: если у задачи указан creates:
или removes:, в check-режиме модуль проверит файл и вернёт корректный
changed. Это ещё одна причина не писать башибл: в dry-run ты не видишь, что
будет делать половина playbook'а.
На втором прогоне apt понимает, что nginx уже стоит, и сообщает ok.
shell выполняется снова и снова, потому что Ansible не умеет заглянуть
внутрь команды.
Что важно запомнить
Идемпотентность — главный принцип Ansible. Хороший playbook на втором прогоне
ничего не меняет: changed=0.
Нормальные модули проверяют состояние сами. command/shell — нет: всегда
changed, выполняются каждый прогон. Если без shell не обойтись, добавляй
creates=, removes= или changed_when:.
--check --diff — безопасный dry-run. Но command/shell в check-mode
пропускаются, и это ещё причина использовать специализированные модули.
В следующем уроке соберём первый playbook.
Дрилл · вопросы как на собесе
Проверь себя без подсказок
Сформулируй ответ вслух или письменно, потом раскрой и честно отметь: знал или нет.
Идемпотентность Ansible — это правда или маркетинг? Назови три ситуации, когда модуль на самом деле не идемпотентен.
ANS-018Как обеспечить идемпотентность собственных задач, основанных на `command`/`shell` (`creates`, `removes`, `changed_when`, `failed_when`)?
ANS-027Что такое идемпотентность простыми словами, с примером модуля?
ANS-008Прошли материал до конца? Отметьте урок пройденным.
Прогресс сохранён в этом браузере
Почистишь историю или откроешь с телефона, и отметки пропадут. Оставь почту, они переедут в аккаунт. Пароль придумывать не нужно.
Готово. теперь в аккаунте: прогресс виден с любого устройства. Мой прогресс