Контроллеры в Kubernetes: Job, спецификация и использование

Написание спецификации Job

Как и во всех других конфигурациях Kubernetes, для работы требуются поля apiVersion, kind и metadata.

Job также нуждается в разделе .spec.

Pod шаблон

.spec.template является единственным обязательным полем .spec.

.spec.template является шаблоном pod. У него точно такая же схема, что и у pod, за исключением того, что он вложенный и не имеет apiVersion или kind.

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

Разрешается только RestartPolicy, равное Never или OnFailure.

Pod селектор

Поле .spec.selector является необязательным. Почти во всех случаях вы не должны указывать это.

Параллельные Job

Существует три основных типа заданий, подходящих для выполнения Job:

  1. Непараллельные Job

    • Обычно запускается только один Pod, если он не вышел из строя.
    • Job завершается, как только его Pod успешно завершается.
  2. Параллельные Job с фиксированным количеством завершений:

    • Укажите ненулевое положительное значение для .spec.completions.
    • Job представляет общую задачу и завершается, когда есть один успешный Pod для каждого значения в диапазоне от 1 до .spec.completions.
  3. Параллельные Job с рабочей очередью:

    • Не указывайте .spec.completions, по умолчанию .spec.parallelism.
    • Pod'ы должны координировать свои действия между собой или внешней службой, чтобы определить, над чем каждый должен работать. Например, Pod может получить пакет из N элементов из рабочей очереди.
    • каждый Pod независимо способен определить, выполнены ли все его одноранговые узлы, и, таким образом, вся Job выполнена.
    • когда какой-либо Pod из Job завершается успешно, новые Pod'ы не создаются.
    • если хотя бы один Pod завершился успешно, а все Pod'ы завершены, Job завершается успешно.
    • после успешного завершения работы любого Pod ни один другой Pod не должен выполнять какую-либо работу для этой Job или записывать какой-либо вывод. Все они должны быть в процессе выхода.

Для непараллельного Job вы можете оставить как .spec.completions, так и .spec.parallelism неустановленными. Когда оба не установлены, по умолчанию оба равны 1.

Для Job с фиксированным числом завершений вы должны установить .spec.completions на необходимое количество завершений. Вы можете установить .spec.parallelism или оставить его неустановленным, и по умолчанию он будет равен 1.

Для Job с рабочей очередью вы должны оставить .spec.completions незаданным и установить .spec.parallelism в неотрицательное целое число.

Управление параллелизмом

Запрашиваемый параллелизм (.spec.parallelism) может быть установлен на любое неотрицательное значение. Если он не указан, по умолчанию используется значение 1. Если он задан как 0, то задание фактически приостанавливается до его увеличения.

Фактический параллелизм (количество Pod'ов, запущенных в любой момент) может быть больше или меньше запрошенного параллелизма по ряду причин:

  • Для Job с фиксированным числом выполнений фактическое количество Pod'ов, работающих параллельно, не будет превышать количество оставшихся завершений. Более высокие значения .spec.parallelism эффективно игнорируются.
  • Для Job с рабочей очередью новые Pod'ы не запускаются после того, как какой-либо Pod завершился успешно, однако оставшиеся Pod'ы могут быть завершены.
  • Если Job Controller не успел среагировать.
  • Если Job Controller'у не удалось создать Pod'ы по какой-либо причине (отсутствие ResourceQuota, отсутствие разрешения и т. д.), То количество Pod'ов может быть меньше запрашиваемого.
  • Job Controller может ограничить создание нового Pod'а из-за чрезмерных сбоев предыдущих Pod'ов в том же Job.
  • Когда Pod изящно выключен (gracefully shut down), требуется время, чтобы остановиться.

Обработка ошибок Pod'а и контейнера

Контейнер в Pod'е может завершиться сбоем по ряду причин, например, из-за того, что процесс в нем завершился с ненулевым кодом завершения, или контейнер был уничтожен из-за превышения лимита памяти и т. д. Если это произойдет, и .spec .template.spec.restartPolicy = "OnFailure", тогда Pod остается на узле, но контейнер перезапускается. Следовательно, ваша программа должна обрабатывать случай, когда она перезапускается локально, или же указать .spec.template.spec.restartPolicy = "Never".

Целый Pod также может выйти из строя по ряду причин, например, когда Pod удаляется с узла (узел обновляется, перезагружается, удаляется и т. д.), или если происходит сбой контейнера Pod'а и .spec.template .spec.restartPolicy = "Never". Когда Pod не работает, контроллер Job запускает новый Pod. Это означает, что ваше приложение должно обрабатывать случай, когда оно перезапускается в новом Pod'е. В частности, он должен обрабатывать временные файлы, блокировки, неполный вывод и тому подобное, вызванные предыдущими запусками.

Обратите внимание, что даже если вы укажете .spec.parallelism = 1 и .spec.completions = 1 и .spec.template.spec.restartPolicy = "Never", одна и та же программа может иногда запускаться дважды.

Если вы укажете .spec.parallelism и .spec.completions, которые больше 1, тогда может быть запущено несколько Pod'ов одновременно. Поэтому ваши Pod'ы также должны быть терпимы к параллелизму.

Политика отказа откатов

Существуют ситуации, когда вы хотите завершить Job после некоторого количества попыток из-за логической ошибки в конфигурации и т. д. Чтобы сделать это, задайте .spec.backoffLimit, чтобы указать количество повторных попыток, прежде чем считать Job неудачным. Предел отсрочки по умолчанию установлен равным 6. Неисправные Pod'ы, связанные с Job, воссоздаются контроллером Job с экспоненциальной задержкой отсрочки (10 с, 20 с, 40 с ...), ограниченной шестью минутами. Счетчик отсрочки сбрасывается, если перед следующей проверкой статуса Job не появляются новые неисправные Pod'ы.

Примечание. Если ваш Job имеет restartPolicy = "OnFailure", имейте в виду, что ваш контейнер, на котором запущено задание, будет завершен после того, как будет достигнут предел отката Job. Это может усложнить отладку исполняемого файла Job'а. Рекомендуется установить restartPolicy = "Never" при отладке Job или использовании системы журналирования, чтобы предотвратить непреднамеренную потерю вывода из сбойных Job.

Прекращение работы и уборка

Когда Job завершается, Pod'ы больше не создаются, но Pod'ы также не удаляются. Хранение их позволяет вам по-прежнему просматривать журналы завершенных Pod'ов, чтобы проверить наличие ошибок, предупреждений или других диагностических результатов. Объект job также остается после его завершения, чтобы вы могли просматривать его состояние. Пользователь может удалить старые job'ы, отметив их статус. Удалите job с помощью kubectl (например, kubectl delete jobs/pi или kubectl delete -f ./job.yaml). Когда вы удаляете job с помощью kubectl, все созданные им Pod'ы также удаляются.

По умолчанию Job будет работать непрерывно, пока не произойдет сбой Pod'а (restartPolicy = Never) или контейнер не выйдет по ошибке (restartPolicy = OnFailure), после чего Job перейдет к описанному выше .spec.backoffLimit. Как только .spec.backoffLimit будет достигнут, Job будет помечен как сбойный, и все запущенные Pod'ы будут прерваны.

Другой способ прекратить работу - установить активный крайний срок (active deadline). Сделайте это, установив в поле .spec.activeDeadlineSeconds Job'а равным несколько секунд. ActiveDeadlineSeconds применяется к продолжительности job'а, независимо от того, сколько Pod'ов создано. Как только Job достигает activeDeadlineSeconds, все его работающие Pod'ы прекращают работу, и статус Job становится типом: Failed with reason: DeadlineExceeded.

Обратите внимание, что .spec.activeDeadlineSeconds Job'а имеет приоритет над его .spec.backoffLimit. Таким образом, Job, который повторяет один или несколько отказавших Pod'ов, не будет развертывать дополнительные Pod'ы, как только достигнет предела времени, указанного в activeDeadlineSeconds, даже если backoffLimit еще не достигнут.

Пример:

apiVersion: batch/v1
kind: Job
metadata:
  name: pi-with-timeout
spec:
  backoffLimit: 5
  activeDeadlineSeconds: 100
  template:
    spec:
      containers:
      - name: pi
        image: perl
        command: ["perl",  "-Mbignum=bpi", "-wle", "print bpi(2000)"]
      restartPolicy: Never

Обратите внимание, что в спецификации Job и в спецификации Pod в Job есть поле activeDeadlineSeconds. Убедитесь, что вы установили это поле на должном уровне.

Автоматически очищать завершенные Job'ы

Завершенные Job'ы обычно больше не нужны в системе. Хранение их в системе будет оказывать давление на сервер API. Если Job'ы управляются непосредственно контроллером более высокого уровня, таким как CronJobs, Job'ы могут быть очищены с помощью CronJobs на основе указанной политики очистки на основе емкости.

Job паттерны

Объект Job может использоваться для поддержки надежного параллельного выполнения Pod'ов. Объект Job не предназначен для поддержки тесно взаимодействующих параллельных процессов, как это обычно встречается в научных вычислениях. Он поддерживает параллельную обработку набора независимых, но связанных рабочих элементов. Это могут быть электронные письма для отправки, фреймы для рендеринга, файлы для транскодирования, диапазоны ключей в базе данных NoSQL для сканирования и т. д.

В сложной системе может быть несколько разных наборов рабочих элементов. Здесь рассматривается только один набор рабочих элементов, которыми пользователь хочет управлять вместе - пакетное задание (batch job).

Есть несколько различных шаблонов для параллельных вычислений, каждый из которых имеет свои сильные и слабые стороны. Компромиссы:

  • Один объект Job для каждого рабочего элемента, в отличие от одного объекта Job для всех рабочих элементов. Последний лучше подходит для большого количества рабочих элементов. Первый создает некоторые накладные расходы для пользователя и для системы для управления большим количеством объектов Job.
  • Количество созданных Pod'ов равно числу рабочих элементов, тогда как каждый Pod может обрабатывать несколько рабочих элементов. Первый тип обычно требует меньше модификаций для существующего кода и контейнеров. Последний лучше подходит для большого количества рабочих элементов по тем же причинам, что и предыдущий пункт.
  • Несколько подходов используют рабочую очередь. Для этого требуется запустить службу очереди и внести изменения в существующую программу или контейнер, чтобы она использовала рабочую очередь. Другие подходы легче адаптировать к существующим контейнерным приложениям.

Расширенное использование. Указание собственного селектора pod

Обычно, когда вы создаете объект Job, вы не указываете .spec.selector. Логика системы по умолчанию добавляет это поле при создании Job. Он выбирает значение селектора, которое не будет перекрываться с другими Job.

Однако в некоторых случаях вам может потребоваться переопределить этот автоматически установленный селектор. Для этого вы можете указать .spec.selector для Job.

Будьте очень осторожны при этом. Если вы укажете селектор меток, который не является уникальным для Pod'ов этого Job, и который соответствует несвязанным Pod'ам, то могут быть удалены Pod'ы несвязанного Job, или этот Job может засчитывать другие Pod'ы как выполняющие его, или один или оба Job могут отказаться от создания Pod'ов или запускать до из завершения. Если выбран неуникальный селектор, то другие контроллеры (например, ReplicationController) и их Pod'ы могут вести себя непредсказуемым образом. Kubernetes не остановит вас от ошибки при указании .spec.selector.

Вот пример случая, когда вы можете использовать эту функцию.

Предположим, старый Job уже работает. Вы хотите, чтобы существующие Pod'ы продолжали работать, но вы хотите, чтобы остальные созданные им Pod'ы использовали другой шаблон Pod и чтобы Job получил новое имя. Вы не можете обновить Job, потому что эти поля не могут быть обновлены. Поэтому вы удаляете старый Job, но оставляете его Pod'ы работающими, используя kubectl delete jobs/old --cascade=false. Прежде чем удалить его, запишите, какой селектор он использует:

kubectl get job old -o yaml

kind: Job
metadata:
  name: old
  ...
spec:
  selector:
    matchLabels:
      controller-uid: a8f3d00d-c6d2-11e5-9f87-42010af00002
  ...

Затем вы создаете новый Job с именем new и явно указываете тот же селектор. Поскольку существующие Pod'ы имеют метку controller-uid=a8f3d00d-c6d2-11e5-9f87-42010af00002, они также контролируются Job с именем new.

Вам необходимо указать manualSelector: true в новом Job, поскольку вы не используете селектор, который система обычно генерирует для вас автоматически.

kind: Job
metadata:
  name: new
  ...
spec:
  manualSelector: true
  selector:
    matchLabels:
      controller-uid: a8f3d00d-c6d2-11e5-9f87-42010af00002
  ...

Сам новый Job будет иметь uid отличающийся от u8f3d00d-c6d2-11e5-9f87-42010af00002. Установка manualSelector: true говорит системе, что вы знаете, что делаете, и разрешите это несоответствие.

Альтернативы Job

Отдельные Pod'ы

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

Replication Controller

Job'ы дополняют Replication контроллеры. Контроллер Replication управляет Pod'ами, которые, как ожидается, не будут завершены (например, веб-серверы), а Job управляет Pod'ами, которые, как ожидается, завершат работу (например, пакетные задачи).

Как обсуждалось в Lifecycle Pod, Job подходит только для Pod'ов с RestartPolicy, равным OnFailure или Never. (Примечание: если RestartPolicy не установлен, значением по умолчанию является Always.)

Одиночный Job запускает контроллер Pod

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

Одним из примеров этого шаблона может быть Job, который запускает Pod, который запускает скрипт, который, в свою очередь, запускает контроллер Spark master, запускает драйвер spark, а затем убирается за собой.

Преимущество этого подхода состоит в том, что весь процесс получает гарантию завершения объекта Job, полный контроль над тем, какие Pod'ы создаются и как им назначается работа.

CronJob

Вы можете использовать CronJob для создания Job, которое будет запускаться в указанное время/дату, аналогично инструменту Unix cron.


Читайте также:


Комментарии

Популярные сообщения из этого блога

Контроллеры в Kubernetes: DaemonSet

Контроллеры в Kubernetes: ReplicaSet

Контроллеры в Kubernetes: StatefulSet