Идемпотентность - главный принцип
Урок 9 - центральный принцип Ansible: один запуск или сто - результат одинаков.
«Идемпотентная операция - это та, которую можно повторять без страха. Прогнать playbook второй раз так же безопасно, как первый».
Идемпотентность - свойство операции давать один и тот же результат независимо от числа повторов. Применил один раз - система пришла в нужное состояние. Применил ещё десять раз - ничего не изменилось. Это фундамент Ansible: ты не боишься запускать playbook повторно, потому что он не наделает лишнего.
Почему это так важно
В реальной эксплуатации playbook'и гоняют постоянно: добавился сервер - прогнали по всем, поправили конфиг - прогнали снова. Если бы каждый запуск что-то ломал или дублировал (создавал пользователя второй раз, добавлял строку в файл повторно), автоматизация была бы опасной. Идемпотентность делает её предсказуемой. По оценкам команд, идемпотентные практики дают заметное сокращение времени и числа инцидентов при выкатах.
ЖЕЛАЕМОЕ: nginx установлен
Запуск 1: проверка -> не установлен -> УСТАНОВИТЬ (changed)
Запуск 2: проверка -> установлен -> ничего (ok)
Запуск 3: проверка -> установлен -> ничего (ok)
^^^^^^^^ ^^^^^^^^^^^
check-then-act действие только если надо
Как работает под капотом
Идемпотентность встроена в модули по схеме check-then-act: сначала прочитать текущее состояние, потом действовать только при расхождении с желаемым. Модуль apt смотрит, установлен ли пакет; template сравнивает содержимое; user проверяет, есть ли пользователь. Если всё уже как надо - статус ok, никаких изменений.
Смоделируем check-then-act как универсальную идемпотентную операцию.
# Универсальная идемпотентная операция: check -> act если надо
def ensure(current_state, desired_state, apply_fn):
if current_state == desired_state:
return {"changed": False, "state": current_state}
apply_fn(desired_state) # выполняем действие
return {"changed": True, "state": desired_state}
applied = []
r1 = ensure("absent", "present", applied.append) # надо установить
r2 = ensure("present", "present", applied.append) # уже стоит
print("Запуск 1:", r1)
print("Запуск 2:", r2)
print("Действий выполнено:", applied) # ['present'] - только один раз
Попробуй сам ▶ Реальное действие (apply_fn) сработало ровно один раз, при первом расхождении. Это и есть суть идемпотентности: повтор не приводит к повторному действию.
Делаем shell идемпотентным
Иногда без command/shell не обойтись. Они не идемпотентны сами по себе - выполняют команду всегда. Спасают параметры creates, removes и условие when:
- name: Распаковать архив только если его ещё нет
ansible.builtin.command: tar xzf /tmp/app.tar.gz -C /opt/app
args:
creates: /opt/app/app.bin # если файл есть - команда пропускается
Параметр creates говорит: «если этот файл уже существует, не выполняй команду». Так даже сырой command становится безопасным для повторов.
Частые ошибки
- shell/command без creates/when. Такая задача всегда
changedи может навредить при повторе. - Дописывать в файл через shell echo >>. Повтор продублирует строку. Используй
lineinfileилиblockinfile. - Путать changed с ошибкой.
changed- это нормально, значит состояние привели в норму. Тревожный статус -failed.
Best practices
- Стремись к тому, чтобы второй прогон playbook давал все
okи нольchanged- это признак чистой идемпотентности. - Любой
command/shellснабжайcreates,removesилиwhen. - Проверяй идемпотентность: прогони playbook дважды подряд и убедись, что второй раз - всё
ok.
В реальной работе
Идемпотентность - это не академическая красота, а основа доверия к автоматизации. Когда ты уверен, что повторный прогон безопасен, ты гоняешь playbook'и спокойно: после каждого изменения, при добавлении сервера, по расписанию в CI. Это и есть рабочий цикл управления конфигурацией - не «настроил и забыл», а «постоянно подтверждаю эталон». Неидемпотентная задача рушит этот цикл: один раз забыл про creates у распаковки архива - и каждый прогон заново тратит время, нагружает диск, а то и портит данные. Поэтому опытные команды относятся к любому changed на повторном прогоне как к багу и расследуют его, а не списывают на «так и должно быть».
Итоги
Идемпотентность - способность операции безопасно повторяться. Модули реализуют её через check-then-act, а сырые команды делают идемпотентными через creates/when. Признак здоровья - чистый второй прогон. Дальше - обработчики и условия, расширяющие логику задач.