CronJobы полезны для создания периодических и повторяющихся задач, таких как создание бэкапов или отправка электронных писем. CronJobы также могут планировать отдельные Jobы (задачи, задания - здесь и далее используется английский термин, чтобы избежать путаницы) на конкретное время, например, запланировать Job на то время, когда ваш кластер, скорее всего, будет простаивать. Jobы Kubernetes в первую очередь предназначены для краткосрочных и пакетных рабочих нагрузок (workloads).
В Kubernetes есть несколько контроллеров для управления подами: ReplicaSets, DaemonSets, Deployments и StatefulSets. У каждого из них есть свой сценарий использования. Однако их всех объединяет гарантия того, что их поды всегда работают. В случае сбоя пода контроллер перезапускает его или переключает его на другой узел (node), чтобы гарантировать постоянную работу программы, которая размещена на подах.
Что, если мы не хотим, чтобы под работал постоянно? Есть сценарии, когда вы не хотите, чтобы процесс продолжался непрерывно. Резервное копирование (backup, создание бэкапа) - это создание копии данных на отдельном носителе, предназначенной для восстановления данных в случае их потери. Данный процесс не должен выполняться постоянно. Напротив, он запускается на выполнение, и после завершения возвращает соответствующий код завершения (exit status), который сообщает, является ли результат успешным или неудачным.
Варианты использования Jobов Kubernetes
Наиболее подходящие варианты использования Jobов Kubernetes:
1. Пакетная обработка данных: допустим вы запускаете пакетную задачу с периодичностью один раз в день/неделю/месяц или по определенному расписанию. Примером этого может служить чтение файлов из хранилища или базы данных и передача их в сервис для обработки файлов.
2. Команды/специфические задачи: например, запуск скрипта/кода, который выполняет очистку базы данных.
Jobы Kubernetes гарантируют, что один или несколько подов выполнят свои команды и успешно завершатся. После завершения всех подов без ошибок, Job завершается. Когда Job удаляется, все созданные поды также удаляются.
Ваш первый Kubernetes Job
Создание Jobа Kubernetes, как и других ресурсов Kubernetes, осуществляется с помощью файла определения (definition file). Откройте новый файл, вы можете назвать его job.yaml. Добавьте в файл следующее:
apiVersion: batch/v1kind: Jobmetadata:name: hello-worldspec:template: metadata: name: hello-world spec: containers: - name: hello-world image: busybox command: ["echo", "Running a hello-world job"] restartPolicy: OnFailure
Как и в случае с другими ресурсами Kubernetes, мы можем применить это определение к работающему кластеру Kubernetes с помощью kubectl. Выглядит это следующим образом:
$ kubectl apply -f job.yamljob.batch/hello-world created
Давайте посмотрим, какие поды были созданы:
$ kubectl get podsNAME READY STATUS RESTARTS AGEhello-world-mstmc 0/1 ContainerCreating 0 1s
Проверим статус jobа с помощью kubectl:
kubectl get jobs hello-world-mstmcNAME COMPLETIONS DURATION AGEhello-world-mstmc 1/1 21s 27s
Подождем несколько секунд и снова запустить ту же команду:
$ kubectl get podsNAME READY STATUS RESTARTS AGEhello-world-mstmc 0/1 Completed 0 25s
Статус пода говорит, что он не запущен. Статус Completed, поскольку job был запущен и выполнен успешно. Job, который мы только что определили, имел простую задачу :) - echo Running a hello-world job в стандартный вывод.
Прежде чем двигаться дальше, давайте убедимся, что job действительно выполнил то, что мы от него хотели:
kubectl logs -f hello-world-mstmcRunning a hello-world job
В логах отображается то, что мы задали, т.е. вывести Running a hello-world job. Job успешно выполнен.
Запуск CronJob вручную
Бывают ситуации, когда вам нужно выполнить cronjob на разовой основе. Вы можете сделать это, создав job из существующего cronjobа.
Например, если вы хотите, чтобы cronjob запускался вручную, вы должны сделать следующее.
kubectl create job --from=cronjob/kubernetes-cron-job manual-cron-job
--from=cronjob/kubernetes-cron-job
скопируйте
шаблон cronjobа и создайте job с именем manual-cron-job
Удаление Jobа Kubernetes и очистка (Cleanup)
Когда Job Kubernetes завершается, ни Job, ни созданные им поды не удаляются автоматически. Вы должны удалить их вручную. Эта особенность гарантирует, что вы по-прежнему сможете просматривать логи, Job и его поды в завершенном статусе.
Job можно удалить с помощью kubectl следующим образом:
kubectl delete jobs job_name
Приведенная выше команда удаляет конкретный Job и все его
дочерние поды. Как и в случае с другими контроллерами Kubernetes,
вы можете удалить Job, только покидая его поды, используя флаг
cascade=false
. Например:
kubectl delete jobs job_name cascade=false
Есть еще несколько ключевых параметров, которые вы можете использовать с jobами/cronjobами kubernetes в зависимости от ваших потребностей. Давайте рассмотрим каждый из них.
1. failedJobHistoryLimit и successfulJobsHistoryLimit: Удаления истории успешных и неудавшихся jobов основано на заданном Вами количестве сохранений. Это очень удобно для отбрасывания всех вхождений, завершенных неудачей, когда вы пытаетесь вывести список jobов. Например,
failedJobHistoryLimit: 5successfulJobsHistoryLimit: 10
2. backoffLimit: общее количество повторных попыток в случае сбоя пода.
RestartPolicy Jobа Kubernetes
В параметре restartPolic
y (политика перезапуска)
нельзя установить политику always (всегда). Job не должен
перезапускать под после успешного завершения по определению. Таким
образом, для параметра restartPolicy
доступны варианты
Never (никогда) и OnFailure (в случае неудачи).
Ограничение выполнения Jobа Kubernetes по времени
Если вы заинтересованы в выполнении Jobа в течение определенного
периода времени, независимо от того, успешно ли завершился процесс,
то у Jobов Kubernetes есть параметр
spec.activeDeadlineSeconds
. Установка для этого
параметра приведет к немедленному прекращению работы Jobа по
истечении заданного количества секунд.
Обратите внимание, что этот параметр переопределяет
.spec.backoffLimit
, т.е. если под завершается неудачей
и Job достигает своего временного ограничения, неудавшийся под не
перезапускается. Все сразу же останавливается.
В следующем примере мы создаем Job как с параметром
backoff limit
, так и с deadline
:
apiVersion: batch/v1kind: Jobmetadata: name: data-consumerspec: backoffLimit: 5 activeDeadlineSeconds: 20 template:spec: containers: - name: consumer image: busybox command: ["/bin/sh", "-c"] args: ["echo 'Consuming data'; sleep 1; exit 1"] restartPolicy: OnFailure
Завершения Jobов и параллелизм
Мы рассмотрели, как можно выполнить одну задачу, определенную внутри объекта Job, что более известно как шаблон run-once. Однако в реальных сценариях используются и другие шаблоны.
Несколько одиночных Jobов
Например, у нас может быть очередь сообщений, которая требует обработки. Мы должны порождать пользовательские jobы, которые извлекают сообщения из очереди, пока она не опустеет. Чтобы реализовать этот шаблон с помощью Jobов Kubernetes, мы устанавливаем в параметр .spec.completions какое-либо число (должно быть ненулевым, а положительным числом). Job начинает создавать поды пока не достигнет заданного числа завершений (completions). Job считается завершенным, когда все поды завершаются с успешным с кодом завершения. Приведем пример. Измените наш файл определения, чтобы он выглядел следующим образом:
apiVersion: batch/v1kind: Jobmetadata:name: data-consumerspec:completions: 5template: metadata: name: data-consumer spec: containers: - name: data-consumer image: busybox command: ["/bin/sh","-c"] args: ["echo 'consuming a message from queue'; sleep 5"] restartPolicy: OnFailure
-
Мы указываем параметр completions равным 5.
Несколько параллельно запущенных Jobов (Work Queue)
Другой шаблон может включать в себя необходимость запуска нескольких jobов, но вместо того, чтобы запускать их один за другим, нам нужно запускать несколько из них параллельно. Параллельная обработка сокращает общее время выполнения и находит применение во многих областях, например в data science и искусственном интеллекте.
Измените файл определения, чтобы он выглядел следующим образом:
apiVersion: batch/v1kind: Jobmetadata:name: data-consumerspec:parallelism: 5template: metadata: name: data-consumer spec: containers: - name: data-consumer image: busybox command: ["/bin/sh","-c"] args: ["echo 'consuming a message from queue'; sleep $(shuf -i 5-10 -n 1)"] restartPolicy: OnFailure
В этом примере мы не задали параметр
.spec.completions
. Вместо этого мы указали параметр
parallelism
. Параметр completions
в нашем
случае по умолчанию равен parallelism
(5). Теперь Job
делает следующее:
Одновременно будут запущены 5 подов, все они будут выполнять один и то же Job. Когда один из подов завершается успехом, это будет означать, что весь Job готов. Поды больше не создаются, и Job в конечном итоге завершается.
В этом сценарии Kubernetes Job одновременно порождает 5 подов. Знать, завершили ли остальные поды свои задачи, в компетенции самих подов. В нашем примере мы предполагаем, что получаем сообщения из очереди сообщений (например, AWS SQS). Когда сообщений для обработки больше нет, Job получает уведомление о том, что он должен завершиться. После успешного завершения первого пода:
-
Поды больше не создаются.
-
Существующие поды завершают свою работу и тоже завершаются.
В приведенном выше примере мы изменили команду, которую выполняет под, чтобы перевести его в спящий режим на случайное количество секунд (от 5 до 10), прежде чем он завершится. Таким образом, мы примерно моделируем, как несколько подов могут работать вместе с внешним источником данных, таким как очередь сообщений или API.
Ограничения CronJobов
Cronjob создает объект job примерно по одному разу за сеанс выполнения своего расписания (schedule). Мы говорим примерно, потому что при определенных обстоятельствах могут быть созданы два jobа или ни одного. Мы пытаемся сделать подобные случаи как можно более редкими, но не можем предотвратить их полностью. Следовательно, jobы должны быть идемпотентными.
Если параметр startingDeadlineSeconds
установлен на
большое значение или не задан (по умолчанию) и параметр
concurrencyPolicy
установлен на Allow, job всегда
будут запускаться как минимум один раз.
Для каждого CronJobа контроллер CronJob проверяет, сколько расписаний он пропустил за период с последнего запланированного времени до настоящего момента. Если пропущенных расписаний больше 100, то job не запускается и в логи вносится сообщение об ошибке.
Cannot determine if job needs to be started. Too many missed start time (> 100). Set or decrease .spec.startingDeadlineSeconds or check clock skew.
Важно отметить, что если параметр
startingDeadlineSeconds
установлен (т.е. не
nil
), то контроллер считает сколько произошло
пропущенных jobов исходя из значения параметра
startingDeadlineSeconds
, начиная с последнего
заданного времени до сих пор. Например, если параметр
startDeadlineSeconds
установлен на 200
,
контроллер подсчитывает, сколько пропущенных jobов произошло за
последние 200 секунд.
CronJob считается пропущенным, если его не удалось создать в
установленное время. Например, если для параметра
concurrencyPolicy
задано значение Forbid
и была предпринята попытка запланировать CronJob во время
выполнения предыдущего расписания, оно будет считаться
пропущенным.
Например, предположим, что CronJob настроен запускать новый Job
каждую минуту начиная с 08:30:00
, а его параметр
startingDeadlineSeconds
не установлен. Если контроллер
CronJob не работал с 08:29:00
до
10:21:00
, job не запустится, так как количество
пропущенных jobов в расписании превышает 100.
Чтобы проиллюстрировать эту концепцию с другой стороны,
предположим, что CronJob запрограммирован планировать новый Job в
расписании каждую минуту начиная с 08:30:00
и его
параметр startingDeadlineSeconds
устанавливается на
200 секунд. Если контроллер CronJob не работает в течение того же
периода, что и в предыдущем примере (с 08:29:00
до
10:21:00
), Job все равно запустится в 10:22:00. Это
происходит, поскольку контроллер теперь проверяет, сколько
пропущенных расписаний было за последние 200 секунд (т. е. 3
пропущенных расписания), вместо того, чтобы считать с последнего
заданного времени до настоящего момента.
CronJob отвечает только за создание Jobов, соответствующих его расписанию, а Job, в свою очередь, отвечает за управление представляемыми им подами.
TL;DR (Too Long; Didnt Read)
-
Jobы Kubernetes используются, когда вы хотите создать поды, которые будут выполнять определенную задачу, а затем завершать работу.
-
Jobы Kubernetes по умолчанию не нуждаются в селекторах подов; Job автоматически обрабатывает их лейблы и селекторы.
-
Параметр
restartPolicy
для Job принимает значения Never или OnFailure
-
Jobы используют параметры completions и parallelism для управления шаблонами, которые определяют порядок работы подов. Поды Jobов могут выполняться как одна задача, несколько последовательных задач или несколько параллельных задач, в которых первая завершенная задача дает указание остальным подам завершиться.
-
Вы можете контролировать количество попыток Jobа перезапустить неудавшиеся поды, используя параметр
.spec.backoffLimit
. По умолчанию этот лимит равен шести. -
Вы можете контролировать время работы jobа, используя параметр .
spec.activeDeadlineSeconds
. Этот лимит отменяетbackoffLimit
. Таким образом, Job не пытается перезапустить неудавшийся под, если дедлайн достигнут. -
Jobы и их поды не удаляются автоматически после завершения. Вы должны удалить их вручную или использовать контроллер
ttlSecondsAfterFinished
, который на момент написания этой статьи все еще находится в статусе alpha.
Перевод статьи подготовлен в преддверии старта курса "Инфраструктурная платформа на основе Kubernetes". Также приглашаем всех желающих записаться на бесплатный демо-урок по теме: "Работа с NoSQL базами в k8s (на примере Apache Cassandra)"