Мониторинг cron-задач: паттерн heartbeat на практике
· 6 мин чтения
HTTP-мониторинг не сообщит, что ночной cron упал. Паттерн heartbeat переворачивает направление: cron сам отчитывается мониторингу, и если он не позвонит вовремя, вы получите оповещение.
Проблема: фоновые задачи без HTTP-эндпоинта
У типичного бэкенда несколько путей:
- Веб-запросы (HTTP/HTTPS к серверу) - вы мониторите их через uptime check.
- Cron-задачи (ежедневный бэкап, ежемесячный биллинг, ежечасная синхронизация) - у них нет HTTP-эндпоинта, внешний мониторинг не может за ними следить.
- Воркеры (Celery, BullMQ, Sidekiq), обрабатывающие очередь, - тоже без HTTP.
Когда cron падает (опечатка в crontab, заполненный диск, отсутствующая переменная окружения (environment variable) или ошибка импорта после обновления зависимости (dependency upgrade)), вас никто не оповестит. Вы узнаёте об этом в понедельник утром, заметив, что за выходные не ушло ни одного счёта.
Паттерн heartbeat: cron пингует мониторинг
Принцип обратен обычному мониторингу:
- В сервисе мониторинга вы создаёте heartbeat-монитор с ожидаемым интервалом (например, "каждые 60 минут").
- Вы получаете уникальный heartbeat URL:
https://epulz.io/heartbeat/abc123xyz. - В своей cron-задаче в конце успешного выполнения вы вызываете этот URL (HTTP GET или POST).
- Если пинг не приходит в ожидаемое время (плюс 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 дней без банковской карты.
Создать аккаунт