К содержанию
Pingera

Мониторинг cron-задач: как понять, что крон не сработал

  • Опубликовано:
  • Обновлено
  • 6 мин чтения
Робот-сторож спит за столом мониторинга с газетой на лице; на стене ряд часов, один — под паутиной; на планшете плоская линия пульса; в дверях заглядывает браузерный персонаж

Классика жанра: бэкапы «делались» полгода, пока однажды не понадобилось восстановиться, — и выяснилось, что последнему успешному бэкапу три недели. Cron честно запускал задачу каждую ночь, задача честно падала на второй минуте, а письмо об ошибке лежало в локальном почтовом ящике root, который никто не открывал.

Коротко: cron запускает команды по расписанию, но не проверяет результат — код возврата игнорируется, вывод теряется, а если задача вообще не запустилась, узнать об этом неоткуда. Ниже разберём, почему сбои cron-задач такие тихие, как проверить, что задача действительно выполняется, и как устроен мониторинг по паттерну «пульсар» (heartbeat).

Почему сбои cron-задач незаметны

  • Cron запускает команду и не интересуется результатом: завершилась задача с кодом 0 или 1 — планировщику всё равно.
  • Вывод задачи по умолчанию отправляется письмом владельцу crontab (обычно root) в локальный почтовый ящик, который на серверах никто не читает. А если на сервере нет почтового агента, вывод просто выбрасывается.
  • Задача может вовсе не запуститься: сервер не поднялся после перезагрузки, crond остановлен, задачу добавили в crontab не того пользователя или в строке остался неэкранированный символ %. В crontab он означает перевод строки, поэтому команда обрезается — классический пример date +%F.
  • Окружение в cron минимальное: PATH короче, переменные из .bashrc не подгружаются, venv не активирован, docker не находится. Команда «работает руками», но падает по расписанию.
  • Задача может зависнуть и висеть часами, блокируя следующие запуски или съедая память.

Общий знаменатель: сбой задачи по расписанию не бросается в глаза. Падение веб-сервера пользователи замечают сразу, а мёртвый крон может молчать неделями.

Базовая диагностика: запускалась ли задача

Лог планировщика

Проверьте, запускал ли cron команду вообще:

# Debian, Ubuntu
grep CRON /var/log/syslog | tail
journalctl -u cron --since today

# RHEL, AlmaLinux, Fedora
journalctl -u crond --since today

Если строки о запуске нет — задача не стартовала: проверьте crontab (чей он, синтаксис расписания, перевод строки в конце файла) и работает ли сам crond. На системах без rsyslog файла /var/log/syslog нет — используйте journalctl.

Свой лог с кодом завершения

Сохраняйте вывод задачи и фиксируйте результат. Удобнее всего сделать это в небольшой обёртке — в одной строке crontab такая логика быстро становится нечитаемой:

#!/bin/bash
# /opt/scripts/run-backup.sh
start=$(date +%s)
/opt/scripts/backup.sh >> /var/log/backup.log 2>&1
code=$?
echo "$(date -Is) exit=$code duration=$(( $(date +%s) - start ))s" >> /var/log/backup.log
exit $code

Так появляется минимальная история: когда задача запускалась, сколько выполнялась и чем закончилась. Внутри скрипта символ % можно использовать свободно — экранировать его нужно только в самой строке crontab.

MAILTO

Строка MAILTO=ops@example.com в начале crontab даёт хоть какой-то сигнал: cron пришлёт письмо, если задача что-то вывела. Но для этого на сервере должна работать отправка почты, а письма теряются в спаме и не превращаются в алерты. Это подстраховка, а не мониторинг.

flock против параллельных запусков

Если задача может выполняться дольше интервала между запусками, защитите её от наложения:

flock -n /var/lock/backup.lock /opt/scripts/backup.sh

С ключом -n второй экземпляр не ждёт освобождения блокировки, а сразу завершается с ошибкой.

timeout против зависаний

Ограничьте время выполнения, чтобы зависшая задача превратилась в обычную ошибку:

flock -n /var/lock/backup.lock timeout 2h /opt/scripts/backup.sh

Если задача не уложится в два часа, timeout завершит её и вернёт код 124 — это будет видно в логе.

Общая проблема всех этих способов: они срабатывают, только когда сервер жив и кто-то смотрит в логи или почту. Если сервер умер целиком, не поднялся crond или потерялся crontab, локальная диагностика молчит вместе с задачей. Нужен сигнал извне.

Паттерн «пульсар»: отчёт извне

Суть паттерна: в конце успешного запуска задача «отчитывается» внешнему сервису — отправляет HTTP-запрос на уникальный URL. Сервис знает, как часто должен приходить такой сигнал, и если он не пришёл вовремя (с учётом допустимой задержки), поднимает тревогу.

В crontab это одна строка:

0 3 * * * /opt/scripts/run-backup.sh && curl -fsS -m 10 --retry 3 https://api.pingera.ru/v1/heartbeats/<ID>/ping > /dev/null

Оператор && гарантирует, что сигнал уйдёт только при нулевом коде завершения, а -m и --retry не дают сетевому сбою подвесить задачу или потерять отчёт.

Что это ловит:

  • задачу, которая упала или зависла, — сигнала нет;
  • сервер, который не поднялся после перезагрузки или обновления;
  • остановленный crond и потерянный crontab — в логах пусто, а сигнал не приходит;
  • сбитое расписание — задача стала запускаться реже, чем задумано, например после неудачной правки crontab.

Два правила. Первое: сигнал отправляется только после успеха. Отчёт в начале задачи маскирует падение на последнем шаге. Второе: допустимая задержка должна с запасом перекрывать разброс времени выполнения — иначе медленный, но успешный запуск будет вызывать ложные алерты.

Чек-лист мониторинга cron-задач

  • Лог каждого запуска: время старта, длительность, код завершения.
  • Алерт на ненулевой код возврата — через обёртку или сборщик логов, а не только через MAILTO.
  • flock для защиты долгих задач от параллельных запусков.
  • timeout для каждой долгой задачи: зависание должно превращаться в ошибку.
  • Полные пути и явное окружение: PATH в crontab, активация venv, полный путь к docker.
  • Пульсар для критичных задач: бэкапов, начислений, синхронизаций, рассылок.
  • Инвентаризация: у каждой задачи в crontab есть владелец. «Ничейных» задач быть не должно.

Как устроены пульсары Pingera

Пульсары Pingera — готовая реализация этого паттерна. Для каждой задачи вы создаёте пульсар, задаёте ожидаемый период и допустимую задержку и получаете уникальный URL вида https://api.pingera.ru/v1/heartbeats/<ID>/ping. Сигнал можно отправить GET- или POST-запросом. В POST можно передать JSON с подробностями запуска — например, длительность или число обработанных записей, — и эти данные вместе с адресом отправителя сохранятся в истории сигналов.

Ключевое свойство — проверка работает снаружи вашей инфраструктуры. Ей всё равно, что именно случилось: упала задача, завис скрипт или умер весь сервер, — отсутствие сигнала будет замечено в любом случае. Если сигнал не пришёл в срок и допустимая задержка истекла, пульсар переходит в статус «недоступен» и присылает алерт, а когда задача снова отчитается — уведомление о восстановлении.

Алерты приходят в Telegram, MAX, на почту, по SMS или в вебхук — туда, где дежурная команда действительно их увидит, а не в локальный ящик root. Для каждого пульсара можно выбрать свои каналы. Менять сами скрипты не нужно — достаточно дописать в crontab вызов curl. Пошаговая настройка, включая создание пульсара через API, — в статье «Пульсары — cronjob-мониторинг с Pingera».

Начать можно на бесплатном тарифе; командам с продакшн-задачами подойдут тарифы Про — от 1800 ₽/мес (при оплате за год два месяца бесплатно).

Часто задаваемые вопросы

Чем пульсар лучше логов?

Логи отвечают на вопрос «что случилось», но только когда вы их читаете. Пульсар отвечает на вопрос «жива ли задача вообще» — постоянно и без вашего участия, в том числе когда сервер не загрузился и логи писать некому.

Что делать, если задача выполняется дольше обычного?

Заложите в допустимую задержку пульсара запас относительно реального времени выполнения. А чтобы задача не висела бесконечно, ограничьте её время утилитой timeout: если задача не уложится в лимит, она будет завершена с ошибкой, сигнал не уйдёт — и вы получите алерт.

Можно ли отправлять сигнал в начале или в середине задачи?

Не стоит. Сигнал должен уходить только после успешного завершения — иначе падение на последнем шаге останется незамеченным, а мониторинг будет показывать, что всё в порядке.

Как мониторить Kubernetes CronJob и systemd-таймеры?

Так же, как обычный cron: вызовите curl после основной команды. В Kubernetes CronJob это может быть sh -c "/app/job && curl -fsS ..." в команде контейнера. В systemd-сервисе с Type=oneshot удобно использовать ExecStartPost=: он выполняется только после успешного завершения основной команды.

Итог

Cron — один из немногих слоёв инфраструктуры, который может месяцами не работать незаметно: планировщик не проверяет результат, а локальные логи видит только тот, кто их открывает. Минимальный набор: лог с кодом завершения, flock, timeout и алерты на ошибки. Для критичных задач — пульсар: Pingera сообщит, если задача не отчиталась вовремя, даже когда молчит весь сервер.

Читайте также

Проверки · Сайты · DevOps

Ошибка 503 Service Unavailable: что значит и как починить

Код 503 — временное «не справляюсь»: перегрузка, rate limiting, плановые работы или ни одного живого бэкенда. Разбираем, какой слой вернул ошибку, как быстро найти причину и как устранить каждую из них.

  • Опубликовано:
  • Обновлено
  • 8 мин чтения

Безопасность · Проверки · DevOps

Почему истекают SSL-сертификаты, даже с автопродлением

Сертификаты истекают не из-за забывчивости, а из-за тихих сбоев автопродления, а сроки действия сокращаются до 47 дней. Разбираем, почему ломается продление, как заметить это заранее и что делать, если сертификат уже истёк.

  • Опубликовано:
  • Обновлено
  • 8 мин чтения