Job и CronJob: разовые и периодические задачи

Job выполняет задачу до успешного завершения, а CronJob запускает Job по расписанию — это контроллеры для batch-работ, а не для постоянно работающих сервисов.

Job — контроллер, который запускает поды и доводит работу до успешного завершения; CronJob — обёртка, создающая Job по расписанию в стиле cron.

Deployment, StatefulSet и DaemonSet рассчитаны на процессы, которые должны работать всегда: упал — перезапусти. Но огромный класс задач устроен иначе — они должны отработать и завершиться: миграция базы данных, разовая обработка файла, генерация отчёта, отправка рассылки, ночной бэкап. Если запустить такое Deployment-ом, Kubernetes воспримет нормальный выход процесса как сбой и будет перезапускать его бесконечно.

Для завершаемых задач есть Job. Он отслеживает не «сколько копий держать», а «сколько успешных завершений получить». Когда нужное число подов завершилось с кодом 0, Job считается выполненным и больше ничего не запускает.

Job: задача до завершения

Минимальный Job запускает один под и ждёт его успешного выхода. Важная деталь — restartPolicy: для Job допустимы только Never или OnFailure (значение Always запрещено, ведь задача обязана когда-то закончиться).

apiVersion: batch/v1
kind: Job
metadata:
  name: db-migrate
spec:
  backoffLimit: 4          # сколько раз повторять при падении
  template:
    spec:
      restartPolicy: Never
      containers:
        - name: migrate
          image: myapp:1.4
          command: ["python", "manage.py", "migrate"]
kubectl apply -f db-migrate.yaml

# COMPLETIONS показывает 0/1, затем 1/1 после успеха
kubectl get job db-migrate

# Логи завершившегося пода доступны, пока его не очистили
kubectl logs job/db-migrate

Если процесс упал (код выхода не 0), Job создаёт под заново — но не бесконечно, а до backoffLimit попыток (по умолчанию 6), с нарастающей паузой между ними. Исчерпав лимит, Job помечается как Failed.

Параллелизм: completions и parallelism

Job умеет обрабатывать пачку элементов несколькими подами сразу. Два параметра управляют этим:

  • completions — сколько подов должно успешно завершиться, чтобы Job считался выполненным.
  • parallelism — сколько подов запускать одновременно.

Например, обработать 10 порций данных, держа максимум 3 пода в работе:

apiVersion: batch/v1
kind: Job
metadata:
  name: process-batch
spec:
  completions: 10        # всего нужно 10 успешных завершений
  parallelism: 3         # одновременно работают до 3 подов
  template:
    spec:
      restartPolicy: OnFailure
      containers:
        - name: worker
          image: batch-worker:2.0

Kubernetes запустит три пода; как только один завершится, поднимет следующий — и так, пока не наберётся 10 успешных. Это удобный встроенный механизм параллельной batch-обработки без внешнего оркестратора.

CronJob: задачи по расписанию

CronJob создаёт Job по расписанию в формате cron из пяти полей: минута час день-месяца месяц день-недели. Подходит для бэкапов, очистки, периодических отчётов.

apiVersion: batch/v1
kind: CronJob
metadata:
  name: nightly-backup
spec:
  schedule: "0 3 * * *"          # каждый день в 03:00
  successfulJobsHistoryLimit: 3  # хранить 3 успешных Job
  failedJobsHistoryLimit: 1
  jobTemplate:
    spec:
      template:
        spec:
          restartPolicy: OnFailure
          containers:
            - name: backup
              image: backup-tool:1.0
              args: ["dump", "--to", "s3://backups/db"]

Несколько примеров расписаний: */15 * * * * — каждые 15 минут; 0 * * * * — в начале каждого часа; 0 9 * * 1 — по понедельникам в 09:00.

# Колонка LAST SCHEDULE показывает время последнего запуска
kubectl get cronjob nightly-backup

# Запустить вручную, не дожидаясь расписания (удобно для проверки)
kubectl create job --from=cronjob/nightly-backup backup-test

Как это работает под капотом

Контроллер Job следит за подами и считает успешные завершения. Под считается завершённым успешно при коде выхода 0; при ненулевом — это провал, и срабатывает повтор в рамках backoffLimit. Параметр activeDeadlineSeconds ограничивает общее время Job: превысил — поды убиваются, Job помечается как проваленный. Завершённые поды по умолчанию не удаляются сразу, чтобы можно было посмотреть логи; навести порядок помогает ttlSecondsAfterFinished, который удаляет Job и его поды через заданное время.

CronJob по расписанию просто создаёт объекты Job. Ключевой нюанс — перекрытие запусков: что делать, если предыдущий Job ещё работает, а пора запускать следующий? Это решает concurrencyPolicy:

ЗначениеПоведение при перекрытии
Allow (по умолчанию)Разрешить параллельные запуски — новый Job стартует, даже если старый ещё идёт
ForbidПропустить новый запуск, пока не завершится предыдущий
ReplaceОтменить текущий Job и заменить его новым

Для бэкапов и тяжёлых задач почти всегда нужен Forbid: два одновременных дампа одной БД могут навредить. Ещё один параметр — startingDeadlineSeconds: если контроллер по какой-то причине пропустил время запуска (например, кластер был недоступен), он решает, запускать ли пропущенный Job задним числом или нет.

Частые ошибки

  • restartPolicy: Always в Job. Запрещено и не пройдёт валидацию: задача обязана завершаться, поэтому допустимы только Never и OnFailure.
  • Запуск разовой задачи через Deployment. Deployment перезапустит «упавший» (на деле успешно завершившийся) под бесконечно — для миграций и батчей нужен Job.
  • concurrencyPolicy: Allow для тяжёлых задач. Если Job идёт дольше интервала расписания, запуски наложатся друг на друга и перегрузят систему. Ставьте Forbid или Replace.
  • Накопление завершённых Job. Без ttlSecondsAfterFinished и лимитов истории старые Job и их поды копятся, засоряя кластер. Задавайте successfulJobsHistoryLimit и TTL.
  • Путаница completions и parallelism. completions — сколько успехов нужно всего, parallelism — сколько подов одновременно. Это разные оси, и их легко перепутать.

Итоги

  • Job доводит задачу до успешного завершения и не перезапускает её бесконечно, в отличие от Deployment.
  • completions задаёт нужное число успешных завершений, parallelism — число одновременно работающих подов.
  • restartPolicy в Job — только Never или OnFailure; повторы при сбое ограничены backoffLimit.
  • CronJob создаёт Job по cron-расписанию; перекрытие запусков регулирует concurrencyPolicy (Allow/Forbid/Replace).
  • Для уборки используйте ttlSecondsAfterFinished и лимиты истории Job.
Проверьте себя
1. Чем Job отличается от Deployment?
AJob держит заданное число реплик постоянно работающими
BJob доводит задачу до успешного завершения и затем останавливается
CJob можно запускать только на control-plane узлах
DJob не умеет запускать несколько подов параллельно
2. Что задаёт параметр parallelism в Job?
AСколько всего успешных завершений нужно для выполнения Job
BСколько подов запускать одновременно
CСколько раз повторять под при падении
DЧерез сколько секунд удалить завершённый Job
3. Какая concurrencyPolicy у CronJob пропустит новый запуск, пока не завершится предыдущий Job?
AAllow
BForbid
CReplace
DSuspend