Однократные задачи в 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.

9 комментариев для “Однократные задачи в Kubernetes

  1. Паша, натыкаюсь уже на второй пост от тебя просто гуля про всякие тонкости в kubernetes. Мы тут GanttPRO в него перекатывем. И как раз все в тему. Просто написал, что-бы тебе было приятно 🙂

  2. Смотри, а вот такая ситуация:
    Есть node.js микросервис. В нем, допустим, есть функция, которую надо вызывать раз в час.
    Что-бы было нагляднее, например он забирает почту с imap и складывает во внешнюю базу.
    И у нас этого микросервиса replicas: 3. Т.е. получается 3 пода одновременно

    Раньше на виртуальной машине было просто, сам node.js шедулил себе эти задачи.
    А тут полуается каждый под начал шедулить сам себе и 3 раза забирет одно и тоже 3 раза. А надо только раз в час, не 3 раза в час. При этом этот микросервис не такой простой и совсем не микро и сложо из него выдернуть эту задачу в отдельный под. Как поступить.

    Хочется как-бы что-бы один под сказал, мол «Я сейчас главный, я сделаю таску, а вы, двас придурка по бокам, пока подождте. Но если я умру, один из вас должен меня заменить»

    Это вообще законно 🙂 или что делать?

    1. Смотри, реальная имплементация может оказаться проще, но согласно абстрактному учебнику микросервисов полагается разделять шедулинг и саму работу. А так как рабочих много, то должен появиться кто-то, кто эту работу распределит.
      Можно сделать так: кубернетовский Cronjob раз в час отправляет сообщение в очередь, на которую подписаны твои рабочие. Так как очередь — это не обычный Pub/Sub, а именно очередь, то сообщения не дублируются по подписчикам, и только один из рабочих сможет это сообщение забрать. То есть архитектура будет такая: Cronjob -> MQ -> Worker(s).
      Можно даже сделать ещё проще: Cronjob pod раз в час будет делать API вызов на сервис, который стоит перед твоими воркерами (curl http://my-worker-service.local/do-stuff). Так как сервис работает как load balancer, то только один под получит этот запрос.
      То есть есть как минимум два варианта:
      1. Cronjob -> [publish] -> Message Queue -> [receive] -> Worker pod (subscriber)
      2. Cronjob -> [http call] -> Workers service -> [load balanced] -> Worker pod

      Про очереди сообщений тут — https://dotsandbrackets.com/asynchronous-communication-with-message-queue-ru/ — в части «Очереди сообщений» можно посмотреть, какие софтины бывают, и с ходу я бы смотрел в сторону RabbitMQ. Но вводить MQ в твою задачу похоже на перебор, так что попробуй второй вариант. Здесь — https://dotsandbrackets.com/kubernetes-example-ru/ — я когда-то писал про сервисы и их load balancer. Вряд ли узнаешь что-то новое, но на всякий случай.

      1. Огромный респект! Я пока взахлеб погрузился в мир кубернетеса, сижу днями и ночами последние 2 недели, тыкаю туда сюда, деплою и смотрю на реакции подопытного )
        Кстати, когда ты отвачаешь на комммент по идее мнедолжно приходить письмо, но его не было
        Когда завершится аппокалипсис, приезжай и я тебе проставлю самое вкусное пиво за кубернетес и за Беларусь. Она тут жыве как никогда, людей как подменили 🙂

        1. Круть, буду ждать, пока зомбиэпидемия закончится 🙂
          Я посмотрю по почтовым настройкам, может какой чекбокс не проставил в вордпрессе. Не сильно обращал внимание на комменто-мыльные дела до сегодняшнего дня.
          Кстати, если секретный способ фокусно прошариться в кубернетес. Есть же профильные сертификации — Certified Kubernetes Administrator (CKA) и Certified Kubernetes Application Developer (CKAD). Возьми где-нибудь подготовительный курс по одному или обоим и вперёд. Я так к гугловым сертификациям на LinuxAcademy готовился. Всякие обучающие платформу по-началу как правило дают неделю бесплатно, и этого времени вполне хватает на подготовку. А на выходе получается чёткая картина мира.

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

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