Введение
Deployment поддерживает сервисы в непрерывно работающем состоянии. Для пакетных задач условие успеха иное: выполнить работу до конца, сохранить результат и остановиться. Kubernetes моделирует разовые задачи с помощью Jobs, а повторяющиеся запуски по расписанию — с помощью CronJobs.
В этой лабораторной работе вы запустите Job, изучите его Pod и журналы, добавите управляемый параллелизм, настроите CronJob, а также узнаете, как приостановка и ограничения истории помогают поддерживать регулярные задачи в рабочем состоянии.
Рассматривайте эту лабораторную работу как сравнение условий успешного выполнения. Deployment считается успешным, если нужное количество реплик остаётся доступным. Job считается успешным, когда необходимое число Pods завершается с кодом выхода 0. CronJob считается успешным, если создаёт Jobs в запланированное время. Разделение этих трёх уровней значительно упрощает чтение YAML.
Запуск разовой Job
На этом шаге вы создадите Job, Pod которой выведет отчёт и успешно завершится. Манифест будет создан с помощью here-документа; заключённый в кавычки EOF не позволяет оболочке подставлять значения и изменять символы внутри YAML.
Перед вводом YAML обратите внимание на его структуру: kind: Job выбирает контроллер, spec.template описывает создаваемый им Pod, а сам Pod содержит один краткоживущий контейнер. В отличие от шаблона Deployment, Pod для Job должен использовать restartPolicy: Never или OnFailure, поскольку успешная задача должна завершиться, а не продолжать работу.
cd /home/labex/project/batch-workloads
cat <<'EOF' > report-job.yaml
apiVersion: batch/v1
kind: Job
metadata:
name: report-job
namespace: practice-batch
spec:
backoffLimit: 2
template:
metadata:
labels:
app: report-job
spec:
restartPolicy: Never
containers:
- name: reporter
image: busybox:1.36
imagePullPolicy: IfNotPresent
command: ["sh", "-c", "echo report-complete"]
EOF
Сначала попросите Kubernetes локально проверить файл. Параметр --dry-run=client проверяет структуру объекта, но ничего не создаёт. Это полезная привычка при подготовке нового манифеста:
kubectl apply --dry-run=client -f report-job.yaml
Если результатом будет job.batch/report-job created (dry run), структура файла корректна. Теперь примените манифест и дождитесь условия Complete, вместо того чтобы приблизительно оценивать время выполнения:
kubectl apply -f report-job.yaml
kubectl wait --for=condition=Complete job/report-job -n practice-batch --timeout=60s
Параметр restartPolicy: Never позволяет контроллеру Job самостоятельно решать, требуется ли создать ещё один Pod. backoffLimit: 2 ограничивает число повторных попыток после ошибок. Выведите результат работы завершившегося Pod через контроллер:
kubectl logs job/report-job -n practice-batch
Вы должны увидеть report-complete. Обращение к job/report-job позволяет kubectl самостоятельно найти Pod этой Job, поэтому не нужно копировать сгенерированное имя Pod.
Эти три команды формируют короткий и понятный жизненный цикл:
job.batch/report-job created
job.batch/report-job condition met
report-complete
Сообщение condition met означает, что контроллер Job зафиксировал завершение. Следующая строка содержит вывод приложения. В только что запущенном локальном кластере даже такая небольшая Job может выполняться несколько секунд, пока настраивается сеть. Ожидание конкретного условия делает процесс надёжным и избавляет от необходимости подбирать длительность sleep.
Просмотр владельца и состояния завершения Job
На этом шаге вы сравните завершившуюся Job с постоянно работающим Deployment. Завершившаяся Job сохраняет свой Pod, поэтому журналы и статус завершения остаются доступными для просмотра.
Начните с обычного табличного представления. У Job должно отображаться одно успешное завершение, а фаза её Pod будет Completed или Succeeded — в зависимости от выбранного столбца:
kubectl get jobs,pods -n practice-batch
kubectl get pods -n practice-batch -l job-name=report-job -o custom-columns='POD:.metadata.name,PHASE:.status.phase,OWNER:.metadata.ownerReferences[0].name,EXIT_CODE:.status.containerStatuses[0].state.terminated.exitCode'
kubectl describe job report-job -n practice-batch
Фаза Pod должна быть Succeeded, его владельцем должен быть report-job, а код выхода — 0. Job считается завершённой, когда необходимое количество Pods успешно завершилось; успешный контейнер не перезапускается только ради поддержания его работы.
Запрос с пользовательскими столбцами преобразует четыре вложенных поля в небольшую таблицу с подтверждающими данными. Это удобнее, чем просматривать весь YAML Pod, если вас интересует конкретный вопрос: «кто владеет этим Pod и успешно ли завершился его процесс?»
Проверенный результат выглядел так:
POD PHASE OWNER EXIT_CODE
report-job-cd54c Succeeded report-job 0
Сгенерированный суффикс у вас будет другим. Прочитайте строку слева направо: OWNER=report-job связывает Pod с его контроллером, а сочетание PHASE=Succeeded и EXIT_CODE=0 подтверждает успешное завершение процесса.
Параллельное выполнение нескольких завершений
На этом шаге вы создадите Job, для которой требуется четыре успешно завершившихся Pod, при этом одновременно разрешено запускать только два.
Перед созданием манифеста важно понять два независимых параметра:
completions: 4означает, что всего необходимо выполнить четыре единицы работы.parallelism: 2означает, что Kubernetes может одновременно выполнять не более двух из них.
Параметр completionMode: Indexed присваивает единицам работы индексы от 0 до 3. Каждый Pod получает собственный индекс через переменную окружения JOB_COMPLETION_INDEX. Это позволяет реальным пакетным приложениям разделять набор данных, чтобы каждый Pod обрабатывал свою часть, а не весь набор целиком.
cat <<'EOF' > indexed-job.yaml
apiVersion: batch/v1
kind: Job
metadata:
name: indexed-job
namespace: practice-batch
spec:
completionMode: Indexed
completions: 4
parallelism: 2
backoffLimitPerIndex: 1
template:
spec:
restartPolicy: Never
containers:
- name: worker
image: busybox:1.36
imagePullPolicy: IfNotPresent
command: ["sh", "-c", "echo worker-index=${JOB_COMPLETION_INDEX}; sleep 2"]
EOF
Примените манифест и дождитесь завершения всех четырёх индексов:
В последней команде используется -L, чтобы добавить в таблицу значение метки batch.kubernetes.io/job-completion-index в качестве дополнительного столбца. Так можно увидеть, какой индекс работы соответствует каждому созданному Pod, не открывая полный YAML.
kubectl apply -f indexed-job.yaml
kubectl wait --for=condition=Complete job/indexed-job -n practice-batch --timeout=90s
kubectl get pods -n practice-batch -l job-name=indexed-job -L batch.kubernetes.io/job-completion-index
completions задаёт общее количество успешно выполненных единиц работы, а parallelism — максимальное число одновременно работающих Pods. Индексированный режим завершения предоставляет каждому Pod стабильный индекс через JOB_COMPLETION_INDEX.
В итоговой таблице должны присутствовать четыре завершившихся Pod с разными индексами. Они могут отображаться не в числовом порядке: выполнение происходит параллельно, поэтому порядок завершения не определён.
Например:
NAME STATUS JOB-COMPLETION-INDEX
indexed-job-0-gw5wq Completed 0
indexed-job-1-vw4qk Completed 1
indexed-job-2-mpvnr Completed 2
indexed-job-3-8z5j8 Completed 3
Четыре строки не означают, что все четыре Pod работали одновременно. Четыре индекса подтверждают общий объём работы, а parallelism: 2 ограничивает число Pods, которые Kubernetes может запускать одновременно.
Планирование CronJob
На этом шаге вы создадите CronJob, которая запускает Job каждую минуту. Синтаксис cron содержит пять полей: минута, час, день месяца, месяц и день недели.
Расписание */1 * * * * означает «каждую минуту». Оно намеренно задано достаточно часто, чтобы вы могли быстро увидеть результат в рамках лабораторной работы. В рабочей среде расписания обычно выполняются реже. Манифест также содержит вложенный jobTemplate, поскольку CronJob создаёт Jobs, а каждая Job затем создаёт Pods.
cat <<'EOF' > heartbeat-cronjob.yaml
apiVersion: batch/v1
kind: CronJob
metadata:
name: heartbeat
namespace: practice-batch
spec:
schedule: "*/1 * * * *"
concurrencyPolicy: Forbid
successfulJobsHistoryLimit: 2
failedJobsHistoryLimit: 1
jobTemplate:
spec:
template:
spec:
restartPolicy: Never
containers:
- name: heartbeat
image: busybox:1.36
imagePullPolicy: IfNotPresent
command: ["sh", "-c", "date -u; echo heartbeat-complete"]
EOF
Примените расписание и просмотрите его сводную информацию:
kubectl apply -f heartbeat-cronjob.yaml
kubectl get cronjob heartbeat -n practice-batch
concurrencyPolicy: Forbid не позволяет новому запуску начаться, пока предыдущий ещё не завершён. Создайте одну Job сразу из шаблона CronJob, чтобы изучить её, не дожидаясь следующей минуты:
kubectl create job --from=cronjob/heartbeat heartbeat-manual -n practice-batch
kubectl wait --for=condition=Complete job/heartbeat-manual -n practice-batch --timeout=60s
kubectl logs job/heartbeat-manual -n practice-batch
В журнале будет временная метка UTC, за которой следует heartbeat-complete. Созданная вручную Job использует тот же шаблон, но не изменяет будущее расписание CronJob.
Типичный вывод:
job.batch/heartbeat-manual condition met
Thu Aug 13 07:48:29 UTC 2026
heartbeat-complete
У вас временная метка будет другой. Обратите внимание: контейнер использует UTC благодаря команде date -u. Это помогает не путать время планирования в кластере с часовым поясом терминала.
Приостановка и возобновление запланированных задач
На этом шаге вы приостановите CronJob, не удаляя её определение. Приостановка полезна во время технического обслуживания или устранения инцидентов.
Команда kubectl patch изменяет только поле, указанное в JSON-параметре. В данном случае spec.suspend устанавливается в true; объект CronJob, его расписание и уже существующие Jobs остаются без изменений.
kubectl patch cronjob heartbeat -n practice-batch --type=merge -p '{"spec":{"suspend":true}}'
kubectl get cronjob heartbeat -n practice-batch -o custom-columns='NAME:.metadata.name,SCHEDULE:.spec.schedule,SUSPEND:.spec.suspend,LAST_SCHEDULE:.status.lastScheduleTime'
Уже созданные Jobs продолжают выполняться; приостановка лишь запрещает создание новых Jobs по расписанию. После проверки возобновите работу:
kubectl patch cronjob heartbeat -n practice-batch --type=merge -p '{"spec":{"suspend":false}}'
kubectl get jobs -n practice-batch --sort-by=.metadata.creationTimestamp
Теперь можно различать три уровня: CronJob задаёт расписание, каждый запланированный запуск создаёт Job, а каждая Job создаёт один или несколько Pods.
Поскольку вся работа может завершиться до наступления следующей минутной границы, в поле LAST_SCHEDULE всё ещё может отображаться <none>. Это не ошибка: heartbeat-manual была создана по запросу с помощью --from, а не планировщиком CronJob. Важно, что итоговое значение SUSPEND=false разрешает будущие запланированные запуски.
Итоги
Вы использовали Jobs для конечных задач, индексированные завершения для параллельной обработки и CronJobs для регулярных запусков. Вы изучили владение объектами контроллеров, журналы, состояние завершения, политику параллельных запусков, ограничения истории и приостановку. Эти различия жизненного цикла важны для выбора подходящего контроллера рабочих нагрузок Kubernetes.


