Мониторинг CronJob в Kubernetes
CronJob в Kubernetes может перестать делать свою работу, не оставив ни одного упавшего пода. Алерты на уровне подов не видят задачу, которая так и не запустилась, поэтому надёжный сигнал устроен наоборот: задача сообщает о каждом успешном запуске, а тишина означает, что что-то не так.
Как CronJob ломается молча
- Слишком много пропущенных запусков. Если контроллер какое-то время не мог запустить задачу, например во время сбоя или обновления control plane, и насчитал больше 100 пропущенных запусков, он перестаёт её запускать и только пишет ошибку в лог.
startingDeadlineSecondsограничивает, как далеко назад он считает. - Зависший запуск блокирует следующие. С
concurrencyPolicy: Forbidновый запуск пропускается, пока предыдущий ещё активен. Один застрявший под останавливает все будущие запуски. - Забытый
suspend: trueпосле работ на кластере. - Поды, которые не стартуют: образ не скачивается или не хватает ресурсов, чтобы разместить под.
- Следы исчезают. По умолчанию хранятся только 3 последних успешных задачи и одна упавшая, так что сбой недельной давности уже не найти.
- Не тот часовой пояс. Без
spec.timeZone(стабильно с Kubernetes 1.27) расписание идёт по часовому поясу controller manager, обычно это UTC.
CronJob, который отчитывается
apiVersion: batch/v1
kind: CronJob
metadata:
name: nightly-report
spec:
schedule: "17 3 * * *"
timeZone: "Europe/Moscow"
concurrencyPolicy: Forbid
startingDeadlineSeconds: 600
failedJobsHistoryLimit: 3
jobTemplate:
spec:
backoffLimit: 2
activeDeadlineSeconds: 3600
template:
spec:
restartPolicy: Never
containers:
- name: report
image: registry.example.com/report:1.4.2
env:
- name: PING_URL
valueFrom:
secretKeyRef:
name: heartcron
key: nightly-report
command: ["/bin/sh", "-c"]
args:
- |
wget -q -T 10 -O /dev/null "$PING_URL/start" || true
/app/report
code=$?
wget -q -T 10 -O /dev/null "$PING_URL/$code"
exit $code
Что делает каждая часть:
activeDeadlineSecondsубивает запуск дольше часа, так что зависший под не заблокирует расписание навсегда.- С
startingDeadlineSeconds: 600контроллер считает пропуски только за последние 10 минут, так что до лимита в 100 дело не дойдёт; запуск, который не успел стартовать, просто пропускается. - Скрипт передаёт код выхода задачи:
0отмечает проверку как работающую, всё остальное сразу отправляет алерт.exit $codeсохраняет статус самой задачи, и Kubernetes по-прежнему повторяет сбои поbackoffLimit. /startпозволяет Heartcron заметить запуск, который начался, но не закончился.
Храните адрес пинга в секрете, а не в манифесте:
kubectl create secret generic heartcron \ --from-literal=nightly-report=https://ping.heartcron.com/<uuid>
Настройте проверку
Используйте то же cron-выражение и часовой пояс, что в CronJob. Запас времени должен покрывать дедлайн старта и обычную длительность: при дедлайне 10 минут и задаче на 15 минут подойдут 30–40 минут. Если запуск не начался, закончился ошибкой или завис дольше запаса, придёт алерт в Telegram, Slack, Discord или на почту.
Когда пришёл алерт
kubectl get cronjob nightly-report # SUSPEND и LAST SCHEDULE kubectl get jobs --sort-by=.metadata.creationTimestamp kubectl describe cronjob nightly-report # события, включая пропущенные запуски kubectl logs job/<job-name>