Однократные задачи в Kubernetes

Задачи в KubernetesПока что все примеры, что я делал для Docker Swarm и Kubernetes постов, строились вокруг какого-нибудь сервиса: веб-сервера, очереди сообщений или шины сообщений. В принципе, оно и не удивительно, весь тот же Docker Swarm построен вокруг концепции сервисов. Да что там Swarm, сами «микро-сервисы», из-за которых мы контейнеры в облака забрасываем, ни о чём не говорят?

Но не всё есть «сервис» в облаках. Есть и эпизодические задачи: рутинное обслуживание, запланированные задачи, конечные вычислительные таски — всё, у чего есть начало и вполне определённый конец.

Например, возьмём юнит тесты. Если у меня есть набор тестов, который выполняется час, то его можно раскидать по шестидесяти контейнерам, забросить кластер, и надеяться, что теперь они выполнятся за минуту. Вполне ведь валидная задача, и тоже не сервис.

Провернуть что-нибудь подобное в Docker Swarm можно, но нужно было бы немного заморочиться. Как минимум, отключить перезапуск завершившихся контейнеров сервиса, и перестать передёргиваться используя команду docker service create.

А в Kubernetes такая штука делается запросто. Там можно как отправлять одиночные поды в кластер, так и создавать специальные контроллеры для них, вроде Job или Cron Job. На всё это богатство выбора мы сегодня и посмотрим.

Подготовка

Чтобы сделать всё это самостоятельно понадобятся только VirtualBoxminikube и kubectl. Ну или доступ к Google Container Engine. Подробности установки я уже описывал раньше, так что лучше мы перейдём сразу к делу.

Поды-работяги (pods)

Как я уже говорил, поды с задачами можно забрасывать прямо в кластер. Допустим у нас появилась странная одержимость математикой и мы решили найти все простые числа между единицей и семидесятью при помощи bash скрипта и почему-то в Kubernetes. Такое случается. И если следующая стрёмная строка скрипта сделает математическую часть:

…то эта конфигурация пода сможет отправить её в кластер:

Это ничем не примечательный под, единственной особенностью которого является отключённая реинкарнация (последняя строка). Теперь его можно забросить в кластер через kubectl create -f pod.yml, подождать, пока он запустится, а затем ловить результаты, которые под заботливо сбрасывает в STDOUT:

Как только задача завершится, kubectl get pod больше не будет возвращать нашего работягу — он же завершился. Но при этом он никуда и не делся. Завершённые контейнеры можно найти при помощи  --show-all параметра.

У такого подхода к запуску задач в кластере есть куча недостатков. Во-первых, если во время работы случится что-то нехорошее с хостом, на котором под запущен, то что-то нехорошее случится и с самим подом. А ведь было бы хорошо, если бы кто-то перезапустил его на новой машине.

Во-вторых, bash находит простые числа отвратительно медленно. Как можно ускорить процесс? Нет, не переписать всё на C. Можно разбить диапазон 1..70 на три меньших диапазона, и начать искать простые числа в них параллельно. Например, запустить очередь сообщений, положить диапазоны туда, и натравить на неё стайку подов. Но ведь все эти поды придётся запускать руками. А что если их сотня?

С другой стороны, есть Job контроллеры.

Jobs

Job — это такой специальный контроллер, который создаёт и отслеживает поды, выполняющие какую-то конечную задачу. Если под или хост под ним упадут с ошибкой, то Job перезапустит pod где-нибудь ещё. Кроме этого у него есть свойство parallelism , которым можно контролировать, сколько именно подов будут работать над задачей, и ещё Job можно сразу сказать, сколько подов должны успешно завершиться (completions), прежде чем вся задача будет считаться сделанной.

Превратить под-работягу в job-работягу — проще-простого:

По сути я просто сделал заголовок задачи и скопи-пастил pod в её template секцию. Но чтобы сделать пример немного интереснее, можно разрешить запуск аж четырёх параллельных подов, и запускать их до тех пор, пока мы не получим 8 успешных результатов.

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

Теперь создаём Job, даём ей немного времени на запуск, и смотрим, что происходит с подами:

Поды действительно запускаются параллельно: 5 уже завершилось, один работает и ещё два только запускаются.

С параметрами parallelism и completions можно было бы и поэксперементировать. Например, если убрать parallelism  вовсе, то получилось бы 8 подов, запускаемых один за другим. А если убрать completions, то Job запустил бы четыре параллельных пода, а после их завершения завершился бы и сам.

Cron Jobs

Подобно обычному Job, Cron Job тоже запускает поды с работой. В отличие от Job, он делает это по расписанию. Самая интересная его настройка — свойство schedule, которое использует тот же формат записи, что и обычный линуксовый cron.

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

Сделать это — вообще элементарно: я просто скопирую описание предыдущей задачи в jobTemplate секцию Cron Job и задам «раз в минуту»:

И всё. Теперь задачу можно создавать и любоваться на поды, как и в прошлый раз.

Визуально всё практически один в один. Только кладбище завершённых подов в системе будет расти со скоростью +8 штук в минуту.

Итого

Как видно, Kubernetes не просто умеет обращаться с однократными задачами, у него есть аж три способа, как это сделать. Для мелких и редких задач можно просто положить их в pod, закинуть прямо в кластер, и забирать результат при помощи kubectl logs %pod-name%. Для задач, которые имеет смысл параллелить или хотя бы быть уверенным, что их перезапустят в случае ЧП, можно воспользоваться Job контроллером. Наконец, если в задаче маячит слово «расписание», то это определённо Cron Job.

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *