Ansible каждый раз показывает changed: проверяем идемпотентность задачи
Первый запуск Ansible создал каталог и конфигурацию, второй опять показывает changed=3. Иногда это означает, что команды действительно каждый раз перезаписывают данные. Иногда действие только читает состояние, но модуль command не знает об этом. Для диагностики важно различить эти случаи: исправление отчёта и устранение повторного изменения решают разные задачи.
Идемпотентная задача приводит систему к одному состоянию независимо от числа одинаковых запусков. Например, каталог существует с заданными правами, а файл содержит заданную строку. Повторный запуск с теми же входами не должен делать лишних изменений. У команды общего назначения такого описания состояния нет: Ansible видит выполнение процесса и его результат.
Начнём с операции чтения
Если задача вызывает uname, проверяет версию программы или читает конфигурацию, её успешный запуск сам по себе не является изменением. Для такого случая changed_when: false честно уточняет результат. Ошибки процесса при этом по-прежнему должны завершать задачу неуспешно, если вы отдельно не изменяли failed_when.
С задачей записи поступать так нельзя. Если команда каждый раз добавляет одну строку в файл, changed_when: false только уберёт предупреждающий сигнал и подавит notify. Сам файл продолжит расти. Сначала задайте желаемое содержимое через copy, template, lineinfile или другой подходящий модуль; затем оцените отчёт.
Пример с двумя разными гарантиями
Сохраните следующий playbook как demo.yml и выполните на отдельной Linux-машине с установленным Ansible. Он работает локально внутри /tmp/latorn-idempotency-demo, не управляет сервисами и не требует become.
- name: Compare repeated execution
hosts: localhost
connection: local
gather_facts: false
tasks:
- name: Ensure demo directory exists
ansible.builtin.file:
path: /tmp/latorn-idempotency-demo
state: directory
mode: '0700'
- name: Read kernel name
ansible.builtin.command:
argv: [uname, -s]
changed_when: false
- name: Create a seed only once
ansible.builtin.command:
argv:
- /usr/bin/touch
- /tmp/latorn-idempotency-demo/seed
creates: /tmp/latorn-idempotency-demo/seed
- name: Manage the complete configuration
ansible.builtin.copy:
content: "mode=worker\n"
dest: /tmp/latorn-idempotency-demo/app.conf
mode: '0600'
ansible-playbook -i localhost, demo.yml ansible-playbook -i localhost, demo.yml
При отсутствии каталога первый запуск создаёт его, seed и app.conf. Чтение uname остаётся ok. При повторном запуске command с creates не выполняет touch, поскольку указанный путь уже существует; copy сравнивает желаемый файл с текущим. Ожидается отсутствие новых изменений. Проверяйте результат по отдельным задачам, поскольку начальное состояние машины может отличаться.
Creates проверяет существование, а не содержимое
У seed намеренно простая роль: он обозначает сам факт первоначального действия. Если кто-то испортит app.conf, задача copy восстановит строку. Если содержимое seed окажется неверным, creates этого не обнаружит. Поэтому маркер подходит для действительно одноразовой операции, но плохо заменяет управление конфигурацией.
Особенно опасен слишком ранний маркер при сложной установке: первая команда создала файл, следующая упала, а повторный запуск решает, что всё готово. Для такого процесса нужен достоверный признак завершения всей операции либо модуль, который умеет проверять состояние. Условие пропуска должно соответствовать обещанию задачи.
Параметр removes решает обратный случай: позволяет выполнять команду, пока определённый путь существует. Creates и removes позволяют command оценить необходимость выполнения в check mode. Произвольный command без таких признаков не получает полноценную модель предварительной проверки автоматически.
Когда собственная утилита сообщает результат
Иногда готового модуля нет, но утилита умеет различать «изменено» и «уже настроено» кодами возврата или структурированным выводом. Тогда register сохраняет результат, failed_when описывает настоящую ошибку, а changed_when — выполненное изменение. Используйте опубликованный контракт этой утилиты; случайное слово changed в сообщении для человека легко поменяется после обновления или локализации.
У списков условий changed_when действует логическое AND. Когда изменение возможно по любой из двух причин, запишите одно выражение с or. Прочитайте выражение как предложение и сравните его с реальными результатами процесса: иначе корректно выполненное изменение не отправит уведомление handler.
Финальная проверка полезнее простого требования changed=0. Сначала добейтесь спокойного второго запуска, затем намеренно измените управляемую строку app.conf и запустите playbook снова. Задача должна обнаружить отклонение, вернуть нужное содержимое и показать changed. Только эти два направления подтверждают, что автоматизация и сохраняет правильное состояние, и замечает реальное нарушение.
Ещё по теме
- Ansible --tags пропускает нужные задачи: include_tasks и import_tasks
- Ansible template: проверяем конфигурацию до замены рабочего файла
Продолжить изучение
Хотите собрать настройку серверов в повторяемый процесс? Inventory, роли, шаблоны и обновление сервисов входят в программу «Ansible: автоматизация инфраструктуры».
Продолжить на практике
- Ansible: автоматизация инфраструктуры ↗
Настраивайте серверы воспроизводимо: inventory, playbooks, роли, шаблоны и секреты. Соберите автоматизацию для нескольких окружений, проверьте повторный запуск и обновляйте сервисы управляемо.
