Планировщик задач: cron и systemd timers через Ansible

Рейтинг: 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

Планировщик задач: cron и systemd timers через Ansible

Сообщение 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
Представь парк из тридцати серверов, на каждом должен по ночам крутиться бэкап. Заходить по ssh на каждый, открывать crontab -e, руками вписывать строку, ничего не перепутать в пяти полях расписания - это путь к ошибкам и седым волосам. Прогнал плейбук дважды - и получил две одинаковые задачи в кроне, потому что забыл, что crontab не дедуплицирует. В этом уроке разберём, как Ansible превращает планировщик задач в управляемый код: модуль ansible.builtin.cron, идемпотентность по имени, файлы в /etc/cron.d, а заодно современную альтернативу - systemd timers. И да, cron-задачи через модуль cron - это типовое задание из главы 10 и классический вопрос на RHCE (EX294), так что материал прикладной во всех смыслах.

Как работает ansible cron и почему имя - это всё

Сначала вспомним механику самого cron. Демон crond читает crontab-файлы и раз в минуту проверяет, не пора ли что-то запустить. Строка расписания - это пять полей: минута, час, день месяца, месяц, день недели, потом сама команда.

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

# m  h  dom mon dow  command
  30 3  *   *   *    /usr/local/bin/backup.sh
Эта строка означает "каждый день в 03:30". Звёздочка - любое значение. Можно списки (1,15), диапазоны (1-5), шаги (*/10 - каждые десять минут). Для частых случаев есть спецвремена: @reboot, @hourly, @daily, @weekly, @monthly, @yearly.

Теперь главное про планировщик ansible. Модуль ansible.builtin.cron не просто дописывает строку в crontab - он управляет ею по идентификатору. И этот идентификатор - параметр name. Ansible вставляет над задачей служебный комментарий вида "#Ansible: backup nightly", и при следующих запусках ищет именно его. Совпало имя - задача уже есть, ничего не делает (ok). Поменялось расписание - перепишет строку (changed). Поставил state: absent - удалит. Вот как выглядит правильная ansible cron job:

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

- name: Ночной бэкап в кроне пользователя root
  ansible.builtin.cron:
    name: "nightly backup"
    minute: "30"
    hour: "3"
    job: "/usr/local/bin/backup.sh >> /var/log/backup.log 2>&1"
    user: root
    state: present
Прогоняй этот плейбук хоть десять раз подряд - задача в кроне будет ровно одна. Это и есть идемпотентность. А теперь запомни главную ловушку: если ты НЕ укажешь name, модуль сравнивает задачи по тексту команды и легко наплодит дубли при малейшем изменении. Поэтому правило простое - name обязателен всегда. На экзамене за дубли в crontab легко потерять баллы, проверяющий смотрит crontab -l.

Поля minute/hour/day/month/weekday по умолчанию равны "*". Удобно: если хочешь "каждый час в 0 минут", задаёшь только minute: "0", остальное само станет звёздочкой. Для спецвремён есть отдельный параметр special_time (значения reboot, hourly, daily, weekly, monthly, annually/yearly):

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

- name: Чистка временных файлов раз в сутки
  ansible.builtin.cron:
    name: "cleanup tmp"
    special_time: daily
    job: "find /var/tmp -type f -mtime +7 -delete"
    user: root
Изображение

Файл в /etc/cron.d и переменные окружения: ansible crontab по-взрослому

Класть всё в личный crontab root - не всегда удобно. На проде чище держать задачу отдельным файлом в /etc/cron.d/ - его видно, легко выгрести в системе контроля версий, легко удалить целиком. Для этого у модуля есть параметр cron_file. Важный нюанс, на котором многие спотыкаются: путь указывается относительно /etc/cron.d, без слеша в начале. Напишешь абсолютный путь - получишь не то. И второе - в формате /etc/cron.d строка обязана содержать пользователя, поэтому параметр user тут не опционален, его требует сам модуль.

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

- name: Бэкап как файл в /etc/cron.d
  ansible.builtin.cron:
    name: "nightly backup"
    cron_file: backup        # это /etc/cron.d/backup
    user: root
    minute: "30"
    hour: "3"
    job: "/usr/local/bin/backup.sh >> /var/log/backup.log 2>&1"
    state: present
Сгенерится файл /etc/cron.d/backup примерно такого вида:

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

#Ansible: nightly backup
30 3 * * * root /usr/local/bin/backup.sh >> /var/log/backup.log 2>&1
Обрати внимание - тут шесть полей, потому что после расписания идёт имя пользователя (root). Это формат именно cron.d, в личном crontab пользователя такого поля нет.

Часто скрипту нужно окружение: правильный PATH, MAILTO для отчётов об ошибках, какая-нибудь переменная для бэкап-утилиты. Тот же модуль умеет управлять и переменными - через env: true. Тогда name трактуется как имя переменной, а job - как её значение:

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

- name: PATH для задач в cron.d
  ansible.builtin.cron:
    cron_file: backup
    user: root
    name: PATH
    env: true
    job: "/usr/local/bin:/usr/bin:/bin"

- name: Слать вывод на почту админа
  ansible.builtin.cron:
    cron_file: backup
    user: root
    name: MAILTO
    env: true
    job: "ops@example.org"
Переменные окружения добавляются в начало файла, выше задач, как и положено в crontab. Если задачу надо временно выключить, не удаляя - есть параметр disabled: true, он закомментирует строку. Удобно для maintenance-окон.

Маленькая заметка про дистрибутивы. Всё вышеописанное одинаково работает на RHEL, Fedora, Rocky/Alma, а также на RED OS и Astra Linux - формат crontab стандартный. На Debian/Ubuntu тоже всё то же, разница лишь в том, что пакет называется cron, а в RHEL-семействе - cronie (ставится через dnf install cronie, сервис crond). Сам модуль про это не думает, он работает с файлами.

systemd timers через Ansible и at для разовых задач

Cron - проверенная классика, но у systemd есть свой планировщик - таймеры. Чем ansible systemd timer лучше cron в ряде случаев: подробное логирование через journalctl, зависимости между юнитами, опция Persistent=true (пропущенный из-за выключенного сервера запуск выполнится при старте), точность и случайные сдвиги (RandomizedDelaySec), чтобы тридцать серверов не ломанулись делать бэкап в одну секунду.

Готового модуля "сделай мне таймер" нет - и не надо. Таймер в systemd это пара файлов: юнит .service (что запускать) и юнит .timer (когда). Раскладываем их шаблоном ansible.builtin.template (или copy), затем включаем через ansible.builtin.systemd_service. Вот связка целиком:

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

- name: Service-юнит бэкапа
  ansible.builtin.copy:
    dest: /etc/systemd/system/backup.service
    content: |
      [Unit]
      Description=Nightly backup

      [Service]
      Type=oneshot
      ExecStart=/usr/local/bin/backup.sh
    owner: root
    group: root
    mode: "0644"

- name: Timer-юнит бэкапа
  ansible.builtin.copy:
    dest: /etc/systemd/system/backup.timer
    content: |
      [Unit]
      Description=Run backup daily at 03:30

      [Timer]
      OnCalendar=*-*-* 03:30:00
      Persistent=true
      RandomizedDelaySec=300

      [Install]
      WantedBy=timers.target
    owner: root
    group: root
    mode: "0644"

- name: Включить и запустить таймер
  ansible.builtin.systemd_service:
    name: backup.timer
    enabled: true
    state: started
    daemon_reload: true
Параметр daemon_reload: true заставит systemd перечитать новые юниты - без него он их просто не увидит. Запускаем именно .timer, а не .service: таймер сам дёрнет сервис по расписанию. Проверить руками: systemctl list-timers покажет ближайшие срабатывания, journalctl -u backup.service - логи последнего прогона. Синтаксис OnCalendar свой, не cron-овский; проверять выражения удобно командой systemd-analyze calendar "Mon *-*-* 03:30:00".

И последнее - разовые задачи. Если надо запустить что-то один раз через два часа, cron избыточен, для этого есть at. Готового модуля тоже нет, гоним через ansible.builtin.command (не забыв про идемпотентность - command всегда changed, поэтому ставим защиту):

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

- name: Разовый прогрев кеша через at
  ansible.builtin.command:
    cmd: "at now + 2 hours -f /usr/local/bin/warmup.sh"
  register: at_res
  changed_when: at_res.rc == 0
Типичные грабли
  • Нет параметра name - дубли в crontab при каждом изменении job. Лечится только именем.
  • Абсолютный путь в cron_file (например /etc/cron.d/backup). Нужно относительное: cron_file: backup.
  • Забыл user при cron_file - модуль выдаст ошибку, в формате cron.d пользователь обязателен.
  • В скрипте бэкапа сломанный или пустой PATH. Cron запускает с очень скудным окружением, поэтому в самом скрипте пиши абсолютные пути к бинарникам или задавай PATH через env-переменную.
  • Создал systemd timer, но забыл daemon_reload и enabled - юнит лежит, но не тикает.
  • SELinux: если бэкап пишет в нестандартный каталог, проверь контекст (ls -Z, при необходимости restorecon или semanage fcontext), иначе скрипт молча упрётся в Permission denied при включённом enforcing.
Мини-лаба

Возьми пару тестовых хостов (или контейнеров) и руками повтори:
  • Напиши плейбук, который кладёт /usr/local/bin/backup.sh (через copy) и заводит ansible cron job на 03:30 с name. Прогони дважды, проверь crontab -l - задача одна.
  • Перенеси ту же задачу в /etc/cron.d через cron_file, добавь env-переменные PATH и MAILTO. Глянь содержимое /etc/cron.d/backup.
  • Поставь disabled: true и убедись, что строка закомментировалась, потом верни обратно.
  • Сделай тот же бэкап через systemd timer: два юнита + systemd_service. Проверь systemctl list-timers и journalctl -u backup.service.
Контрольные вопросы
  • Почему параметр name критичен для идемпотентности модуля cron и что произойдёт без него?
  • Чем путь в cron_file отличается от обычного и какой ещё параметр становится обязательным при работе с /etc/cron.d?
  • Какие два юнита нужны для systemd timer и почему мы запускаем именно .timer, а не .service?
  • Что даёт Persistent=true в таймере и какого аналога нет у классического cron?
Итог

Планировщик через Ansible - это про предсказуемость. Модуль ansible.builtin.cron с обязательным name даёт идемпотентные задачи, cron_file выносит их в аккуратный файл /etc/cron.d, env-переменные чинят окружение. Где нужны логи, зависимости и устойчивость к простоям - бери systemd timers через template + systemd_service, для разовых дел - at. На RHCE спросят именно cron-модуль, так что доведи работу с name и расписанием до автоматизма.
👍5 ❤️3 🔥3 😄 🤔
Аватара пользователя
raetim
Сообщения: 1
Зарегистрирован: 11 май 2026, 18:08

Re: Планировщик задач: cron и systemd timers через Ansible

Сообщение raetim »

Спасибо, наконец дошло зачем name обязателен - у меня реально плодились дубли в crontab после каждого прогона, теперь ясно. А systemd timer для бэкапов прям зашёл, journalctl удобнее чем ловить вывод из мейла.
👍2 ❤️2 🔥 😄 🤔
Аватара пользователя
nginx_whale
Сообщения: 1
Зарегистрирован: 21 май 2026, 04:36

Re: Планировщик задач: cron и systemd timers через Ansible

Сообщение nginx_whale »

Вопрос: а cron_file путь правда без слеша в начале? Я по привычке писал /etc/cron.d/backup и потом долго не мог понять почему задача не там где жду. Поставил просто backup - заработало.
👍 ❤️ 🔥 😄 🤔
Ответить
← Предыдущая глава
LVM через Ansible: тома, группы и расширение
Следующая глава →
Безопасность: hardening SSH и репозитории dnf/yum

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

Поделиться темой: ✈ Telegram VK
Похожие запросы: условие when в ansible как задать в задачеуправление selinux через ansible булевы контексты и портыуправление lvm и cron через ansible тома и таймеры

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

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

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