Job’ы и Cronjob’ы Kubernetes

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

 

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/v1
kind: Job
metadata:
name: hello-world
spec:
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.yaml
job.batch/hello-world created

Давайте посмотрим, какие поды были созданы:

$ kubectl get pods
NAME              	READY    STATUS          RESTARTS      AGE
hello-world-mstmc        0/1 	ContainerCreating   0          1s

Проверим статус job’а с помощью kubectl:

kubectl get jobs hello-world-mstmc
NAME            COMPLETIONS     DURATION     AGE
hello-world-mstmc   1/1           21s        27s

Подождем несколько секунд и снова запустить ту же команду:

$ kubectl get pods
NAME              	READY   STATUS  	RESTARTS       AGE
hello-world-mstmc       0/1 	Completed          0           25s

Статус пода говорит, что он не запущен. Статус Completed, поскольку job был запущен и выполнен успешно. Job, который мы только что определили, имел простую задачу :) - echo “Running a hello-world job” в стандартный вывод.

Прежде чем двигаться дальше, давайте убедимся, что job действительно выполнил то, что мы от него хотели:

kubectl logs -f hello-world-mstmc
Running 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: 5
successfulJobsHistoryLimit: 10

2. backoffLimit: общее количество повторных попыток в случае сбоя пода.

RestartPolicy Job’а Kubernetes

В параметре restartPolicy (политика перезапуска) нельзя установить политику “always” (всегда). Job не должен перезапускать под после успешного завершения по определению. Таким образом, для параметра restartPolicy доступны варианты “Never” (никогда) и “OnFailure” (в случае неудачи).

Ограничение выполнения Job’а Kubernetes по времени

Если вы заинтересованы в выполнении Job’а в течение определенного периода времени, независимо от того, успешно ли завершился процесс, то у Job’ов Kubernetes есть параметр spec.activeDeadlineSeconds. Установка для этого параметра приведет к немедленному прекращению работы Job’а по истечении заданного количества секунд.

Обратите внимание, что этот параметр переопределяет .spec.backoffLimit, т.е. если под завершается неудачей и Job  достигает своего временного ограничения, неудавшийся под не перезапускается. Все сразу же останавливается.

В следующем примере мы создаем Job как с параметром backoff limit, так и с deadline:

apiVersion: batch/v1
kind: Job
metadata:
 name: data-consumer
spec:
 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/v1
kind: Job
metadata:
name: data-consumer
spec:
completions: 5
template:
 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/v1
kind: Job
metadata:
name: data-consumer
spec:
parallelism: 5
template:
 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; Didn’t 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)"

Источник: https://habr.com/ru/company/otus/blog/546376/


Интересные статьи

Интересные статьи

В этой статье мы собираемся создать пайплайн непрерывной интеграции (CI) с Tekton, фреймворком с открытым исходным кодом для создания конвейеров CI / CD в Kubernetes. Мы собираемся подго...
В этой статье мы расскажем, как оптимизировать крупный проект в «Битрикс24» и увеличить его производительность в 3 раза, изменяя настройки MySQL и режим питания CPU. Дано Корпоративн...
В этой статье я расскажу о том, как автоматизировать заказ и продление сертификатов от Let’s Encrypt (и не только) для Ingress’а в Kubernetes с помощью дополнения cert-manager. Но начну с кра...
В своей практике мы часто сталкиваемся с задачей адаптации клиентских приложений для запуска в Kubernetes. При проведении данных работ возникает ряд типовых проблем. Одну из них мы недавно ос...
Павел Селиванов, архитектор решений Southbridge и преподаватель Слёрма, выступил с докладом на DevOpsConf 2019. Этот доклад — часть одной из тем углубленного курса по Kubernetes «Слёрм Мега». Сл...