Обработка ошибок: failed_when, changed_when и ignore_errors

Рейтинг: 67.6% · 8 голосов
Подробный курс по Ansible с прицелом на экзамен RHCE EX294: установка и инвентарь, плейбуки, переменные и факты, Vault, циклы и условия, шаблоны Jinja2, роли и коллекции, Execution Environments и ansible-navigator, управление системами (диски, LVM, cron, SELinux, firewalld). Примеры на RHEL/Fedora. Актуально на 2026.
Ответить
Аватара пользователя
Maksim_DevOps
Сообщения: 47
Зарегистрирован: 11 май 2026, 05:31

Обработка ошибок: failed_when, changed_when и ignore_errors

Сообщение Maksim_DevOps »

Оглавление курса (47)
  1. Что такое Ansible и зачем он нужен: автоматизация без агентов
  2. Сертификация RHCE и экзамен EX294: что внутри и как готовиться
  3. Архитектура Ansible: control node, узлы, модули и плагины
  4. Установка Ansible на control node: dnf, pip и версии ansible-core
  5. Подготовка управляемых узлов: SSH, пользователь и sudo
  6. Инвентарь Ansible: hosts, группы и переменные
  7. Настройка Ansible: файл ansible.cfg и приоритеты конфигурации
  8. Host patterns и инструменты командной строки Ansible
  9. Ad hoc команды Ansible: быстрые задачи без плейбука
  10. Ansible playbook: что это такое и как устроен
  11. Структура плейбука: play, task, модули и переменные
  12. Запуск плейбуков: проверка, теги, ограничения и отладка
  13. Параллелизм и порядок выполнения: forks, serial, strategy
  14. Факты Ansible: сбор информации об узлах
  15. Переменные Ansible: типы, объявление и приоритет
  16. Регистрация результатов и специальные переменные
  17. Ansible Vault: шифрование паролей и секретов
  18. Организация инвентаря: group_vars, host_vars и вложенные группы
  19. Динамический инвентарь и инвентарные плагины
  20. Циклы в Ansible: loop, списки и словари
  21. Повтор задачи до условия: until, retries и delay
  22. Условия в Ansible: директива when и логика
  23. Jinja2 в Ansible: выражения, фильтры и подстановки
  24. Обработчики Ansible: handlers, notify и flush_handlers
  25. Обработка ошибок: failed_when, changed_when и ignore_errors (вы здесь)
  26. Блоки в Ansible: block, rescue и always
  27. Управление файлами: модули file, copy, fetch и stat
  28. Архивы и сборка файлов: archive, unarchive, assemble
  29. Точечное редактирование файлов: lineinfile и blockinfile
  30. Шаблоны Jinja2: модуль template и динамические конфиги
  31. Jinja2 продвинуто: фильтры, циклы, макросы и lookup
  32. Модули Ansible: ansible-doc и поиск нужного модуля
  33. Разработка собственного модуля Ansible на Python
  34. Роли Ansible и Ansible Galaxy: переиспользование
  35. Разработка роли Ansible: создание с нуля
  36. Зависимости ролей и requirements.yml
  37. Коллекции Ansible: ansible.posix, community и своя коллекция
  38. Execution Environments: контейнеры для запуска Ansible
  39. Сборка Execution Environment с ansible-builder
  40. ansible-navigator: запуск плейбуков в Execution Environment
  41. Управление SSH-ключами через Ansible: authorized_key
  42. Управление дисками: filesystem, mount и parted
  43. LVM через Ansible: тома, группы и расширение
  44. Планировщик задач: cron и systemd timers через Ansible
  45. Безопасность: hardening SSH и репозитории dnf/yum
  46. SELinux через Ansible: булевы, контексты и порты
  47. Сквозной проект и подготовка к экзамену EX294
Рано или поздно ты столкнёшься с тем, что плейбук падает не там, где надо, или наоборот - радостно репортит "changed" на ровном месте, хотя ничего не поменялось. Классика: запускаешь ansible.builtin.command, он отрабатывает, возвращает rc=2, и Ansible считает это провалом - хотя для твоей логики rc=2 это норма. Или наоборот: команда отдаёт rc=0, но в stdout написано "ERROR: connection refused", а Ansible бодро рапортует "ok". И ещё одна вечная боль - command и shell ВСЕГДА показывают changed=yes, потому что Ansible понятия не имеет, изменила ли твоя команда систему. Из-за этого хендлеры триггерятся когда не надо, а отчёт по идемпотентности превращается в кашу.

В этом уроке разберём арсенал, которым ты рулишь успехом и провалом задач вручную: ignore_errors, failed_when, changed_when, плюс принудительный fail и assert, и групповые рубильники any_errors_fatal и max_fail_percentage. Это не просто удобство - на RHCE (EX294) умение правильно поставить changed_when и failed_when на command/shell это частая тонкость, на которой народ срезается. Дистрибутив по умолчанию RHEL/Fedora (dnf, systemd, firewalld), но вся эта механика контроля статусов от дистрибутива не зависит вообще - она про логику Ansible, а не про пакеты.

Как Ansible решает, что задача упала: rc, failed и changed

Каждый модуль возвращает результат - словарь с ключами. Для нас важны три вещи: rc (код возврата команды), failed (булев флаг "провал") и changed (булев флаг "что-то поменялось"). По умолчанию для command и shell правило простое: если rc не ноль - задача failed; и changed всегда true, потому что модуль не умеет проверять, был ли реальный эффект.

Все три флага ты можешь переопределить своими условиями. ignore_errors говорит "если задача упала - не останавливай весь плей, иди дальше". failed_when задаёт СВОЁ определение провала. changed_when задаёт СВОЁ определение изменения. Важно понимать порядок: сначала модуль отрабатывает, потом Ansible применяет твои changed_when и failed_when к тому, что вернулось. То есть это пост-фактум переоценка результата, а не предсказание.

Чтобы посмотреть, что вообще возвращает модуль, сохрани результат в register и распечатай:

Код: Выделить всё

- name: Посмотреть, что отдаёт команда
  hosts: web
  tasks:
    - name: Запустить проверку
      ansible.builtin.command: id apache
      register: out

    - name: Показать структуру результата
      ansible.builtin.debug:
        var: out
В выводе увидишь out.rc, out.stdout, out.stderr, out.failed, out.changed. На эти поля и опираются все условия ниже.

Изображение

ignore_errors: продолжить, когда сбой не критичен

Самый грубый инструмент. По запросу "ansible ignore_errors" обычно ищут именно это: как не дать одной упавшей задаче угробить весь прогон. Ставишь ignore_errors: true - и даже если задача завалилась, плей идёт дальше. Задача всё равно помечается красным как failed в отчёте, но фатальной остановки нет.

Код: Выделить всё

- name: Попытаться остановить сервис, которого может не быть
  ansible.builtin.systemd:
    name: legacy-app
    state: stopped
  ignore_errors: true
Грабли: ignore_errors глотает ЛЮБУЮ ошибку, включая те, про которые ты бы хотел знать. Это не "обработка ошибки", это "затыкание рта". Если тебе нужно именно отделить ожидаемые ситуации от настоящих поломок - это работа для failed_when, а не для ignore_errors. И ещё: ignore_errors не спасает от ошибок самого Ansible (несуществующий модуль, плохой YAML) - только от провала отработавшего модуля. Для недоступных хостов есть отдельный ignore_unreachable: true.

failed_when: своё определение провала

Вот где начинается взрослая работа с ошибками. failed_when принимает условие (или список условий) - и если оно истинно, задача считается провалившейся, даже если модуль сам по себе отработал успешно. И наоборот: если модуль вернул rc!=0, но твоё failed_when ложно - задача НЕ падает.

Допустим, утилита делает поиск и возвращает rc=1, когда ничего не нашла - для нас это нормально, а вот rc=2 это уже реальная беда:

Код: Выделить всё

- name: Грепнуть конфиг, rc=1 (не найдено) это ок
  ansible.builtin.command: grep "max_connections" /etc/my.cnf
  register: grep_out
  failed_when: grep_out.rc == 2
Частый сценарий по запросу "ansible failed_when" - ловить ошибку не по коду возврата, а по тексту в выводе. rc=0, но в stdout строка с проблемой:

Код: Выделить всё

- name: Проверить вывод на скрытую ошибку
  ansible.builtin.command: /usr/local/bin/deploy.sh
  register: dep
  failed_when: >
    dep.rc != 0 or
    "ERROR" in dep.stdout or
    "connection refused" in dep.stderr
Список в failed_when объединяется через AND - все элементы должны быть истинны, чтобы засчитать провал:

Код: Выделить всё

- name: Провал только если И код плохой, И вывод пустой
  ansible.builtin.command: /opt/check.sh
  register: chk
  failed_when:
    - chk.rc != 0
    - chk.stdout == ""
Если нужна логика OR между блоками условий - пиши одно строковое выражение с or, как в примере с deploy.sh выше. Это типичная путаница: YAML-список это всегда AND.

changed_when и спасение от вечного "changed" у command/shell

Теперь главный герой урока для экзамена. Модули command, shell, raw, script не идемпотентны by design - они не знают, что делает твоя команда, поэтому штампуют changed=yes на каждый запуск. Если такая команда чисто читающая (проверка, grep, статус) - руками говорим Ansible "это изменением не считается":

Код: Выделить всё

- name: Чисто читающая проверка, изменением не считается
  ansible.builtin.command: systemctl is-active nginx
  register: nginx_state
  changed_when: false
changed_when: false - идиома, которую на RHCE проверяют постоянно. Любая команда, которая ничего не меняет (а только сообщает состояние), должна иметь changed_when: false, иначе твой плейбук перестаёт быть честным по идемпотентности и зря дёргает хендлеры.

А если команда реально что-то делает, но только иногда - завязывай changed на вывод. Например, скрипт печатает "UPDATED", когда применил изменение:

Код: Выделить всё

- name: Применить миграцию, changed только при реальном апдейте
  ansible.builtin.command: /opt/app/migrate.sh
  register: migr
  changed_when: "'UPDATED' in migr.stdout"
  notify: restart app
Теперь хендлер restart app дёрнется только когда миграция реально прошла, а не на каждый прогон. Это и есть правильная связка changed_when плюс notify. Маленькая ремарка для Ubuntu/Debian и RU-дистрибутивов (RED OS, Astra): вся эта логика одинаковая - меняется только сама команда внутри (apt вместо dnf, пути сервисов), а changed_when/failed_when работают тем же образом.

Принудительный fail, assert и групповые рубильники

Иногда нужно упасть осознанно. Модуль ansible.builtin.fail роняет задачу с твоим текстом - по запросу "ansible fail" это первое, что нужно. Обычно в паре с when:

Код: Выделить всё

- name: Проверить переменную окружения
  ansible.builtin.fail:
    msg: "Переменная deploy_env не задана или неверна: {{ deploy_env | default('пусто') }}"
  when: deploy_env not in ['dev', 'stage', 'prod']
Когда условий несколько и они про валидацию входных данных - чище взять ansible.builtin.assert. Он проверяет список выражений в that и сам формирует понятное сообщение об ошибке (можно задать fail_msg и success_msg):

Код: Выделить всё

- name: Превентивная проверка параметров перед раскаткой
  ansible.builtin.assert:
    that:
      - app_port | int > 1024
      - app_port | int < 65536
      - target_dir is defined
    fail_msg: "Неверные параметры: порт {{ app_port }} или не задан target_dir"
    success_msg: "Параметры в порядке, продолжаем"
Грабли с assert: выражения в that это Jinja-условия, но БЕЗ фигурных скобок. Если обернёшь в {{ }} - получишь невнятную ошибку. И помни про типы: app_port из inventory это строка, поэтому фильтр int обязателен, иначе сравнение отработает не так, как ждёшь. Любой неотловленный "ansible error" в продакшене обычно дешевле поймать вот таким assert на старте плея, чем разгребать полусломанный сервер потом.

Теперь масштаб. По умолчанию падение задачи на одном хосте убирает только этот хост из дальнейшей работы - остальные продолжают. Иногда так нельзя. any_errors_fatal: true означает "упал хоть один хост на этой задаче - валим весь плей на всех хостах разом". Полезно, когда хосты в кластере зависят друг от друга:

Код: Выделить всё

- name: Критичная фаза, падение одного валит всех
  hosts: dbcluster
  any_errors_fatal: true
  tasks:
    - name: Применить схему
      ansible.builtin.command: /opt/apply-schema.sh
      changed_when: true
max_fail_percentage даёт порог терпимости: "продолжай, пока процент упавших хостов не превысил N". Например, при раскатке на 100 машин допустим 20 процентов потерь:

Код: Выделить всё

- name: Роллинг-апдейт с порогом отказов
  hosts: webfarm
  serial: 10
  max_fail_percentage: 20
  tasks:
    - name: Обновить пакет
      ansible.builtin.dnf:
        name: myapp
        state: latest
И ещё один важный нюанс про хендлеры: если задача в плее упала, Ansible по умолчанию НЕ запускает накопленные хендлеры на этом хосте. Иногда это плохо - например, ты поменял конфиг (notify на restart), а следующая задача упала, и сервис так и не перезапустился со старым конфигом в памяти. force_handlers: true заставляет прогнать хендлеры даже после провала:

Код: Выделить всё

- name: Гарантировать перезапуск даже при сбое дальше
  hosts: web
  force_handlers: true
  tasks: ...
Мини-лаба

Повтори руками на тестовой RHEL/Fedora-машине:
  • Запусти ansible.builtin.command с заведомо читающей командой (например, id root), сохрани в register, выведи debug - найди rc, changed, failed.
  • Добавь changed_when: false и убедись, что в отчёте задача стала green ok вместо yellow changed.
  • Сделай задачу с grep по файлу: настрой failed_when так, чтобы rc=1 (не найдено) НЕ был провалом, а rc=2 (ошибка доступа) был.
  • Напиши assert на проверку переменной (например, app_port в диапазоне), прогони с заведомо плохим значением и посмотри на fail_msg.
  • Поставь на задаче ignore_errors: true и сравни поведение плея с тем же без него.
Контрольные вопросы
  • Почему command и shell всегда показывают changed=yes и какой строчкой это чинят для читающих команд?
  • Чем ignore_errors принципиально отличается от failed_when - в каком случае что выбрать?
  • Как в failed_when сделать логику OR между несколькими условиями, если YAML-список это AND?
  • Что произойдёт с остальными хостами при падении одного, если задан any_errors_fatal: true, и чем тут поможет max_fail_percentage?
Итог

ignore_errors глушит ошибку грубо, failed_when и changed_when дают точный контроль над тем, что считать провалом и изменением, fail и assert роняют плей осознанно с понятным сообщением, а any_errors_fatal, max_fail_percentage и force_handlers рулят поведением на всём парке хостов. Главное, что унесёшь на экзамен: для command/shell почти всегда нужен либо changed_when: false (читающие команды), либо changed_when по выводу, плюс осмысленный failed_when - без них плейбук врёт об идемпотентности и падает не там, где должен.
👍2 ❤️3 🔥 😄 🤔
Аватара пользователя
TorAndy
Сообщения: 1
Зарегистрирован: 26 май 2026, 04:23

Re: Обработка ошибок: failed_when, changed_when и ignore_errors

Сообщение TorAndy »

Спасибо, наконец-то дошло почему мои хендлеры дёргались на каждый прогон - забивал changed_when: false на проверочные command. Поправил, и отчёт стал честный.
👍 ❤️ 🔥 😄 🤔1
Аватара пользователя
paulsmith
Сообщения: 1
Зарегистрирован: 28 май 2026, 02:39

Re: Обработка ошибок: failed_when, changed_when и ignore_errors

Сообщение paulsmith »

А вопрос: если на задаче стоит и ignore_errors: true, и failed_when - что победит? У меня failed_when срабатывает, а плей всё равно идёт дальше, я запутался.
👍1 ❤️ 🔥 😄 🤔1
Ответить
← Предыдущая глава
Обработчики Ansible: handlers, notify и flush_handlers
Следующая глава →
Блоки в Ansible: block, rescue и always

Все главы курса «Ansible: автоматизация и подготовка к RHCE (EX294)»

Поделиться темой: ✈ Telegram VK

Вернуться в «Ansible: автоматизация и подготовка к RHCE (EX294)»

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и 1 гость