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

Рейтинг: 64.6% · 12 голосов
Kubernetes для разработчиков: поды, деплойменты, сервисы, ingress, конфиги и отладка. Уроки по главам с обсуждением.
Ответить
Аватара пользователя
anton_k8s
Сообщения: 26
Зарегистрирован: 12 май 2026, 03:23

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

Сообщение anton_k8s »

АкадемияKubernetes на практикеГлава 14 из 19
Оглавление курса (19)
  1. Зачем нужен Kubernetes и из чего состоит кластер
  2. Поднимаем локальный кластер: minikube и kind
  3. Поды: базовая единица запуска
  4. Deployment и ReplicaSet: управляем репликами
  5. Service: сетевой доступ к подам
  6. ConfigMap и Secret: выносим конфигурацию
  7. Ingress: пускаем трафик снаружи
  8. Хранилище: Volumes и PersistentVolumeClaim
  9. Namespaces, requests и limits
  10. Health checks: liveness и readiness пробы
  11. Отладка: почему под не стартует
  12. Helm: пакетный менеджер для Kubernetes
  13. Базовая безопасность: RBAC и доступы
  14. Job и CronJob: разовые и периодические задачи (вы здесь)
  15. StatefulSet и DaemonSet: stateful-нагрузки и системные агенты
  16. Стратегии обновления и планирование: rollout и rollback, graceful shutdown, nodeSelector, affinity, taints
  17. Автомасштабирование: HPA по метрикам, обзор VPA и Cluster Autoscaler
  18. Наблюдаемость: логи, метрики, events, обзор Prometheus и Grafana
  19. Безопасность глубже: securityContext, Pod Security Standards, NetworkPolicy, шифрование секретов
Deployment следит, чтобы процесс жил вечно. Но половина задач в проде устроена иначе: миграция схемы базы, ночной бэкап, пересчёт отчёта, прогрев кэша. Такой процесс должен отработать, вернуть код 0 и исчезнуть. Для разовых запусков в Kubernetes есть Job, для периодических CronJob, и у обоих хватает неочевидных настроек.

Job, запустить и дождаться конца:

Job создаёт под и следит за его завершением. Код выхода 0 означает успех, любой другой означает провал, и контроллер повторяет запуск, пока не исчерпает backoffLimit (по умолчанию 6). Повторы идут с экспоненциальной задержкой: 10, 20, 40 секунд и дальше, с потолком в 6 минут.

Классический пример, миграция базы перед выкаткой новой версии:

Код: Выделить всё

apiVersion: batch/v1
kind: Job
metadata:
  name: migrate-2026-06
spec:
  backoffLimit: 3
  activeDeadlineSeconds: 600
  ttlSecondsAfterFinished: 86400
  template:
    spec:
      restartPolicy: Never
      containers:
      - name: migrate
        image: registry.infra.local/shop/backend:1.42.0
        command: ["python", "manage.py", "migrate", "--noinput"]
        envFrom:
        - secretRef:
            name: db-credentials
Три поля, без которых Job в проде запускать не надо. restartPolicy обязан быть Never или OnFailure, манифест с Always API не примет. Разница: при Never контроллер на каждый провал создаёт новый под, при OnFailure kubelet перезапускает контейнер в том же поде. Бюджет backoffLimit тратят оба варианта, но с Never поды упавших попыток остаются, и их логи можно читать. activeDeadlineSeconds убивает весь Job по таймауту, даже если попытки ещё есть. ttlSecondsAfterFinished автоматически удаляет завершённый Job вместе с подами, иначе они копятся месяцами.

Запускаем и ждём:

Код: Выделить всё

kubectl apply -f migrate-job.yaml
kubectl wait --for=condition=complete job/migrate-2026-06 --timeout=10m
kubectl logs job/migrate-2026-06
Нюанс: spec.template у созданного Job неизменяем. Упала миграция из-за опечатки в команде, значит удаляем Job и создаём заново, на лету не починить.

Параллельная обработка:

Когда работу можно порезать на куски, помогают completions и parallelism:

Код: Выделить всё

spec:
  completions: 10
  parallelism: 3
  completionMode: Indexed
Контроллер держит не больше трёх подов одновременно, пока не наберётся десять успешных завершений. В режиме Indexed каждый под получает переменную окружения JOB_COMPLETION_INDEX от 0 до 9 и берёт свой кусок данных. Упавший индекс перезапускается отдельно от остальных.

CronJob, то же самое по расписанию:

CronJob сам поды не запускает, он по расписанию штампует объекты Job. Ночной дамп PostgreSQL в S3-совместимое хранилище:

Код: Выделить всё

apiVersion: batch/v1
kind: CronJob
metadata:
  name: pg-backup
spec:
  schedule: "30 2 * * *"
  timeZone: "Europe/Moscow"
  concurrencyPolicy: Forbid
  startingDeadlineSeconds: 300
  successfulJobsHistoryLimit: 3
  failedJobsHistoryLimit: 5
  jobTemplate:
    spec:
      backoffLimit: 2
      activeDeadlineSeconds: 3600
      ttlSecondsAfterFinished: 259200
      template:
        spec:
          restartPolicy: Never
          containers:
          - name: backup
            image: registry.infra.local/ops/pg-backup:16.6
            args: ["--bucket", "backups-prod", "--prefix", "pg/daily"]
            envFrom:
            - secretRef:
                name: backup-s3-creds
Разбор по полям. schedule пишется в обычном cron-формате, минимальный шаг одна минута, секунд там нет. timeZone задаёт пояс явно. Без него расписание считается в часовом поясе kube-controller-manager, обычно это UTC, и задуманный на половину третьего ночи бэкап стартует в 5:30 утра по Москве. concurrencyPolicy: Forbid не даёт начать новый запуск, пока жив предыдущий (Replace убивает старый, Allow по умолчанию разрешает наложение). startingDeadlineSeconds даёт 5 минут на опоздание: не успел контроллер создать Job в это окно, запуск пропускается.

Ждать расписания для проверки не обязательно:

Код: Выделить всё

kubectl create job pg-backup-manual --from=cronjob/pg-backup
А spec.suspend: true ставит CronJob на паузу, удобно на время работ с базой.

Типичные грабли:

- Семантика у Job "хотя бы один раз", а не "ровно один раз". При сбое узла или рестарте контроллера задача может выполниться повторно. Пишите задачи идемпотентными: миграции с локом в базе, бэкапы с уникальными именами файлов.
- Allow по умолчанию плюс задача длиннее интервала равно лавина параллельных Job, которые съедают ноды. Всему тяжёлому ставьте Forbid.
- Sidecar-контейнеры. Если в под инжектится прокси сервис-меша, Job не завершится: основной контейнер отработал, а прокси живёт. Лечится нативными сайдкарами (initContainer с restartPolicy: Always), они стабильны с Kubernetes 1.33 и гасятся автоматически вслед за основным контейнером.
- Пропустил CronJob больше 100 запусков подряд при незаданном startingDeadlineSeconds, и контроллер перестаёт создавать новые Job, только шлёт event. Классика: сняли suspend после долгой паузы и удивляются тишине.
- Без ttlSecondsAfterFinished и history-лимитов сотни мёртвых подов замусоривают etcd и вывод kubectl get pods.

Итог:

Job доводит задачу до успешного завершения с бюджетом попыток и таймаутом, CronJob по расписанию создаёт Job и через concurrencyPolicy управляет наложением запусков. Три слова, которые надо унести из главы: идемпотентность, Forbid, timeZone. Дальше посмотрим на StatefulSet и DaemonSet, то есть на нагрузки, которым нужны стабильные имена и присутствие на каждом узле.
👍2 ❤️3 🔥3 😄 🤔1
✔ Лучший ответ сформирован автоматически — kube87
anton_k8s писал(а):Без него расписание считается в часовом поясе kube-controller-manager, обычно это UTC прочувствовано на своей шкуре. полгода бэкапы гонялись в 5:30 утра вместо половины третьего, заметили только когда они начали пересекаться с утренним трафиком и база стала тупить. теперь timeZone в каждом кронджобе, без него ревью не пропускаю
Перейти к ответу →
Аватара пользователя
kube87
Сообщения: 1
Зарегистрирован: 16 май 2026, 13:56

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

Сообщение kube87 »

✔ Лучший ответ — сформирован автоматически
anton_k8s писал(а):Без него расписание считается в часовом поясе kube-controller-manager, обычно это UTC
прочувствовано на своей шкуре. полгода бэкапы гонялись в 5:30 утра вместо половины третьего, заметили только когда они начали пересекаться с утренним трафиком и база стала тупить. теперь timeZone в каждом кронджобе, без него ревью не пропускаю
👍2 ❤️ 🔥 😄 🤔1
Аватара пользователя
deno777
Сообщения: 1
Зарегистрирован: 26 май 2026, 18:04

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

Сообщение deno777 »

а чем Job принципиально лучше голого пода с restartPolicy: OnFailure? под же и так сам перезапустится при падении. весь смысл только в backoffLimit и ttl?
👍1 ❤️ 🔥 😄 🤔
Аватара пользователя
Mcvay
Сообщения: 1
Зарегистрирован: 24 май 2026, 19:20

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

Сообщение Mcvay »

выше про голый под спрашивали. под прибит к своей ноде: нода умерла или ушла в дрейн, и никто ничего не пересоздаст. Job потерю ноды переживает и доводит дело до конца. плюс в CI удобно, kubectl wait --for=condition=complete и пайплайн честно ждёт миграцию, с подом так нормально не сделать
👍 ❤️ 🔥2 😄 🤔
Аватара пользователя
scotc
Сообщения: 2
Зарегистрирован: 17 май 2026, 07:20

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

Сообщение scotc »

подтверждаю боль с сайдкарами. джобы с istio-proxy висели в Running вечно, дежурные прибивали их руками по алерту. переехали на нативные сайдкары и проблема ушла, правда ради этого пришлось тащить кластер с 1.28 на свежую версию
👍1 ❤️2 🔥1 😄 🤔
Ответить
← Предыдущая глава
Базовая безопасность: RBAC и доступы
Следующая глава →
StatefulSet и DaemonSet: stateful-нагрузки и системные агенты

Все главы курса «Kubernetes на практике»

Поделиться темой: ✈ Telegram VK

Вернуться в «Kubernetes на практике»

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и 0 гостей