Назад в блог

Мониторинг cron-задач: паттерн heartbeat на практике

· 6 мин чтения

HTTP-мониторинг не сообщит, что ночной cron упал. Паттерн heartbeat переворачивает направление: cron сам отчитывается мониторингу, и если он не позвонит вовремя, вы получите оповещение.

Мониторинг cron-задач: паттерн heartbeat на практике

Проблема: фоновые задачи без HTTP-эндпоинта

У типичного бэкенда несколько путей:

  • Веб-запросы (HTTP/HTTPS к серверу) - вы мониторите их через uptime check.
  • Cron-задачи (ежедневный бэкап, ежемесячный биллинг, ежечасная синхронизация) - у них нет HTTP-эндпоинта, внешний мониторинг не может за ними следить.
  • Воркеры (Celery, BullMQ, Sidekiq), обрабатывающие очередь, - тоже без HTTP.

Когда cron падает (опечатка в crontab, заполненный диск, отсутствующая переменная окружения (environment variable) или ошибка импорта после обновления зависимости (dependency upgrade)), вас никто не оповестит. Вы узнаёте об этом в понедельник утром, заметив, что за выходные не ушло ни одного счёта.

Паттерн heartbeat: cron пингует мониторинг

Принцип обратен обычному мониторингу:

  1. В сервисе мониторинга вы создаёте heartbeat-монитор с ожидаемым интервалом (например, "каждые 60 минут").
  2. Вы получаете уникальный heartbeat URL: https://epulz.io/heartbeat/abc123xyz.
  3. В своей cron-задаче в конце успешного выполнения вы вызываете этот URL (HTTP GET или POST).
  4. Если пинг не приходит в ожидаемое время (плюс grace-период), мониторинг вас оповещает.

Практический пример: heartbeat из cron-задачи

# /etc/crontab
0 3 * * * www-data /usr/local/bin/backup.sh && curl -fsS -m 10 \
  https://epulz.io/heartbeat/abc123xyz > /dev/null

Ключевой здесь оператор &&: heartbeat отправляется только тогда, когда backup.sh завершается с кодом выхода 0. Если скрипт упадёт, пинг не придёт, и в течение часа вы получите оповещение.

Совет: Для более тщательного покрытия добавьте также heartbeat "start":

curl -fsS -m 10 https://epulz.io/heartbeat/backup-start-xyz > /dev/null
/usr/local/bin/backup.sh && \
  curl -fsS -m 10 https://epulz.io/heartbeat/backup-done-xyz > /dev/null

Тогда мониторинг сможет отличить "начал, но не завершил" (скрипт завис) от "вообще не начал" (cron-задача не запустилась).

Python: requests + try/except

import os, requests
HEARTBEAT_URL = os.environ["HEARTBEAT_URL"]

def sync_data():
    # ... your logic ...
    pass

try:
    sync_data()
    requests.get(HEARTBEAT_URL, timeout=10)
except Exception as e:
    # Heartbeat не отправляется - мониторинг вас оповестит
    raise

Node.js: async / await

const HEARTBEAT_URL = process.env.HEARTBEAT_URL;

async function nightlyJob() {
  await processInvoices();
  await fetch(HEARTBEAT_URL, { signal: AbortSignal.timeout(10000) });
}

nightlyJob().catch(err => {
  console.error(err);
  process.exit(1);
});

Grace-период: сколько времени дать перед оповещением

Heartbeat-монитору нужна некоторая толерантность. Cron иногда выполняется дольше обычного, у сети есть задержка, а синхронизация NTP может быть слегка смещена. Grace-период - это время после истечения ожидаемого интервала, в течение которого мониторинг ещё ждёт, прежде чем запустить оповещение.

Практические значения:

  • Ежечасный cron: интервал 60 мин + grace 10 мин
  • Ежедневный бэкап (в среднем 20 мин): интервал 1440 мин + grace 60 мин
  • Еженедельный отчёт: интервал 10080 мин + grace 360 мин (6 ч)

Слишком тесный grace = ложные оповещения (false positive). Слишком свободный = запоздалое оповещение в тот момент, когда задача реально падает.

Где паттерн heartbeat помогает больше всего

  • Ночные бэкапы БД
  • Синхронизация с внешними API (CRM, бухгалтерия, платежи)
  • Расчёты отчётов
  • Задачи cleanup (удаление старых сессий, логов, временных файлов)
  • Цикл healthcheck долгоживущих воркеров
  • Запланированные письма, рассылки, биллинг

Заключение

Фоновые задачи (background) зачастую критичнее самого сайта, но остаются слепым пятном мониторинга. Паттерн heartbeat требует всего пять минут на внедрение (достаточно добавить curl в конец строки cron) и даёт ту же уверенность, что и uptime-мониторинг фронтенда.

Краткая напоминалка: синтаксис cron

Большинство ошибок heartbeat на самом деле являются ошибками cron - пинг никогда не срабатывает, потому что расписание было неверным. Пять полей:

┌── минута (0-59)
│ ┌── час (0-23)
│ │ ┌── день месяца (1-31)
│ │ │ ┌── месяц (1-12)
│ │ │ │ ┌── день недели (0-7, 0 и 7 = воскресенье)
│ │ │ │ │
* * * * *  команда
Выражение Запускается
*/5 * * * * каждые 5 минут
0 * * * * в начале каждого часа
0 3 * * * каждый день в 03:00
0 3 * * 1 каждый понедельник в 03:00
30 2 1 * * в 02:30 первого числа месяца

Классическая ловушка: */5 в поле час означает не "каждые 5 часов от текущего момента", а часы, делящиеся на пять (0, 5, 10, 15, 20). Если ожидаемый интервал монитора и реальное расписание cron не совпадают, вы получаете фантомные оповещения.

Принцип "dead man's switch"

Паттерн heartbeat - это dead man's switch (предохранитель мёртвого человека): тревога - это тишина. Это переворачивает обычный режим сбоя. Обычный мониторинг может отказать беззвучно - если упадёт сам сервер мониторинга, он просто перестанет слать оповещения, и вы об этом никогда не узнаете. Heartbeat - наоборот: триггером оповещения служит отсутствие ожидаемого сигнала, поэтому задача, которая полностью перестаёт выполняться (весь сервер выключен, демон cron отключён, машина выведена из эксплуатации и забыта), - это именно тот случай, который он улавливает лучше всего.

За пределами cron: другие планировщики

Паттерн одинаков независимо от того, что запускает задачу: - таймеры systemd - добавьте curl в цепочку ExecStart или в ExecStartPost=. Сопоставьте таймер OnCalendar= с соответствующим интервалом heartbeat. - Kubernetes CronJob - последний шаг контейнера пингует heartbeat URL. Если под падает или вообще не планируется (неверный node selector, сбой image pull), пинг отсутствует, и вы получаете оповещение. - Windows Task Scheduler - завершите задачу командой PowerShell Invoke-WebRequest -Uri $env:HEARTBEAT_URL -TimeoutSec 10. - CI / запланированные пайплайны - ночной GitHub Actions или расписание GitLab может последним шагом пингануть heartbeat, подтвердив, что пайплайн запустился и прошёл.

Распространённые антипаттерны

  • Пинг в начале, а не в конце. Heartbeat, отправленный до работы, доказывает лишь то, что задача началась. Поставьте его после работы, с условием успеха (&&), чтобы сбой посреди выполнения тоже задержал пинг.
  • Игнорирование кода выхода. backup.sh; curl ... (точка с запятой) отправляет пинг, даже если бэкап упал. Всегда используйте &&.
  • Молчаливое проглатывание сбоев curl. -fsS заставляет curl громко падать при HTTP-ошибке, так что логи вашей задачи покажут, что heartbeat не удалось доставить (сетевая проблема на машине).
  • Один heartbeat на десять задач. Если пять cron'ов делят один URL, вы не узнаете, какой перестал выполняться. Дайте каждой запланированной задаче собственный heartbeat. Если для того же сервиса вы выставляете и HTTP-эндпоинт, сопоставьте heartbeat с обычным uptime check и проверкой порта на порту воркера, чтобы покрыть и "процесс работает", и "запланированный запуск произошёл".

Конкретный сценарий: тихий сбой бэкапа

Хрестоматийный случай для heartbeat выглядит так. Ночной pg_dump пишет на смонтированный том. Месяцами всё работает. Затем том заполняется, pg_dump завершается с ненулевым кодом, и поскольку строка cron использовала обычную точку с запятой, heartbeat всё равно отправляется, и дашборд остаётся зелёным. Через три недели база данных повреждена, и пригодного бэкапа нет, потому что каждый "успешный" бэкап с момента заполнения диска был файлом нулевого размера. Паттерн heartbeat ловит это уже в первую ночь, но только если он подключён правильно: обусловьте пинг через &&, чтобы ненулевой выход его задержал, и рассмотрите проверку вывода, а не только кода выхода. Скрипт бэкапа может завершиться с кодом 0 и всё равно выдать обрезанный файл. Более защищённая строка проверяет, что дамп нетривиален, ещё до сигнала об успехе:

/usr/local/bin/backup.sh && \
  test "$(stat -c%s /backups/db.sql.gz)" -gt 1000000 && \
  curl -fsS -m 10 https://epulz.io/heartbeat/abc123xyz > /dev/null

Теперь heartbeat приходит только если скрипт отработал успешно и итоговый файл больше 1 МБ. Всё, что меньше, задерживает пинг, и вы получаете оповещение в пределах grace-периода, а не три недели спустя.

Начните мониторить свои cron-задачи

ePulz.io поддерживает heartbeat-проверки с настраиваемым grace-периодом. 7 дней бесплатно.

Запустить мониторинг →

Связанное

Поделиться: Ссылка скопирована

Попробуйте ePulz.io бесплатно - 7 дней без банковской карты.

Создать аккаунт