Запланировать запуск кода в вашей контейнерной среде

Добро пожаловать в очередной выпуск серии блогов Kubernetes in a Nutshell. До сих пор мы рассматривали ресурсы (объекты) Kubernetes, такие как Deployments, Services, Volumes и т. Д.

В этом блоге мы рассмотрим Job и CronJob. С помощью примеров вы узнаете о:

  • Как использовать эти компоненты.
  • Укажите ограничения, такие как ограничение по времени, параллелизм.
  • Обработка сбоев и т. Д.

Код (много YAML) доступен на GitHub.

Работа Kubernetes

Вы можете использовать Kubernetes Job для запуска пакетных процессов, заданий ETL, специальных операций и т. Д. Он запускает Pod и позволяет ему работать до конца. Это сильно отличается от других Pod контроллеров, таких как Deployment или ReplicaSet.

Как всегда, мы будем учиться на практике. Итак, приступим!

Привет, работа!

Вот как выглядит типичный Job манифест:

apiVersion: batch/v1
kind: Job
metadata:
  name: job1
spec:
  template:
    spec:
      containers:
        - name: job
          image: busybox
          args:
            - /bin/sh
            - -c
            - date; echo sleeping....; sleep 90s; echo exiting...; date
      restartPolicy: Never

Этот Job просто запустит busybox контейнер, который просто выполняет кучу команд оболочки. Давайте создадим это Job и разберемся, что происходит

Чтобы упростить задачу, на файл YAML ссылаются непосредственно из репозитория GitHub, но вы также можете загрузить файл на свой локальный компьютер и использовать его таким же образом.

kubectl apply -f https://raw.githubusercontent.com/abhirockzz/kubernetes-in-a-nutshell/master/jobs/job1.yaml

Проверьте Job и связанный с ним Pod.

kubectl get job/job1
NAME   COMPLETIONS   DURATION   AGE
job1   0/1           8s         8s

Вы должны увидеть Pod в состоянии Running, например:

kubectl get pod -l=job-name=job1
job1-bptmd 1/1  Running

Если вы проверите Pod журналы, вы должны увидеть что-то похожее на это:

kubectl logs <pod_name>
Thu Jan  9 10:10:35 UTC 2020
sleeping....

Проверьте задание еще раз через ~ 90 с.

kubectl get job/job1
NAME   COMPLETIONS   DURATION   AGE
job1   1/1           95s        102s

Job проработал немногим более 90 секунд, и COMPLETIONS отражает успешное завершение Pod. Это также отразится в Pod журналах.

Thu Jan  9 10:10:05 UTC 2020
sleeping....
exiting...
Thu Jan  9 10:11:35 UTC 2020

Кроме того, статус Pod должен измениться на Completed.

kubectl get pod -l=job-name=job1
job1-bptmd 0/1  Completed

Если все, что Job было сделано, - это создать Pod для запуска контейнера, почему мы не можем использовать старый добрый Pod?

Это потому, что Job может быть перезапущен Kubernetes в случае сбоя контейнера, чего не может произойти с изолированным Pod. В дополнение к этому, есть много других возможностей, которые предоставляет контроллер заданий, которые мы рассмотрим в будущем.

Чтобы удалить это Job, просто запустите kubectl delete job/job1.

Установление ограничения по времени

Например, вы выполняете пакетное задание, и его выполнение по какой-то причине занимает слишком много времени. Это может быть нежелательно. Вы можете ограничить время, в течение которого Job может продолжать работать, установив атрибут activeDeadlineSeconds в спецификации.

Вот пример:

apiVersion: batch/v1
kind: Job
metadata:
  name: job2
spec:
  activeDeadlineSeconds: 5
  template:
    spec:
      containers:
        - name: job
          image: busybox
          args:
            - /bin/sh
            - -c
            - date; echo sleeping....; sleep 10s; echo exiting...; date
      restartPolicy: Never

Обратите внимание, что activeDeadlineSeconds был установлен на 5 секунд, в то время как процесс контейнера был назначен для запуска в течение 10 секунд.

Создайте Job, подождите несколько секунд (~ 10 секунд) и проверьте Job.

kubectl apply -f https://raw.githubusercontent.com/abhirockzz/kubernetes-in-a-nutshell/master/jobs/job2.yaml
kubect get job/job2 -o yaml

Прокрутите вниз, чтобы проверить поле status, и вы увидите, что Job находится в состоянии Failed из-за DeadlineExceeded.

status:
  conditions:
  - lastProbeTime: "2020-01-09T10:57:13Z"
    lastTransitionTime: "2020-01-09T10:57:13Z"
    message: Job was active longer than specified deadline
    reason: DeadlineExceeded
    status: "True"
    type: Failed

Чтобы удалить задание, просто запустите kubectl delete job/job2.

Обработка сбоев

Что делать, если возникают проблемы из-за сбоя контейнера (процесс завершен) или Pod сбоя? Давайте попробуем это сделать, смоделировав сбой.

В этом Job контейнер печатает date, sleeps в течение 5 секунд и выходит со статусом 1 для имитации сбоя.

apiVersion: batch/v1
kind: Job
metadata:
  name: job3
spec:
  backoffLimit: 2
  template:
    spec:
      containers:
        - name: job
          image: busybox
          args:
            - /bin/sh
            - -c
            - date; echo sleeping....; sleep 5s; exit 1;
      restartPolicy: OnFailure

Обратите внимание, что restartPolicy: OnFailure отличается от предыдущего примера, где он был установлен на Never. Мы вернемся к этому чуть позже.

Создайте Job и следите за конкретным Pod для этой работы.

kubectl apply -f https://raw.githubusercontent.com/abhirockzz/kubernetes-in-a-nutshell/master/jobs/job3.yaml
kubectl get pod -l=job-name=job3 -w

Вы должны увидеть что-то похожее на изображение ниже:

NAME                                     READY   STATUS              RESTARTS   AGE
job3-qgv4b                               0/1     ContainerCreating   0          4s
job3-qgv4b                               1/1     Running             0          6s
job3-qgv4b                               0/1     Error               0          12s
job3-qgv4b                               1/1     Running             1          17s
job3-qgv4b                               0/1     Error               1          22s
job3-qgv4b                               0/1     CrashLoopBackOff    1          34s
job3-qgv4b                               1/1     Running             2          40s
job3-qgv4b                               1/1     Terminating         2          40s
job3-qgv4b                               0/1     Terminating         2          45s
job3-qgv4b                               0/1     Terminating         2          51s

Обратите внимание, как меняется Pod статус.

  • Он начинается с вытягивания и запуска контейнера.
  • Он переходит в состояние Error, так как выходит со статусом 1 (после сна в течение 5 секунд).
  • Он снова возвращается к состоянию Running (обратите внимание, что счетчик RESTARTS теперь равен 1).
  • Как и ожидалось, он снова переходит в состояние Error и перезапускается еще раз - RESTARTS count теперь 2.
  • Наконец, это terminated.

Kubernetes (точнее, контроллер заданий) перезапустил контейнер за нас, потому что мы указали restartPolicy: OnFailure.

Но может возникнуть ситуация, когда это может продолжаться бесконечно, поэтому мы ограничиваем это с помощью backoffLimit: 2, что гарантирует, что Kubernetes повторяет попытки только дважды, прежде чем пометить этот Job как Failed.

Обратите внимание, что это был пример перезапуска контейнера. Контроллер заданий также может создать новый Pod в случае сбоя Pod.

Если вы проверите состояние Job ...

kubectl get job/job3 -o yaml

… Вы увидите, что у него Failed из-за BackoffLimitExceeded.

status:
  conditions:
  - lastProbeTime: "2020-01-09T11:16:24Z"
    lastTransitionTime: "2020-01-09T11:16:24Z"
    message: Job has reached the specified backoff limit
    reason: BackoffLimitExceeded
    status: "True"
    type: Failed

restartPolicy из Never означает, что сбой не приведет к перезапуску контейнера или созданию нового Pod, когда что-то пойдет не так. Кроме того, ограничение по умолчанию для backoffLimit составляет 6.

Чтобы удалить это задание, просто запустите kubectl delete job/job3.

Больше лучше

Существуют требования, при которых вы можете захотеть, чтобы Job набирал более одного Pod, чтобы добиться результата.

Например, рассмотрим сценарий, в котором вы запускаете пакетное задание для обработки записей из базы данных - наличие нескольких Pod, разделяющих нагрузку, определенно может помочь.

Один из способов сделать это может заключаться в том, чтобы каждый Pod запускался последовательно, записывал количество строк, обработанных во внешнем источнике (например, в другой таблице БД), а другой Pod мог забираться оттуда.

Это можно сделать, добавив свойство completions в спецификацию Job.

apiVersion: batch/v1
kind: Job
metadata:
  name: job4
spec:
  completions: 2
  template:
    spec:
      containers:
        - name: job
          image: busybox
          args:
            - /bin/sh
            - -c
            - date; echo sleeping....; sleep 10s; echo exiting...; date
      restartPolicy: Never

Создайте Job и следите за его развитием.

kubectl apply -f https://raw.githubusercontent.com/abhirockzz/kubernetes-in-a-nutshell/master/jobs/job4.yaml
kubectl get job/job4 -w

Вы должны увидеть что-то похожее на это:

NAME   COMPLETIONS   DURATION   AGE
job4   0/2           3s         3s
job4   1/2           20s        20s
job4   2/2           37s        37s

Поскольку мы установили completions равным двум:

  • Два Pod были созданы один за другим (последовательно).
  • Job был отмечен Completed (успешным) только после того, как оба Pods были выполнены до конца. В противном случае были бы применены условия отказа (как описано выше).

Давайте также проверим Pod журналы.

kubectl get pods -l=job-name=job4
kubect logs <pod_name>

Если вы видите журналы для обоих Pod, вы сможете подтвердить, что они запускались один за другим в последовательности (и каждый работал в течение ~ 10 секунд).

Журналы для Pod 1.

Thu Jan  9 11:31:57 UTC 2020
sleeping....
exiting...
Thu Jan  9 11:32:07 UTC 2020

Журналы для Pod 2.

Thu Jan  9 11:32:15 UTC 2020
sleeping....
exiting...
Thu Jan  9 11:32:25 UTC 2020

Как насчет запуска пакетной обработки в параллельном режиме, когда все Pod создаются одновременно (а не последовательно)?

Чтобы справиться с этим случаем, наша логика обработки должна быть соответствующим образом настроена, поскольку между параллельными Pods требуется координация с точки зрения того, какой набор рабочих элементов выбрать и как обновить их статус завершения.

Мы не будем углубляться в это, но я надеюсь, что вы уловили идею с точки зрения требований.

Теперь этого можно достичь, используя parallelism вместе с completions. Вот пример:

apiVersion: batch/v1
kind: Job
metadata:
  name: job5
spec:
  completions: 3
  parallelism: 3
  template:
    spec:
      containers:
        - name: job
          image: busybox
          args:
            - /bin/sh
            - -c
            - date; echo sleeping....; sleep 10s; echo exiting...; date
      restartPolicy: Never

Используя атрибут parallelism, мы смогли ограничить максимальное количество Pod, которые могут выполняться одновременно. В этом случае, поскольку parallelism установлено в три, это означает, что:

  • Все три Pod будут созданы одновременно.
  • Job будет помечен как Completed (успешно), только если все три будут выполнены до конца. В противном случае применяются условия отказа (как описано выше).

Как только вы закончите

Вы можете использовать ttlSecondsAfterFinished, чтобы указать количество секунд, по истечении которых Job может быть автоматически удален после его завершения (либо Completed, либо Failed). Это также удаляет зависимые объекты, такие как Pods, порожденные Job.

CronJob

Объект CronJob позволяет вам планировать Job выполнение, а не запускать их вручную.

Он использует формат Cron для выполнения задания по расписанию. По сути, CronJob - это абстракция более высокого уровня, которая включает в себя шаблон Job (как показано выше) вместе с расписанием (формат cron) и другими атрибутами.

Давайте создадим простой CronJob, который повторяется каждую минуту.

apiVersion: batch/v1beta1
kind: CronJob
metadata:
  name: cronjob1
spec:
  schedule: "*/1 * * * *"
  jobTemplate:
    spec:
      template:
        spec:
          containers:
            - name: cronjob
              image: busybox
              args:
                - /bin/sh
                - -c
                - date; echo sleeping....; sleep 5s; echo exiting...;
          restartPolicy: Never

Раздел jobTemplate такой же, как у Job. Он просто встроен в эту CronJob спецификацию. Это тот же контейнер, который мы использовали в примере Job.

Создайте CronJob и проверьте его:

kubectl apply -f https://raw.githubusercontent.com/abhirockzz/kubernetes-in-a-nutshell/master/jobs/cronjob1.yaml
kubectl get cronjob/cronjob1

Выход:

NAME       SCHEDULE      SUSPEND   ACTIVE   LAST SCHEDULE   AGE
cronjob1   */1 * * * *   False     0        <none>          4s

Следите за Job, порождаемым этим CronJob.

kubectl get job -w
NAME                  COMPLETIONS   DURATION   AGE
cronjob1-1578572340   0/1           2s         2s
cronjob1-1578572340   1/1           11s        11s
cronjob1-1578572400   0/1                      0s
cronjob1-1578572400   0/1           0s         0s
cronjob1-1578572400   1/1           10s        10s
cronjob1-1578572460   0/1                      0s
cronjob1-1578572460   0/1           0s         0s
cronjob1-1578572460   1/1           11s        11s

Каждую минуту создается новый Job, который, как и ожидалось, работал ~ 10 секунд. Вы также можете проверить журналы индивидуального Pod, созданного Job (точно так же, как вы это делали с предыдущими примерами).

kubectl get pod -l=job-name=<job_name>
kubectl logs <pod_name>

Есть и другие (необязательные) свойства CronJob в дополнение к атрибуту schedule. Давайте посмотрим на один из них.

concurrencyPolicy

У него есть три возможных значения - Forbid, Allow и Replace.

Выберите Forbid, если вы не хотите одновременного выполнения вашего Job. Когда пора активировать Job в соответствии с расписанием, а экземпляр Job уже запущен, текущая итерация пропускается.

Если вы выберете Replace в качестве политики параллелизма, текущий запущенный Job будет остановлен и будет создан новый Job.

Указание Allow позволит нескольким Job экземплярам работать одновременно.

Вот пример:

apiVersion: batch/v1beta1
kind: CronJob
metadata:
  name: cronjob2
spec:
  schedule: "*/1 * * * *"
  concurrencyPolicy: Allow
  jobTemplate:
    spec:
      template:
        spec:
          containers:
            - name: cronjob
              image: busybox
              args:
                - /bin/sh
                - -c
                - date; echo sleeping....; sleep 90s; echo exiting...;
          restartPolicy: Never

Вы можете создать это CronJob, а затем отслеживать отдельных Job, чтобы наблюдать за поведением.

kubectl apply -f https://raw.githubusercontent.com/abhirockzz/kubernetes-in-a-nutshell/master/jobs/cronjob2.yaml
kubectl get job -w

Поскольку расписание составляет каждую минуту, а контейнер работает в течение 90 секунд, вы увидите, что одновременно выполняется несколько Job. Такое совпадение возможно, поскольку мы применили concurrencyPolicy: Allow.

Вы можете увидеть что-то вроде этого:

cronjob2-1578573480   0/1                      0s
cronjob2-1578573480   0/1           0s         0s
cronjob2-1578573540   0/1                      0s
cronjob2-1578573540   0/1           0s         0s
cronjob2-1578573480   1/1           95s        95s

Обратите внимание, что задание cronjob2-1578573540 было запущено до завершения cronjob2-1578573480.

Другие свойства CronJob:

  • История заданий: successfulJobsHistoryLimit и failedJobsHistoryLimit можно использовать, чтобы указать, сколько истории вы хотите сохранить для неудачных и завершенных Jobs.
  • Срок начала указан startingDeadlineSeconds.
  • Приостановить указано suspend.

Заключение

Это все, что касается этой части серии «Kubernetes in a Nutshell». Следите за новостями.

Я очень надеюсь, что вам понравилась эта статья и вы ее чему-то научились. Рад получить ваш отзыв в виде комментария.