Книга: Паттерны Kubernetes: Шаблоны разработки собственных облачных приложений
Назад: Часть II. Поведенческие паттерны
Дальше: Глава 8. Периодическое задание

Глава 7. Пакетное задание

Паттерн Batch Job (Пакетное задание) предназначен для управления изолированными атомарными единицами работы. Он основан на абстракции задания Job, предназначенной для запуска короткоживущих подов, действующих в распределенной среде и завершающихся самостоятельно.

Задача

Основным примитивом для запуска контейнеров и управления ими является под (Pod) — группа контейнеров. Существуют разные способы создания подов с различными характеристиками:

Простой под

Позволяет вручную создать под для запуска контейнеров. Однако когда узел, на котором выполняется такой под, выходит из строя, под не перезапускается. Запускать поды таким способом не рекомендуется, за исключением случаев разработки или тестирования. Этот механизм также известен под названиями неуправляемые, или голые, поды.

Набор реплик ReplicaSet

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

Набор демонов DaemonSet

Контроллер для запуска единственного пода на каждом узле. Обычно используется для управления механизмами платформы, такими как мониторинг, объединение журналов, хранилища и др. Подробнее о наборах демонов рассказывается в главе 9 «Фоновая служба».

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

Решение

Задание Job в Kubernetes напоминает набор реплик ReplicaSet — оно точно так же создает один или несколько подов и обеспечивает их выполнение. Однако, в отличие от набора реплик, после успешного завершения ожидаемого количества подов задание считается выполненным и дополнительные поды не запускаются. В листинге 7.1 показано, как выглядит определение задания.

Листинг 7.1. Определение задания Job

apiVersion: batch/v1

kind: Job

metadata:

  name: random-generator

spec:

  completions: 5                       

  parallelism: 2                       

  template:

    metadata:

      name: random-generator

    spec:

      restartPolicy: OnFailure         

      containers:

      - image: k8spatterns/random-generator:1.0

        name: random-generator

        command: [ "java", "-cp", "/", "RandomRunner",

                   "/numbers.txt", "10000" ]

Задание должно выполнить пять подов, и все они должны завершиться с признаком успеха.

Одновременно могут выполняться два пода.

В определениях заданий требуется обязательно задать параметр restartPolicy.

Одним из важных различий между заданием Job и определением набором реплик ReplicaSet является определение параметра .spec.template.spec.restartPolicy. Для набора реплик этот параметр получает значение по умолчанию Always (всегда), имеющее смысл для длительных процессов, которые должны выполняться постоянно. Значение Always недопустимо для заданий, и единственными возможными вариантами являются OnFailure (в случае сбоя) или Never (никогда).

Итак, почему для однократного запуска лучше создавать задание вместо простого пода? Задания дают много преимуществ в смысле надежности и масштабируемости, что делает их предпочтительным вариантом:

• Задание Job — это не эфемерный процесс в памяти, а постоянная задача, которая автоматически восстанавливается после перезапуска кластера.

• По завершении задание не удаляется, а сохраняется и доступно для трассировки. Поды, созданные как часть задания, также не удаляются и доступны для исследования (например, для исследования журналов контейнера). То же верно и для простых подов, но только если установлен параметр restartPolicy: OnFailure.

• Задание можно выполнить несколько раз. В параметре .spec.completions можно указать, сколько раз под должен успешно завершиться, чтобы задание было признано выполненным.

• Когда задание должно выполниться несколько раз (согласно значению параметра .spec.completions), его также можно масштабировать и одновременно запускать несколько подов. Это можно сделать, определив параметр .spec.parallelism.

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

Все это делает механизм заданий привлекательным для использования в сценариях, когда необходимы гарантии выполнения единицы работы.

Главные роли в поведении задания играют два параметра:

.spec.completions

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

.spec.parallelism

Определяет, сколько реплик пода может выполняться параллельно. Большое число в этом параметре не гарантирует высокого уровня параллелизма, и на самом деле количество одновременно выполняемых подов может быть меньше (а в некоторых тупиковых ситуациях больше) желаемого (например, из-за особенностей регулирования, ограниченности ресурсов, при приближении к числу .spec.completions и по другим причинам). Запись значения 0 в этот параметр эффективно приостанавливает задание.

На рис. 7.1 показано, как действует паттерн Batch Job (Пакетное задание), настройки которого приводились в листинге 7.1, со счетчиком выполнений, равным пяти, и уровнем параллелизма, равным двум.

Исходя из значений этих двух параметров, можно выделить следующие типы заданий:

Задание с одиночным подом

Этот тип выбирается, когда вы оставляете значение по умолчанию 1 в обоих параметрах, .spec.completions и .spec.parallelism. Это задание

593480.png 

Рис. 7.1. Параллельное выполнение заданий в паттерне Batch Job с фиксированным счетчиком выполнений

запускает только один под и завершается по окончании выполнения этого единственного пода с признаком успеха (с кодом 0).

Задание с фиксированным числом выполнений

Число в параметре .spec.completions определяет, сколько раз должен выполниться под и завершиться с признаком успеха. При желании можно также установить параметр .spec.parallelism или оставить в нем значение по умолчанию, равное единице. Такое задание считается выполненным после того, как под успешно завершится .spec.completions раз. Листинг 7.1 демонстрирует, как действует этот режим, который считается лучшим выбором, когда количество единиц работы известно заранее и стоимость обработки одной единицы оправдывает использование выделенного пода.

Рабочая очередь

Опустив параметр .spec.completions и присвоив параметру .spec.parallelism целое число больше единицы, вы получите рабочую очередь для параллельных заданий. Задание типа «рабочая очередь» считается выполненным, если все поды завершились и хотя бы один из них завершился с признаком успеха. В этом сценарии требуется, чтобы поды координировали свою работу друг с другом и определяли, над чем каждый будет работать. Например, когда в очереди имеется фиксированное, но неизвестное количество элементов для обработки, параллельно выполняющиеся поды могут выбирать их один за другим и обрабатывать. Первый под, обнаруживший, что очередь опустела, и завершившийся с признаком успеха, указывает на выполнение задания. Контроллер Job также ожидает завершения всех других подов. Поскольку один под может обработать несколько элементов, этот тип заданий является отличным выбором, когда накладные расходы на запуск одного пода для обработки одного элемента оказываются неоправданно высокими.

Для обработки неограниченного потока элементов лучше использовать другие контроллеры управления подами, такие как набор реплик ReplicaSet.

Пояснение

Абстракция задания Job — простой, но важный примитив, на котором основываются другие примитивы, такие как планировщик заданий CronJob. Задания помогают превратить изолированные единицы работы в надежную и масштабируемую единицу выполнения. Однако задание никак не определяет, как отдельные элементы для обработки должны отображаться в задания или поды. Это вы должны сделать сами, приняв во внимание плюсы и минусы каждого варианта:

Отдельное задание Job для каждой единицы работы

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

Одно задание на все единицы работы

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

Контроллер заданий Job реализует лишь самый минимум, необходимый для планирования единиц работы. Любая более сложная реализация должна объединять примитив Job с фреймворками пакетной обработки (такими, как Spring Batch и JBeret в экосистеме Java) для достижения желаемого результата.

Не все службы должны работать постоянно. Некоторые могут выполняться по требованию, некоторые – в определенные моменты времени, а некоторые — периодически. Использование заданий позволяет запускать поды только при необходимости и только на время выполнения задачи. Задания планируются на узлах, которые имеют необходимые ресурсы, удовлетворяют политикам размещения подов и другим зависимостям контейнера. Использование заданий для кратковременных задач вместо долгоживущих абстракций (таких, как ReplicaSet) экономит ресурсы платформы. Все это делает задания Job уникальным примитивом и помогает платформе Kubernetes поддерживать разные виды рабочей нагрузки.

Дополнительная информация

• Пример пакетного задания (http://bit.ly/2Jnloz6).

• Задания Job как средство выполнения единиц работы (http://bit.ly/2W1ZTW2).

• Параллельная обработка с использованием заданий (http://bit.ly/2Y563GL).

• Грубая параллельная обработка с использованием рабочей очереди (http://bit.ly/2Y29cqS).

• Тонкая параллельная обработка с использованием рабочей очереди (http://bit.ly/2Obtutr).

• Создание индексируемого задания с помощью метаконтроллера (http://bit.ly/2FkjQSA).

• Фреймворки и библиотеки Java для пакетной обработки (https://github.com/jberet).

Назад: Часть II. Поведенческие паттерны
Дальше: Глава 8. Периодическое задание