SSD Nodes Learn Hosting plans →
Руководства Matt ConnorАвтор: Matt Connor · Обновлено 2026-08-21

Как работают Ansible --check и --diff для dry run

Узнайте, как использовать флаги --check и --diff для безопасного тестирования playbook. Разбираем, почему модули без поддержки check mode могут давать ложные результаты.

Что делает режим проверки Ansible

Режим проверки (check mode) в Ansible — это пробный запуск: ansible-playbook --check подключается к каждому хосту в сценарии, запрашивает у каждого модуля, соответствует ли текущее состояние целевому, и сообщает о возможных изменениях, не внося их. Добавьте --diff, чтобы увидеть содержимое файлов до и после внесения изменений. Вместе эти параметры отвечают на вопрос, который стоит задать перед любым реальным запуском: что именно изменится на этих серверах?

Режим проверки не является симуляцией вашего playbook. Модели сервера не существует. Каждый модуль просто получает запрос на чтение вместо записи. Модуль, способный работать в режиме только для чтения, сообщает changed и продолжает работу. Модуль, который не поддерживает такой запрос, ничего не делает и ничего не сообщает. В документации Ansible это сформулировано одной фразой: «Модули, не поддерживающие режим проверки, ничего не сообщают и ничего не делают». Этот пробел — причина, по которой пробный запуск может дать неверный ответ, поэтому большая часть данного руководства посвящена именно этому ограничению.

Запуск пробного прогона: --check и --diff

ansible-playbook -i inventory.ini site.yml --check --diff --limit web1

-C и -D — это сокращенные формы двух флагов. Использование --limit оправдано. Diff для одного хоста легко прочитать. Diff для двадцати хостов заставит вас долго прокручивать экран.

Весь отчет строится на четырех ключевых словах.

  • ok: [web1] означает, что модуль проверил состояние и оно уже соответствует целевому. Никаких изменений не требуется.
  • changed: [web1] означает, что модуль внес бы изменения. При использовании --diff строки выше показывают, какие именно.
  • skipping: [web1] означает, что задача не была выполнена. Либо условие when оказалось ложным, либо модуль не поддерживает работу в режиме проверки.
  • fatal: [web1] означает, что задача завершилась с ошибкой во время проверки. Прочитайте сообщение об ошибке, прежде чем делать вывод о неисправности playbook.

--diff выводит унифицированный diff для файловых модулей, где удаленные строки помечены -, а добавленные — +. Заголовок блока начинается с символов --- before и +++ after и содержит путь к целевому файлу. Модули, которые не работают с файлами, выводят свои данные «до» и «после», поэтому ansible.builtin.user отображает атрибуты, которые будут изменены, а не содержимое файла.

Включите diff на постоянной основе в ansible.cfg, чтобы не забывать этот флаг:

[diff]
always = true
context = 5

Перед использованием режима проверки стоит выполнить две более простые проверки. ansible-playbook site.yml --syntax-check анализирует YAML и структуру play без обращения к хостам. ansible-playbook site.yml --list-tasks выводит список задач, которые будут выполнены; это помогает понять, что роль, которую вы считали помеченной тегом, на самом деле таковой не является. Обе команды не устанавливают соединение, поэтому выполняются мгновенно.

Сам режим проверки устанавливает соединение. Он открывает SSH-сессию к каждому хосту в паттерне и собирает факты, поэтому недоступный хост приведет к ошибке при пробном прогоне. Это само по себе полезный сигнал, и именно поэтому важно определиться с тем, как playbook должен реагировать на недоступные хосты, прежде чем добавлять пробный прогон в CI.

Почему режим проверки завершается ошибкой на новом сервере

Этот плейбук корректен. Запустите его с флагом --check на сервере, где еще нет Nginx, и большая часть задач завершится с ошибкой.

- name: Install nginx
  ansible.builtin.apt:
    name: nginx
    state: present

- name: Write the site config
  ansible.builtin.template:
    src: site.conf.j2
    dest: /etc/nginx/conf.d/site.conf

- name: Start and enable nginx
  ansible.builtin.service:
    name: nginx
    state: started
    enabled: true

Задача apt сообщает о changed, и это верно: пакет отсутствует, поэтому реальный запуск привел бы к его установке. Режим проверки не выполнил установку. Задача template затем завершается ошибкой, так как /etc/nginx/conf.d/ не существует на этом хосте, и ничто его не создало. Задача service также завершается ошибкой, поскольку нет юнита nginx, состояние которого можно было бы запросить. Ни один из этих сбоев не является ошибкой в плейбуке. Пробный запуск не получил необходимого состояния, что и подразумевает документация, когда предупреждает, что режим проверки не может выдать полезный результат для задачи, чей вход зависит от изменений, внесенных предыдущей задачей.

Таким образом, честная формулировка правила: режим проверки точен для хоста, который уже был приведен к целевому состоянию плейбуком, и выдает много шума при работе с новым хостом. Запуск с --check, при котором каждая задача сообщает о ok, является реальным показателем того, что хост приведен к целевому состоянию, так как это означает, что никаких изменений не потребуется. На совершенно новом хосте --check в основном говорит вам лишь о том, что хост новый. Когда вы пишете свой первый Ansible плейбук для VPS, ожидайте, что первый пробный запуск будет выглядеть как сплошная стена красного текста, и оценивайте плейбук по результатам второго запуска.

Почему задачи command и shell пропускаются в режиме проверки (check mode)

ansible.builtin.command и ansible.builtin.shell не могут знать, что именно делает ваша команда. Не существует способа выполнить произвольный бинарный файл в режиме «только чтение», поэтому в режиме проверки модуль отказывается его запускать. Результат задачи содержит skipped: true и сообщение Command would have run if not in check mode, а в выводе отображается skipping: [web1].

Документация модуля называет поддержку режима проверки «частичной», а предлагаемый обходной путь — это creates и removes. Укажите в задаче путь creates, и режим проверки сможет хотя бы выполнить проверку файла:

- name: Extract the release bundle
  ansible.builtin.command: /usr/bin/tar xf /tmp/app.tar.gz -C /opt/app
  args:
    creates: /opt/app/bin/app

Если /opt/app/bin/app уже существует, режим проверки сообщает Would not run command since '/opt/app/bin/app' exists, что является корректным ответом. Если путь отсутствует, вы получаете Command would have run if not in check mode, что также является корректным ответом. Без creates эта задача останется «пустым местом» в вашем пробном запуске.

Последствия этого хуже, чем просто пустая строка в выводе. Пропущенная задача всё равно регистрирует результат, но этот результат имеет статус пропуска и не содержит ключа stdout. В результате условие следующей задачи при оценке вызывает ошибку, близкую к 'dict object' has no attribute 'stdout'. Ваш playbook работает при реальном запуске, но ломается в режиме проверки, что является самым запутанным сбоем в этой функциональности.

check_mode: false и единственное место, где этот параметр уместен

check_mode: false в задаче означает «выполнить по-настоящему, даже в режиме --check». Это решение проблемы пропуска команд, и оно безопасно только для задач, которые выполняют чтение.

- name: Read the installed app version
  ansible.builtin.command: /usr/local/bin/app --version
  register: app_version
  check_mode: false
  changed_when: false

Эта задача корректно работает в обоих режимах. Она считывает версию и не вносит изменений, changed_when: false предотвращает ложные отчеты об изменениях, а check_mode: false гарантирует, что app_version.stdout будет существовать во время пробного запуска, поэтому условия, зависящие от этой переменной, будут вычислены верно.

Понимайте назначение этого ключевого слова буквально, прежде чем использовать его где-либо еще. Задача с check_mode: false вносит изменения на серверы во время ansible-playbook --check. Если добавить его в задачу apt или template, чтобы «сделать вывод чище», ваш пробный запуск перестанет быть таковым. Если задачу записи нельзя сделать безопасной, используйте вместо этого проверку условий:

- name: Apply the database migration
  ansible.builtin.command: /usr/local/bin/app migrate --apply
  when: not ansible_check_mode

ansible_check_mode — это специальная переменная, которую Ansible устанавливает в true во время выполнения в режиме проверки. Существует и обратный параметр. check_mode: true принудительно переводит задачу в режим проверки всегда, даже во время реального запуска. Это превращает задачу в инструмент поиска отклонений: сохраните результат, и если changed сообщает об изменениях, значит, состояние хоста больше не соответствует заданному в задаче.

Почему задача сообщает об изменениях при каждом запуске

Запустите playbook дважды подряд без каких-либо изменений между запусками. При втором запуске каждая задача должна сообщать ok. Если какая-либо задача продолжает сообщать changed, это означает одно из двух: модуль не может определить текущее состояние объекта или входные данные нестабильны. Обе проблемы решаемы, и ни одна из них не является «шумом», который нужно просто игнорировать.

  • command и shell без creates, removes или changed_when сообщают changed при каждом запуске, так как у модуля нет способа узнать, произошло ли что-то. Добавьте creates или настройте changed_when для проверки строки в выводе.
  • ansible.builtin.file с state: touch сообщает changed при каждом запуске по своей архитектуре, так как обращение к файлу обновляет его временные метки. Используйте state: file, если вам нужно было только установить владельца или права доступа.
  • template, чей результирующий вывод постоянно меняется, перезаписывает файл при каждом запуске. Временная метка из ansible_date_time, вызов now() или пароль, генерируемый заново, создают разные байты, поэтому модуль корректно сообщает об изменении. Уберите динамическое значение из шаблона.
  • ansible.builtin.user с password: "{{ pw | password_hash('sha512') }}" меняется при каждом запуске, так как password_hash выбирает случайную соль при каждом вызове, поэтому итоговый хеш никогда не совпадает с тем, что уже находится в /etc/shadow. Передавайте явную соль, основанную на стабильных данных.
  • state: latest в модуле управления пакетами сообщает changed всякий раз, когда доступно обновление. Это честное поведение. Именно поэтому state: latest приводит к результату, который невозможно предсказать. Используйте state: present и обновляйте пакеты целенаправленно.
  • ansible.builtin.unarchive, указывающий на URL без creates, будет повторно скачивать и распаковывать данные. Укажите локальный путь creates.

--diff — самый быстрый способ разобраться в причинах. Если задача сообщает changed, а diff показывает различающиеся байты, значит, ваши входные данные нестабильны. Если она сообщает changed, а diff пуст, значит, модуль не может отобразить, что именно изменилось; обычно это относится к задачам command или операциям, затрагивающим только метаданные, например, временные метки.

Не используйте changed_when: false, чтобы скрыть «шумную» задачу. Это подавляет отчет, поэтому событие notify никогда не срабатывает, и обработчик (handler), отвечающий за перезапуск службы, не выполняется. Вместо этого исправьте саму задачу.

Уменьшение радиуса поражения: --limit, --tags и --step

Режим проверки (check mode) показывает, какие изменения будут внесены. Эти флаги определяют, на скольких машинах изменения применяются одновременно.

--limit ограничивает выполнение сценария подмножеством инвентаря. Флаг принимает те же шаблоны, что и hosts:, поэтому работают как --limit web1, так и --limit 'webservers:!web3'. Шаблон необходимо брать в кавычки. Неограниченный кавычками ! в интерактивной сессии bash вызывает расширение истории по восклицательному знаку, и оболочка переписывает команду до того, как её получит Ansible.

Проверяйте шаблон перед тем, как доверить ему выполнение. ansible-playbook site.yml --limit 'webservers:!web3' --list-hosts выводит список соответствующих хостов и завершает работу, не подключаясь ни к одному из них. Шаблон, который ничего не находит, безопасен, так как Ansible не переходит к выполнению на всём инвентаре. Он выводит предупреждение о том, что не удалось сопоставить шаблон хостов, а затем завершается с ошибкой, сообщающей, что хосты и --limit не соответствуют ни одному из хостов. Понимание того, как файл инвентаря определяет эти группы, делает поведение шаблона предсказуемым.

--tags deploy запускает только задачи с указанными тегами, а --skip-tags packages — всё остальное. --list-tags выводит список доступных тегов. Теги становятся полезны, когда сценарий разрастается настолько, что вы уже не готовы запускать его целиком; это также одна из причин для разделения длинного playbook на роли.

--start-at-task "Write the site config" возобновляет выполнение после сбоя, начиная с указанной задачи. Используйте этот флаг для восстановления, но учитывайте последствия: всё, что предшествует этой задаче, пропускается, включая задачи, которые устанавливают факты или регистрируют переменные, необходимые для последующих шагов.

--step запрашивает подтверждение перед каждой задачей и ждёт ответа: да, нет или продолжить. Это медленный процесс, но он является подходящим инструментом при первом запуске деструктивных операций, так как позволяет остановиться между двумя задачами, а не после двадцати.

Развертывание изменений с использованием serial

По умолчанию Ansible выполняет одну задачу для всех хостов в play, прежде чем перейти к следующей. Это быстро, но означает, что неудачная задача затронет весь парк серверов в одну секунду. К тому моменту, как вы заметите ошибку и нажмете Ctrl-C, изменения уже будут везде.

serial разбивает play на группы (пакеты). Весь play выполняется сначала для первой группы, затем для следующей.

- name: Roll out the web tier
  hosts: webservers
  serial: [1, 5, "30%"]
  max_fail_percentage: 0
  tasks:
    - name: Deploy the release
      ansible.builtin.include_role:
        name: webapp

Первая группа состоит из одного хоста. Если он успешно проходит проверку, вторая группа будет состоять из пяти хостов, а каждая последующая — из 30 процентов хостов в play. max_fail_percentage: 0 завершает выполнение play, как только любой хост в группе выдает ошибку, поэтому неудачное обновление остановится на одной машине. any_errors_fatal: true — более жесткий вариант, который завершает выполнение play для всех при первой же ошибке на любом хосте.

Запуск сначала на одном хосте — это не паранойя, и на то есть конкретная причина. Состав групп в инвентаре со временем меняется. Сервер, добавленный через полгода после остальных, может использовать другой релиз дистрибутива, содержать сервис, установленный вручную, или иметь другую разметку дисков. Playbook может быть корректным для группы в целом, но ошибочным для этого конкретного хоста, и никакой тестовый запуск (dry run) на уже сконфигурированном хосте этого не покажет. Управление парком Linux-серверов — это во многом практика поиска «нестандартного» хоста до того, как его найдут изменения.

Порядок выполнения операций

  1. ansible-playbook site.yml --syntax-check выявляет ошибки в YAML и структуре без обращения к сети.
  2. ansible-playbook site.yml --limit web1 --list-hosts подтверждает, что ваш шаблон соответствует ожидаемым данным.
  3. ansible-playbook site.yml --limit web1 --check --diff — это пробный запуск. Изучите полученный diff.
  4. ansible-playbook site.yml --limit web1 --diff применяет изменения к конкретному хосту.
  5. Повторите шаг 4. Все задачи должны сообщить о статусе ok. Любой элемент со статусом changed — это задача, которую необходимо исправить, прежде чем применять изменения к остальному парку серверов.
  6. ansible-playbook site.yml --check --diff для всего инвентаря теперь возвращает осмысленный результат, так как сконвергированные хосты не выдают изменений, и вы видите только реальную разницу.

Предупреждение относительно шага 3. --diff выводит содержимое файлов в терминал и в лог CI-задания, поэтому шаблон, содержащий пароль от базы данных, отобразит этот пароль в логах. Установите diff: false для этой задачи, чтобы скрыть вывод, или no_log: true, чтобы полностью скрыть результат, а само значение храните в зашифрованном файле Ansible Vault, а не в репозитории.

FAQ

Изменяет ли ansible-playbook --check что-либо на сервере?

Нет, за одним исключением, которое вы контролируете. В режиме проверки (check mode) каждый модуль должен отчитываться о действиях вместо их выполнения; модули, которые не могут работать в таком режиме, ничего не делают и не сообщают. Исключением является ключевое слово задачи check_mode: false, которое заставляет эту конкретную задачу выполниться по-настоящему даже во время запуска --check. Проверьте свои плейбуки и роли на наличие check_mode: false, прежде чем доверять результатам пробного запуска, и убедитесь, что каждое такое вхождение относится к задаче, которая только считывает состояние.

В чем разница между --check и --diff?

--check определяет, будут ли выполняться какие-либо реальные действия. --diff определяет уровень детализации вывода. --check сам по себе сообщает, что файл был бы изменён. --diff сам по себе применяет изменения и показывает изменённые строки. Используйте их вместе для получения читаемого отчёта о пробном запуске, а также оставьте --diff включённым для реальных запусков, установив always = true в секции [diff] в файле ansible.cfg.

Почему моя задача Ansible сообщает о состоянии changed при каждом запуске?

Потому что модуль не может определить состояние, которым он управляет, или значение, которое вы ему передаёте, каждый раз отличается. Модули command и shell всегда сообщают changed, если вы не добавите creates или changed_when. file с параметром state: touch меняется по своей логике работы. Шаблон, который генерирует временную метку или свежий пароль, каждый раз создаёт разные байты, поэтому файл действительно перезаписывается. Запустите плейбук дважды подряд: всё, что по-прежнему имеет статус changed на втором проходе, является задачей, требующей исправления.

Почему мои задачи command и shell пропускаются во время пробного запуска?

Потому что не существует способа выполнить произвольную команду в режиме «только чтение». В режиме проверки модуль command устанавливает skipped: true с сообщением Command would have run if not in check mode. Добавьте creates или removes, чтобы режим проверки мог оценить результат проверки файла. Для задачи, которая только считывает состояние, установите check_mode: false вместе с changed_when: false, чтобы зарегистрированный результат существовал во время пробного запуска, а условия, построенные на его основе, продолжали работать.

Почему режим проверки завершается ошибкой на новом сервере, но проходит на уже настроенном?

Потому что режим проверки не создаёт состояние, от которого зависят последующие задачи. Пробный запуск на хосте без nginx сообщает об установке как changed, а затем завершается ошибкой на задаче, которая записывает данные в /etc/nginx/conf.d/, так как этот каталог не был создан. Это ожидаемое поведение. Режим проверки — это инструмент обнаружения отклонений (drift detector) для хостов, которые плейбук уже привёл к целевому состоянию. Он не может проверить первый запуск. На новом хосте примените плейбук к одной машине, а затем анализируйте результат второго запуска.

#ansible#check-mode#idempotency#automation#safety