Идемпотентный playbook приводит сервер к нужному состоянию и при повторном запуске не меняет уже корректную систему. Постоянный changed затрудняет аудит, зря перезапускает сервисы и может скрывать реальный дрейф. Причина обычно находится в shell-командах, нестабильных шаблонах или неверной проверке результата.
Запустите playbook дважды на одинаковом тестовом хосте с diff и сохраните список задач, которые изменились во второй раз. Разбирайте их по одной, заменяя процедурные команды декларативными модулями или точным changed_when.
Что проверить в первую очередь
Начните с воспроизводимого сценария: зафиксируйте время сбоя, идентификатор объекта, версию приложения или конфигурации и последнее известное рабочее состояние. Не меняйте несколько параметров одновременно. Один контролируемый шаг должен подтверждать или исключать одну гипотезу, иначе временное исчезновение симптома легко принять за исправление. Перед работой с данными и настройками подготовьте резервную копию и понятный способ отката.
- Выполните два последовательных запуска без ручных изменений между ними.
- Включите diff только для безопасных файлов без секретов.
- Найдите handlers, которые запускаются из-за ложного changed.
- Проверьте generated timestamps, случайный порядок словарей и окончания строк в шаблонах.
Почему возникает проблема
Внешний симптом часто появляется не в том компоненте, где возникла первичная ошибка. Интерфейс может показывать неверное состояние из-за backend, очереди, кеша, прав доступа или внешнего API. Полезно проследить данные от источника до результата и найти первую точку расхождения. Это надежнее, чем исправлять последнее сообщение об ошибке или бесконечно перезапускать сервис.
- Shell или command всегда считается изменением без creates/removes/changed_when.
- Шаблон содержит текущее время, случайное значение или нестабильный порядок.
- lineinfile ищет строку по слишком широкому regexp и каждый раз добавляет новую.
- Пакет устанавливается через команду вместо package-модуля.
- Handler меняет тот же файл, который уведомляет его о запуске.
Пошаговая диагностика
Диагностику проводите на тестовой записи или отдельном окружении. В журналах скрывайте токены, пароли, персональные данные и содержимое документов. Для каждого шага сохраняйте измеримый результат: код ответа, версию записи, идентификатор события, состояние процесса, контрольную сумму или время выполнения. Сравнение одной и той же операции до и после изменения помогает отделить причину от совпадения.
- Сравните before/after для каждой changed-задачи второго запуска.
- Запустите только проблемный role с tags и check mode, учитывая ограничения check.
- Проверьте регистрируемый rc/stdout и условие changed_when.
- Посмотрите фактический файл байт-в-байт: пробелы, newline и порядок секций.
- Разделите изменение конфигурации и restart на отдельные задачи.
Декларативный модуль против процедурной команды
Модуль знает текущее и желаемое состояние и меняет только различие. Команда знает лишь действие, поэтому для нее приходится явно описывать условие выполнения и признак изменения.
- Используйте package, user, file, service, mount и другие профильные модули.
- Для command задавайте creates/removes, если результат представлен файлом.
- Для API используйте модуль или проверяйте объект до изменения.
- changed_when должен описывать реальный бизнес-результат, а failed_when — реальную ошибку.
- Генерируемые секреты создавайте один раз и храните в vault, а не пересоздавайте.
Как исправить проблему
Разбейте исправление на небольшие обратимые изменения. Сначала устраните подтвержденную причину, затем повторите исходный сценарий и проверьте соседние функции. Массовое обновление данных запускайте на ограниченной выборке с отчетом и только после сверки расширяйте на весь объем. Не отключайте авторизацию, валидацию, шифрование или проверку сертификатов ради быстрого исчезновения ошибки.
- Замените shell на декларативные модули там, где они поддерживают нужное состояние.
- Уберите текущее время и случайный порядок из шаблонов.
- Исправьте regexp и backrefs для точного изменения одной строки.
- Уведомляйте handler только при реальном изменении конфигурации.
- Добавьте отдельный тест второго запуска, который требует changed=0.
Безопасный порядок внедрения
- Сохраните затрагиваемые данные, конфигурацию и текущие журналы, заранее проверив реальный способ восстановления.
- Повторите проблему на тестовом объекте без реальных списаний, рассылок и необратимых изменений клиентских данных.
- Зафиксируйте изменение в системе контроля версий или журнале работ вместе с причиной и планом отката.
- Проведите тест на нормальном сценарии, ошибочном вводе, повторном запросе, параллельной операции и временной недоступности зависимости.
- После выпуска наблюдайте логи, метрики и полный пользовательский путь, а не только один успешный запрос.
Как проверить результат
Разовый успешный тест недостаточен. Повторите операцию, проверьте крайние значения, одновременные действия и восстановление после перезапуска или временного сбоя. Для важного сценария сохраните автоматический тест либо короткий регрессионный чек-лист. Итог должен подтверждаться не только интерфейсом, но и состоянием базы, очереди, внешнего сервиса и журналом действий.
- Второй запуск на неизмененном хосте завершается changed=0.
- Изменение одной переменной меняет только ожидаемые файлы и сервисы.
- Check mode не показывает бесконечные расхождения там, где он поддерживается.
- После сбоя посередине повторный запуск безопасно завершает настройку.
Типичные ошибки при исправлении
- Принудительно ставить changed_when: false, скрывая реальное изменение.
- Проверять идемпотентность только на давно настроенном сервере.
- Перезапускать все сервисы в конце playbook независимо от изменений.
- Хранить mutable latest-артефакты без версии и checksum.
Как предотвратить повторение
Профилактика строится вокруг явных контрактов, повторяемых релизов и наблюдаемости. Система должна не только работать сейчас, но и позволять быстро увидеть нарушение правила при следующем обновлении, росте нагрузки или сбое внешнего сервиса. Проверки полезно автоматизировать там, где ошибка уже привела к потерям времени, данных или заявок.
- Тестируйте роли в чистом окружении и повторным запуском в CI.
- Фиксируйте версии коллекций, пакетов и артефактов.
- Проверяйте ansible-lint и diff перед merge.
- Описывайте ожидаемый changed для задач, где он допустим при каждом запуске.
Что подготовить для технического разбора
- Описание ожидаемого и фактического поведения с точной последовательностью действий.
- Время проблемы, идентификатор тестового объекта и версии затронутых компонентов.
- Фрагменты журналов до и после ошибки без секретов и персональных данных.
- Перечень последних изменений и уже выполненных проверок.
- Безопасный доступ к тестовой среде либо способ воспроизвести сбой без влияния на клиентов.
Частые вопросы
Всегда ли второй запуск должен показывать changed=0?
Почти всегда. Исключения вроде ротации или обновления динамических данных должны быть осознанными, документированными и отделенными от обычной конфигурации.
Можно ли исправить все через changed_when?
Нет. Это меняет отчет, но не обязательно поведение команды. Сначала сделайте само действие безопасным при повторе.
Когда нужна помощь специалиста
Если Ansible каждый раз меняет серверы или перезапускает службы, я могу разобрать роли, устранить ложный changed, сделать команды безопасными при повторе и добавить проверку идемпотентности в CI.