Kubernetes CronJob: почему задачи не запускаются по расписанию
CronJob выглядит самой простой сущностью в кластере: расписание, образ, команда — дальше среда сама поднимает задание. Именно поэтому сбой расписания замечают с задержкой, когда в отчётах всплывает пропущенный запуск задания или дублирование регламентных задач: одна и та же выгрузка, сверка или рассылка отрабатывает дважды.
Планировщик не будит задание точно по расписанию — он сверяет время в цикле. Если управляющий контур перезапускался, узел был недоступен или расписание задано чаще шага сверки, окно запуска проходит мимо. Параметр startingDeadlineSeconds решает, сколько опоздание ещё допустимо, а concurrencyPolicy: Forbid не даёт новому экземпляру стартовать поверх работающего.
Дублирование чаще всего идёт от политики Allow и длинной задачи: следующий запуск наступает, когда предыдущий ещё пишет в ту же таблицу. Тот же эффект дают несколько реплик управляющего контура без выбранного лидера и ручные «догоны» после инцидента.
Зависшие процессы в кластере — отдельный класс. Без activeDeadlineSeconds и лимита повторов задание может висеть сутками: экземпляр не выходит из Pending из-за квот, не поднимается образ, контейнер не завершается по сигналу. Такой процесс держит ресурсы и блокирует следующий по политике запрета.
Наконец, потерянные результаты: успешный экземпляр удаляется, логи уходят вместе с ним, а история хранит лишь несколько последних запусков. Отсюда картина «задание прошло, но результата нет». Разбираем, как каждый класс сбоев закрывается конфигурацией, а не дежурством.
Управляемые задания по расписанию: эффект для команды
Регламентные задания перестают быть точкой риска, когда расписание, лимиты и политика перезапуска описаны в манифестах Kubernetes, а не восстанавливаются вручную после каждого сбоя. Эффект измерим: доля потерянных запусков, время восстановления и число ручных вмешательств дежурного.
Сокращение ручных перезапусков
Ограничение времени выполнения и политика повторов не дают задаче зависнуть и заблокировать следующее окно. Причину падения видно в истории запусков, поэтому типовой сбой закрывается правкой манифеста, а не перезапуском «на удачу». Число ручных разборов сокращается с нескольких в неделю до единичных за квартал.
Предсказуемая нагрузка на кластер
Расписания разведены по времени, для заданий заданы запросы и лимиты ресурсов, параллельные запуски ограничены. Пики перестают попадать в рабочие часы: ночные задания не конкурируют с сервисами, и кластер не уходит в очередь на обслуживание.
Быстрое восстановление после сбоя
Возврат в работу — откат манифеста и повторный прогон пропущенного окна, а не восстановление из резервной копии. Время восстановления падает с десятков минут до единиц: порядок действий известен заранее и не зависит от того, кто на дежурстве.
Пропущенное окно перестаёт быть незаметным — расхождение видно в мониторинге в тот же день. Расписание становится воспроизводимым артефактом: его можно передать другой команде или развернуть в новом окружении без устных инструкций.
Kubernetes CronJob: варианты запуска и их ограничения
Регламентная задача в кластере редко бывает одна: выгрузка отчёта, ротация ключей, чистка устаревших записей, пересчёт агрегатов. CronJob закрывает расписание, но не отвечает на два вопроса — сколько копий задания допустимо одновременно и что делать, если предыдущий проход ещё не завершился. Отсюда четыре режима, между которыми реально выбирают.
Дело не в том, «какой инструмент лучше», а в том, где живёт блокировка и кто отвечает за пропущенный запуск. Границы применимости каждого варианта:
| Вариант | Когда подходит | Ограничения |
|---|---|---|
| Одиночный запуск — задание с запретом параллельных копий | Идемпотентная задача, укладывается в интервал расписания | Долгий проход вытесняет следующий; пропущенный слот не переносится |
| Распределённый запуск с блокировкой | Несколько реплик или кластеров претендуют на одну работу | Нужен внешний координатор и аренда блокировки с таймаутом, иначе при отказе держателя задача встаёт |
| Запуск вне кластера | Тяжёлая задача с длинным окном и доступом к внешним системам | Нет истории запусков и повторов средствами кластера; мониторинг и секреты собираются отдельно |
| Штатный планировщик без блокировки | Лёгкие периодические задачи внутри кластера | Разрешение расписания — минуты; пропуски отсекаются по startingDeadlineSeconds, часовой пояс задаётся явно |
На практике режимы смешивают: расписание и запуск оставляют планировщику, а блокировку и защиту от наложения выносят в сам обработчик — тогда перенос задачи за пределы кластера не ломает логику. Следить стоит не за фактом «под отработал», а за результатом: метрикой успешных проходов, временем последнего завершения и числом пропущенных слотов.
Порядок внедрения: от аудита расписаний до передачи
Внедрение начинается не с манифестов, а с инвентаризации: cron на серверах, таймеры, планировщик в системе сборки и уже живущие задания в кластере. Уже здесь видно, какие из них конкурируют за ресурсы, где расписания перекрываются по времени и какие падения никто не замечал месяцами.
- Аудит текущих расписаний. Сводим задания в таблицу: периодичность, фактическая длительность, зависимости по данным, владелец. Помечаем дубли и те, что запускаются чаще, чем нужно.
- Схема запуска заданий. Для каждого фиксируем политику параллелизма, предельное время выполнения, лимиты ресурсов и глубину хранения истории завершённых запусков.
- Настройка повторных попыток. Задаём число повторов, задержку между ними и политику перезапуска контейнера; отдельно решаем, что делать с заданием, не уложившимся в отведённое окно.
- Проверка отказоустойчивости. Роняем pod, отключаем внешнюю зависимость, забиваем узел нагрузкой — задание должно перезапуститься, не создать дублей и не заблокировать следующее по расписанию.
- Наблюдаемость. Настраиваем метрики успешных и упавших запусков, оповещение о пропущенном окне и журнал, по которому причина сбоя видна без ручного разбора.
- Передача сопровождения. Отдаём схему расписаний, конфигурации, инструкции по типовым сбоям и порядок внесения изменений.
Каждый этап заканчивается проверяемым артефактом: обновлённым расписанием, конфигурацией, отчётом о прогоне проверок на отказ. Так снимается главный риск — когда задания живут сами по себе и о сбое узнаёшь от смежной команды, а не от оповещения.
Комплект артефактов: манифесты, политики, регламенты
Работы заканчиваются не «настроенным кластером», а комплектом, который можно передать другой команде и воспроизвести с нуля. Всё лежит в репозитории с историей изменений: манифесты применяются декларативно, отличия между контурами описаны явно, а не правятся руками на рабочем окружении.
- Описание конфигурации заданий — манифесты с явными
concurrencyPolicy,backoffLimit, лимитами ресурсов и политикой хранения завершённых подов; отдельно зафиксировано, почему выбраны именно такие значения. - Политики доступа и роли — сервисные учётные записи под каждую задачу, права в границах пространства имён, сетевые политики, секреты из внешнего хранилища вместо значений в манифестах.
- Схема окружений — разделение контуров разработки, предпродакшна и рабочего, переопределения значений по среде, порядок продвижения изменений и отката.
- Мониторинг и оповещения — метрики запусков и их длительности, правила оповещения о пропущенном расписании, повторных отказах и зависших заданиях.
- Регламент обработки сбоев — что делает дежурный при падении задания, как приостановить расписание через
suspendи перезапустить пропущенный запуск без дублей. - Документация сопровождения — назначение каждого расписания, зависимости от внешних систем, типовые операции и точки, где требуется вмешательство человека.
Комплект передаём вместе с разбором: инженер заказчика проходит по нему и повторяет развёртывание на тестовом контуре, после чего сопровождение можно вести своими силами.
Практика: как сократили долю потерянных запусков
Клиент пришёл с платформенной командой, у которой по расписанию работало около 120 задач: ночные выгрузки, сверки остатков, пересчёт условий, рассылки. Часть запусков пропадала — и узнавали об этом по жалобам бизнеса, а не по сигналу мониторинга.
Что показывали метрики до
Доля потерянных запусков держалась на уровне 8–9% в сутки. Примерно половина из них — тихие пропуски: задача не стартовала или не завершилась, но ни один алерт не сработал. Среднее время реакции на сбой составляло несколько часов, потому что единственным сигналом был звонок из отдела, который ждал отчёт.
Что изменили
Свели расписания в один контур управления на Kubernetes: задали startingDeadlineSeconds, чтобы слишком поздно поднятая задача не накладывалась на следующее окно, concurrencyPolicy: Forbid для пересекающихся заданий и backoffLimit с activeDeadlineSeconds вместо бесконечных повторов. Алерт поставили не на падение, а на отсутствие успешного завершения за окно, плюс TTL для истории завершённых заданий. Сами задачи сделали идемпотентными — повтор после сбоя больше не дублировал списания и отправки.
Что стало
Доля потерянных запусков снизилась с 8–9% до 0,2–0,4%, время реакции на сбой — с нескольких часов до 1–2 минут. Класс отказов «пропущенный запуск, о котором никто не узнал» закрыт: сам пропуск окна стал событием контроля выполнения задач.
Чем задания по расписанию отличаются от рабочих очередей?
Граница проходит по источнику истины. Задание по расписанию запускает время: планировщик сам решает, что момент наступил, и стартует процесс, не проверяя, есть ли для него работа. Очередь запускает событие: пока новая задача не пришла, обработчик ждёт и не занимает ресурсы.
Отсюда и разные классы отказов. У расписания — перекрытия и пропуски: два запуска одного пересчёта накладываются друг на друга, долгий прогон съедает следующий, а остановка планировщика остаётся незамеченной. Закрывается это политикой одновременности, допустимым окном опоздания и хранением истории запусков, по которой видно и успех, и падение. У очереди — повторная доставка и «зависшие» задачи, которые упали и не вернулись в работу без отдельной проверки.
И там, и там обязательна идемпотентность операций. Повторный ночной пересчёт или повторно доставленное событие не должны списать дважды, отправить второе письмо или создать дубль заказа — ключ идемпотентности и сверка с текущим состоянием в базе решают это независимо от способа запуска.
Выбор простой: регламент без внешнего триггера — отчёты, синхронизация справочников, чистка и архивация — идёт по расписанию. Поток событий — заказы, вебхуки, файлы — идёт через очередь, где число обработчиков растёт вместе с длиной очереди. Рабочий компромисс — гибрид: задание в Kubernetes не выполняет работу само, а только наполняет очередь, а обработку и повторы ведут отдельные исполнители.
Технические вопросы, которые задают перед стартом
Перед стартом почти всегда всплывают одни и те же сомнения: как расписание переживёт обновление кластера, что делать с затянувшейся задачей и откуда берутся двойные запуски. Ниже — четыре вопроса, которые чаще всего влияют на схему размещения.
Задание может запуститься дважды — как это исключить?
Дубли появляются, когда предыдущий запуск не успел закончиться к следующему. Поведение задаёт политика одновременности: Forbid пропускает новый запуск, Replace останавливает текущий и стартует свежий. Для выгрузок и записи в одну таблицу ставим Forbid.
Что делать, если задание зависло?
Зависание ловим таймаутом самого задания: activeDeadlineSeconds снимает под принудительно, backoffLimit ограничивает повторы, ttlSecondsAfterFinished убирает отработавшие объекты. Дополнительно следим за временем последнего успешного запуска — оно растёт раньше, чем сработает таймаут.
Переживёт ли задание обновление кластера?
При обновлении узлы выводятся из работы и под прерывается. Короткие задачи переживают это за счёт повторов, длинные переносим в окна, свободные от обслуживания, и делаем идемпотентными, чтобы повтор не портил данные. Описание задания держим в актуальной версии — иначе обновлённый кластер его не примет.
Как ограничить время выполнения?
Ограничение по времени не даёт задаче висеть вечно и уходить внахлёст со следующим запуском. Если работа не укладывается в интервал расписания, выносим её в отдельный контур или удлиняем период — иначе часть запусков будет пропускаться.
Обсудим задачу: разберём расписания и точки отказа
Чтобы вернуться с оценкой и планом работ по расписаниям, нам нужен срез окружения, а не формулировка «периодические задачи иногда не срабатывают». Опишите задачу — сверим её со схемой и предложим конфигурацию, которую можно внедрять без остановки сервисов.
Что прислать как вводные для оценки:
- схему текущего окружения: кластеры и контуры, чем отличаются параметры запуска между ними;
- перечень периодических задач: расписание, типичная длительность, зависимости между шагами, допустимое окно задержки;
- как запуск устроен сейчас: планировщик внутри приложения, системный таймер или ручной скрипт;
- требования к поведению после сбоя: что произойдёт при двойном выполнении одной и той же задачи;
- сколько хранить историю успешных и неуспешных попыток и нужен ли откат на предыдущую сборку;
- действующие правила оповещения и известные точки отказа: куда приходят сигналы и кто читает их вне рабочего времени;
- ограничения контура: сетевые политики, доступы к внешним ресурсам, окна обслуживания.
На выходе вы получите карту задач с оценкой риска, план работ по расписаниям по этапам и набор проверок, доказывающих, что запуск действительно прошёл и результат записан. Пришлите вводные — вернёмся с оценкой и планом.







