Идемпотентность - главный принцип

Урок 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. Признак здоровья - чистый второй прогон. Дальше - обработчики и условия, расширяющие логику задач.

Проверьте себя
1. Что означает идемпотентность задачи в Ansible?
AЗадача выполняется максимально быстро
BПовторный запуск даёт тот же результат и не делает лишних изменений
CЗадача шифрует данные
DЗадача работает только один раз и блокируется
2. Как сделать сырую команду command идемпотентной?
AДобавить --become
BИспользовать параметр creates/removes или условие when, чтобы пропускать команду при уже достигнутом состоянии
CЗапустить её дважды
DКомандой это сделать нельзя никак