Русский
Русский
English
Статистика
Реклама

Cronjob

Когда Cron подводит

30.11.2020 10:17:56 | Автор: admin

Привет!

У нас есть огромные ночные отчёты попродуктовым метрикам, которые с утра попадают их владельцам. Пару лет назад они собирались обычным Cron'ом, но это оказалось очень нестабильной историей. Cron требует учёта определенных нюансов и дисциплины: если отчёт не собрался, то нужно перезапустить скрипт.А не собраться он может по многим причинам: одна из MySQL-баз не ответила, или не ответил опрашиваемый сервис, или сервер, на котором работает Cron, ночью перезагружался или вышел из строя.В большинстве своём такие отчеты создавались аналитиками, которые обычно всего этого не учитывали. Потому что они аналитики и это не их задача.Поэтому мы начали искать варианты, которые упростят жизнь всем.

И нашли.

В финальном решении используется планировщик Airflow с его возможностью взаимодействия с кластером Kubernetes. Система получилась гибкая и надежная и упрощает жизнь конечным потребителям.Для разработки таких систем есть мы, техническая команда из четырёх человек, называемая Data Team, а конечные потребители аналитики,продакт-менеджеры и техлиды.

Что и как считается

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

Какие были проблемы

В далёкие времена все отчёты делали разработчики продуктов. Нужны продукту отчёты их делает его разработчик.Это был плохой подход:создание нового отчёта было медленным и дорогим, так как приходилось отвлекать разработчиков от реализации продуктовых фичей. Кроме этого, отчёты обитали в production-окружении, использовали боевые базы, создавая на них нездоровую ночную нагрузку. Постепенно отчётами стали заниматься аналитики, в распоряжении которых были реплики боевых баз данных. Помимо реплик для них постепенно наливалось озеро данных, которое для отчётов было даже удобнее. Такой подход оказался благоприятным и привел к экспоненциальному росту количества отчетов.

Когда отчёты стали быстрыми в разработке, продакт-менеджеры захотели очень много всего: разные отчёты с разной регулярностью. Аналитики оперативно собирали файлик с логикой на python, скармливали его Cron'у и уходили. Никто не думал о ревью, хороших практиках написания и сопровождения отказоустойчивого кода. И это неудивительно, так как аналитики хороши в анализе данных, а писать поддерживаемый код задача разработчиков. В итоге отчёты были нестабильными: либо не собирались вовсе, либо, что ещё хуже, собирались частично и с вероятно недостоверными показателями.

DataTeam решила разобраться со всеми проблемами на уровне инфраструктуры. Путь был тернистым, но результат стоит проделанной работы.

Примечание: Далее контекст повествования уходит от отчётов и будет использоваться термин "задача", под которым понимается любая атомарная логика, которая может быть запущена по расписанию.

С чего все начиналось

Запустить что-то в определённое время или определённое количество раз за некий интервал времени очень частая необходимость. Первый, самый очевидный и, скорее всего, подходящий вариант использовать планировщик операционной системы. Как правило,это инструмент, который прост инадёжен, как топор. Если, конечно,вы не решили забивать обухом гвозди и лезвие не проносится в миллиметрах от вашей головы. Тот же Cron работает на одном сервере и встроен в ОС. Для запуска задачи по расписанию нужно только завести исполняемый скрипт с логикой задачи и сообщить планировщику, что этот скрипт следует запускать по расписанию.

Но сервер с Cron'ом состоит из одного узла. Сервер может выйти из строя в момент выполнения задачи или, что вероятнее, его просто отключили для проведения каких-либо профилактических работ. А у Cron'а нет механизма резервирования состояния запущенных задач. После восстановления сервера задача, выполнение которой прервалось, не будет перезапущена. Остается только ждать следующего запуска по расписанию или запускать руками.

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

Повышаем надежность системы

Ограничения, вызванные ненадёжностью одиночных серверов (не только в вопросах регулярного запуска задач), давно решаются кластерными системами. У нас есть кластер Kubernetes'а.Там свой собственный планировщик,который называется CronJob. Из названия можно предположить, что принципиально он ничем не отличается от классического Cron'а. Но это не так:в первую очередь,CronJob запущен в кластере, и выход из строя одного узла не помешает запуску задач по расписанию. Для этого выйти из строя должен весь кластер, что тоже возможно, но менее вероятно. Помимо этого, Cron гарантирует запуск задачи строго один раз в указанный в расписании момент времени, согласно системным часам сервера. CronJob обещает запустить задачу "примерно" один раз согласно указанному расписанию (из документации:A cron job creates a job objectaboutonce per execution time of its schedule.Подробнее). "Примерно" означает, что в момент предполагаемого запуска задача может быть запущена более одного раза или не запущена вообще. Такое ограничение обязывает делать задачи идемпотентными. ЕщёCronJob позволяет определять политики перезапуска и конкурентного запуска задач. Про это так жеможно прочитать в документации.

Кажется, с появлением кластераKubernetes'а гарантии выполнения задач строго по расписанию всё ещё нет. Это не значит, чтоCronJob бесполезный, мы им достаточно активно пользуемся для запуска небольших атомарных задач, которые необязательны к выполнению с точностью швейцарских часов. Если задача не запустилась сейчас ничего критичного, запустится в следующий раз. Но есть задачи с более высокими требованиями к запуску, когда нужны гарантии, что задача запустится хотя бы один раз согласно расписанию, пусть и с некоторыми погрешностями во времени. Более того, запуск задачи должен зависеть от выполнения определённых предусловий, например, нужно убедиться, что какие-то предыдущие задачи, которые подготавливают данные для текущей, выполнились успешно.

Для соблюдения таких требований существуют продвинутые планировщики, которые являются отдельными самостоятельными инструментами планирования. В нашем случае это Airflow.

Airflow

Его уже разбирали в разных публикациях (например, вэтой). Arflow мы используем давно, хоть и не на полную мощь. Основной сценарий выстроить граф зависимостей между задачами (направленный ациклический граф или directed acyclic graph).

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

Для нас основное удобство в его использовании это отсутствие привязки к платформе. Упомянутые выше Cron и CronJob работают исключительно в рамках установленных платформой границах. Для Cron'а это физический сервер, на котором он запускается, для CronJob кластер Kubernetes'а.В свою очередь, Airflow запущен как отдельный сервис и может управлять запусками задач и в отдельно стоящих серверах, и в кластерахKubernetes'а, и даже где-то в облаках. В нашем случае он занимается только оркестрацией и на своих мощностях задачи не выполняет, хотя и такое возможно. При определённой степени неаккуратности Airflow может стать маленьким монолитом, на который завязаны запуски всех задач, имеющихся в компании. В таком случае каждая из запускаемых задач может вывести из строя сервер и до устранения проблемы все задачи выполняться не будут.

Мы от такого исхода защищены, часто логика тяжёлых задачах у нас реализована в виде отдельных сервисов, запускаемых в Kubernetes'е. Airflow предоставляетудобный механизм запускаподов, точнее, даже два: KubernetesPodOperator и KubernetesExecutor. Мы используем KubernetesPodOperator: для запуска пода необходимо иметь собранный docker-образ сервиса в кластере Kubernetes'а.

Оператор использует официальныйkubernetes-clientк API Kubernetes'а, что даёт возможность гибко конфигурировать запускаемые поды со стороны Airflow.В кластере можно завести configmap'ы или секреты, использование которых также указывается в операторе. У KubernetesExecutor'а другое предназначение: он позволяет динамически расширять мощности Airflow за счёт запуска подов, в которых будут выполняться различные операторы или набор операторов, выстроенных в ациклический граф.

Связка Airflow + KubernetesPodOperator + Kubernetes подходит нам по функциональности и надёжности планирования и запуска задач, позволяя спать ночами спокойно, зная, что в это время в меру дотошный и исполнительный Airflow проконтролирует выполнение всей необходимой работы на мощностях Kubernetes, который может быть запущен как и на соседней серверной стойке, так и в другом дата-центре или на стороне облачного провайдера.

Альтернатива KubernetesPodOperator'у

Отсутствие Kubernetes'а или даже Docker'а не ставит крест на возможностях Airflow.Какое-то время мы активно пользовались SimpleHttpOperator'ом или PythonOperator'ом, делаяhttp-запросы на урлы сервиса, запуская тем самым логику задач. Такой вариант тоже имеет право на существование, но у него есть недостатки, из-за которых мы от него решили отказаться. Первый недостаток все прокси или балансировщики перед сервисом требуют донастройки, потому что иначе запрос будет отваливаться по тайм-аутам. Этого можно избежать, сделав запуск асинхронным: запустили и, не дожидаясь окончания, считаем, что дело сделано. Но при таком подходе нет возможности использовать механизм перезапуска не выполнившихся задач Airflow. Второй недостаток из-за задачи, которая запускается раз в день или вовсе раз в неделю, приходится иметь запущенный сервис, который бОльшую частьвремени просто занимает ресурсы, не выполняя полезную работу.

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

И снова немного об отчетах

У отчётов есть один явный недостаток: они статичны и для внесения изменений необходимо править генерирующий их скрипт. С ростом объёма и качества озера данных мы стали отказываться от подхода с генерацией отчётов. Заинтересованные в продуктовых метриках лица используют BI-систему Metabase, которая предоставляет гибкий и удобный интерфейс доступа к данным в аналитическом хранилище. Но это уже совсем другая история (с).

А связкаAirflow + KubernetesPodOperator + Kubernetes продолжает активно использоваться для разного рода технических задач.

Что делать, если возникла мысль "Хочу так же!"?

Действия простые:

  1. определиться с набором текущих проблем;

  2. понять, какие именно проблемы решит усложнённая инфраструктура;

  3. ещё раз подумать, точно ли хотите;

  4. засучив рукава, начать затаскивать в окружение недостающие инструменты.
    Повторюсь, что пользу от использования Airflow можно извлечь, не имея в эксплуатации контейнеров и/или систем их оркестрирования;

  5. пробовать, натыкаться на грабли и подводные камни, наращивать экспертизу;

  6. вернуться к пункту 5.

А если тема окажется злободневной, мы поделимся своим опытом и в формате "how to" подробно расскажем про реализацию разных связок сAirflow и про проблемы, с которыми мы сталкивались.

Подробнее..

Проблемы мониторинга дата-пайплайнов и как я их решал

16.06.2021 00:20:01 | Автор: admin

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

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

Чтобы понять, как все это мониторить, давайте разберемся кто вообще работает с данными, которые мы с таким трудом намайнили:

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

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

Код - запускается следующий в цепочке пайплайн, происходят преобразования, рисуются графики и т.п.

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

Вот примеры:

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

  • Пайплайн стучится в источник с надеждой получить свежие данные, которые появляются в API источника с неопределенной задержкой (привет Apple), пайплан может успешно завершиться как скачав отчет, так и получив сообщение о том, что отчета еще нет. Тут, конечно, можно делать бесконечные ретраи внутри, либо просто ронять пайплайн, но тогда не особо очевидно будет, по какой причине он упал - что-то пошло не так, или данные в источнике еще не подтянулись.

  • Пайплайн работает слишком долго - это может быть как разовой проблемой из-за тормозов в сети или в источнике, так и постоянной проблемой: что-то сломалось в трансформации, или ретраев стало больше чем нужно, в любом случае требуется вмешательство человека.

ETL как он естьETL как он есть

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

Чего не хватает во встроенных мониторингах систем работы с данными:

  • Бизнес не может просто посмотреть в модный мониторинг типа того же Airflow или ELK и понять, можно или нельзя доверять данным, актуальность состояния данных непрозрачна.

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

  • Нет возможности собрать реальную статистику и проанализировать, где самое слабое место. Просто считать инциденты не вариант, т.к. важнее не количество инцидентов, а время, в течение которого состояние данных было неактуальным. Знаю, что такие проблемы некоторые компании решают самописными парсерами логов или подсчетом пиков-просадок на графиках (напишите, как это реализовано у вас).

Все это превращается в такие вот проблемы:

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

  2. Люди из бизнеса хотят более наглядного отображения состояния данных и системы, чем оно представлено в технических мониторингах.

  3. Статистика, если и собирается, то собирается по техническим проблемам и нельзя понять, насколько эти технические проблемы повлияли на бизнес.

Концепция

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

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

Почему вообще вебхуки?

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

После того, как мы начали отсылать из процессов события, нужно их проанализировать и получить ответы на всякие важные вопросы, например (все числа абстрактны):

  • запустилась ли наша задача 10 раз за последний день?

  • не превышает ли количество падений (определяем падение, если полученное значение > 0, например) 15% от всех запусков за сегодня?

  • нет ли процессов, которые длятся больше 20 минут?

  • не прошло ли больше часа с момента последнего успешного завершения?

  • стартовало ли событие по планировщику в нужное время?

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

Реализация

Я начал с дашборда, дизайн - не моя профессия, так что просто взял за основу дашборд, показывающий состояние крон-джобов на сайте Nomadlist, у меня получилось как-то так:

Дашборд состояния серверов Sensorpad средствами SensorpadДашборд состояния серверов Sensorpad средствами Sensorpad

Каждый монитор на этом дашборде - это комбинация метрик, которые должны быть понятны и бизнесу, и инженерам.

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


Для инженера тут все понятно:

  • скрипт отрабатывает быстро (еще бы, простая крон-джоба);

  • монитор вполне живой, 25 минут назад обновился;

  • места еще с запасом (цифра 53 в левом нижнем углу - это последнее принятое значение);

Для людей из бизнеса тут тоже все просто:

  • монитор зеленый;

  • статус прописан в первой же строчке;

  • никакой лишней информации;

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

И насколько просто такое настроить?

  1. Создаем вебхук в самом сервисе, они там называются сенсорами, по аналогии со всякими штуками из физического мира.

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

    df -h |grep vda1 | awk  '{ print $5 }'| sed 's/.$//' | xargs -I '{}' curl -G "https://sensorpad.link/<уникальный ID>?value={}" > /dev/null 2>&1
    
  3. Присоединяем к этому вебхуку монитор, называем его: количество свободного места (но можно еще и другие, например, то, что события уходят по графику означает, что сервер не упал)

  4. Настраиваем правила, по которым монитор меняет свой статус.

  5. Присоединяем каналы для отправки уведомлений.

  6. Добавляем монитор на один или несколько дашбордов.

А можно поподробнее?

Для вебхуков я пока что сделал саму простую имплементацию:

  • базовый вебхук, который будет нужен для 80% проектов;

  • cron-вебхук, который ожидает события в заданное через cron-синтаксис время;

  • chain-вебхук, который умеет отслеживать события от процессов, соединенных в цепочки;

главное в нашем деле - не усложнять интерфейсыглавное в нашем деле - не усложнять интерфейсы

После создания попадаем на страницу сенсора, тут автоматически появляются полученные события (повозился в js) и кнопочкой можно отсылать тестовые события, для тех, у кого не установлен Curl или кому лень делать это из браузера:

Догфудинг в действииДогфудинг в действии

Дальше создаем тот самый монитор - квадратик, меняющий статус и цвет.

Можно даже иконку выбратьМожно даже иконку выбрать

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

Теперь, собственно то, из-за чего я и написал эту балалайку: правила и гибкая логика.

К каждому монитору можно прицепить определенное количество правил, дальше движок мониторинга проверяет все правила раз в пару секунд. Правила проверяются поочередно, как только одно из правил вернет значение True, статус монитора изменится и остальные правила проверяться не будут. Если никакое из правил не сработало, статус монитора примет значение по умолчанию - то самое, которое задавалось при создании монитора.

Правила для мониторинга места на дискеПравила для мониторинга места на диске

На скриншоте выше видно уже созданные правила, но я покажу как они создаются.

Например правило, которое можно сформулировать так: "установи статус Warning, если за последний день было больше 5 джоб, которые работали дольше 10 секунд".

А вот какие вообще можно выбирать проверки в каждом из пунктов:

И какие реальные кейсы можно покрыть этими правилами?

У каждого свои кейсы. Дата-инженерия вообще весьма специфичное для каждой компании направление. Если у вас есть дата-пайплайны или cron jobs, сервис оповестит вас, если (все цифры, разумеется, конфигурируемы):

  • Cron job, Airflow DAG или любой другой процесс не запустился по расписанию;

  • 20% задач одного и того же пайплайна за день не отработали как надо;

  • связанная задача в пайплайне не запустилась через 2 минуты после окончания родительской задачи;

  • интервал между запусками двух задач меньше 1 минуты (похоже, у нас две конкурентные джобы);

  • с момента последнего успешного завершения пайплайна прошло 2 часа (а данные должны считаться каждый час);

  • время работы пайплайна уже целых 20 минут (а должен был отработать за 5, что-то подвисло).

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

А теперь - статистика!

Красивые графики любят все. Крутость sensorpad в том, что он показывает в статистике реальные состояния систем, а не просто количество уведомлений (их, кстати, тоже показывает). А уже с этими данными можно разгуляться: и самому поковырять, чтобы понять во что вложить силы и что нужно оптимизировать, да и стейкхолдерам не грех показать идеально зеленый график.

Немного полезных и не очень графиковНемного полезных и не очень графиков

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

Вот такой концепт. Чего не хватает?


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

Потыкайте его вживую, заодно зацените, какой я у мамы дизайнер лендингов: https://sensorpad.io

Подробнее..

Продвинутые абстракции Kubernetes Job, CronJob

05.11.2020 04:10:16 | Автор: admin


Что такое Job и CronJob в Kubernetes, для чего они нужны, а для чего их использовать не стоит.
Эта статья выжимка из лекции вечерней школы Слёрм Kubernetes.


Job: сущность для разовых задач


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


Когда используют Job


При установке и настройке окружения. Например, мы построили CI/CD, который при создании новой ветки автоматически создаёт для неё окружение для тестирования. Появилась ветка в неё пошли коммиты CI/CD создал в кластере отдельный namespace и запустил Job тот, в свою очередь, создал базу данных, налил туда данные, все конфиги сохранил в Secret и ConfigMap. То есть Job подготовил цельное окружение, на котором можно тестировать и отлаживать новую функциональность.


При выкатке helm chart. После развёртывания helm chart с помощью хуков (hook) запускается Job, чтобы проверить, как раскатилось приложение и работает ли оно.


Таймауты, ограничивающие время выполнения Job


Job будет создавать поды до тех пор, пока под не завершится с успешным результатом. Это значит, что если в поде есть ошибка, которая приводит к неуспешному результату (exit code не равен 0), то Job будет пересоздавать этот под до бесконечности. Чтобы ограничить перезапуски, в описании Job есть два таймаута: activeDeadlineSeconds и backoffLimit.


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


Например, если указать в Job, что activeDeadlineSeconds равен 200 сек., а наше приложение падает с ошибкой через 5 сек., то Job сделает 40 попыток и только после этого остановится.


backoffLimit это количество попыток. Если указать 2, то Job дважды попробует запустить под и остановится.


Параметр backoffLimit очень важен, потому что, если его не задать, контроллер будет создавать поды бесконечно. А ведь чем больше объектов в кластере, тем больше ресурсов API нужно серверам, и что самое главное: каждый такой под это как минимум два контейнера в остановленном состоянии на узлах кластера. При этом поды в состоянии Completed или Failed не учитываются в ограничении 110 подов на узел, и в итоге, когда на узле будет несколько тысяч контейнеров, докер-демону скорее всего будет очень плохо.


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


Удаление Job


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


В Kubernetes есть специальный TTL Controller, который умеет удалять завершенные Job вместе с подами. Вот только он появился в версии 1.12 и до сих пор находится в статусе alpha, поэтому его необходимо включать с помощью соответствующего feature gate TTLAfterFinished.


ttlSecondsAfterFinished указывает, через сколько секунд специальный TimeToLive контроллер должен удалить завершившийся Job вместе с подами и их логами.


spec:  ttlSecondsAfterFinished: 100

Манифест


Посмотрим на пример Job-манифеста.


apiVersion: batch/v1kind: Jobmetadata:  name: hellospec:  backoffLimit: 2  activeDeadlineSeconds: 60  template:    spec:      containers:      - name: hello        image: busybox        args:         - /bin/sh        - -c        - date; echo Hello from the Kubernetes cluster      restartPolicy: Never

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


В спецификации указаны таймауты и темплейт пода, который будет запускаться. Опции backoffLimit: 2 и activeDeadlineSeconds: 60 значат, что Job будет пытаться выполнить задачу не более двух раз и в общей сложности не дольше 60 секунд.


template это описание пода, который будет выполнять задачу; в нашем случае запускается простой контейнер busybox, который выводит текущую дату и передаёт привет из Kubernetes.


Практические примеры


kubectl apply -f job.yaml

И посмотрим, что получилось.


k get pod -w

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


Статистику по Job можно посмотреть следующей командой.


kubectl get job

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


Ненужный Job обязательно надо удалять. Потому что, если мы не удалим его руками, Job и под будут висеть в кластере всегда никакой garbage collector не придёт и не удалит их.


Если у вас запускаются по 10-20 заданий в час, и никто их не удаляет, они копятся и в кластере появляется много абстракций, которые никому не нужны, но место занимают. А как я уже говорил выше, каждый под в состоянии Completed это, как минимум, два остановленных контейнера на узле. А докер демон начинает притормаживать, если на узле оказывается несколько сотен контейнеров, и не важно, работают они или остановлены.


Команда для удаления:


kubectl delete job hello

Что будет, если сломать Job


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


Поправим yaml: добавим в темплейт контейнера exit 1. То есть скажем Jobу, чтобы он завершался с кодом завершения 1. Для Kubernetes это будет сигналом о том, что Job завершился неуспешно.


    containers:      - name: hello        image: busybox        args:         - /bin/sh        - -c        - date; echo Hello from the Kubernetes cluster; exit 1      restartPolicy: Never

Применяем и смотрим, что происходит: один контейнер создался и упал с ошибкой, затем ещё и ещё один. Больше ничего не создаётся.



В статистике подов видим, что создано три пода, у каждого статус Error. Из статистики Job следует, что у нас создан один Job и он не завершился.



Если посмотреть описание, то увидим, что было создано три пода, и Job завершился, потому что был достигнут backoffLimit.



Обратите внимание! В yaml лимит равен 2. То есть, если следовать документации, Job должен был остановиться после двух раз, но мы видим три пода. В данном случае после выполнения двух раз значит 3 попытки. Когда мы проделываем то же самое на интенсиве с сотней студентов, то примерно у половины создаётся два пода, а у оставшихся три. Это надо понять и простить.


Проверка ограничения по времени


Сделаем бесконечный цикл и посмотрим, как работает ограничение по времени activeDeadlineSeconds.


Ограничения оставим теми же (60 секунд), изменим описание контейнера: сделаем бесконечный цикл.


    containers:      - name: hello        image: busybox        args:         - /bin/sh        - -c        - while true; do date; echo Hello from the Kubernetes cluster; sleep 1; done      restartPolicy: Never

Под запустился.



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


Через 60 секунд под оказывается в статусе Terminating (иногда это происходит через 10 сек).



Вспомним, как в Kubernetes реализована концепция остановки подов. Когда приходит время остановить под, то есть все контейнеры в поде, контейнерам посылается sigterm-сигнал и Kubernetes ждёт определённое время, чтобы приложение внутри контейнера отреагировало на этот сигнал.
В нашем случае приложение это простой bash-скрипт с бесконечным циклом, реагировать на сигнал некому. Kubernetes ждёт время, которое задано в параметре graceful shutdown. По дефолту 30 секунд. То есть если за 30 секунд приложение на sigterm не среагировало, дальше посылается sigkill и процесс с pid 1 внутри контейнера убивается, контейнер останавливается.


Спустя чуть более 100 секунд под удалился. Причем ничего в кластере не осталось, потому что единственный способ остановить что-то в контейнере это послать sigterm и sigkill. После этого приходит garbage collector, который удаляет все поды в статусе Terminating, чтобы они не засоряли кластер.


В описании Job мы увидим, что он был остановлен, так как активность превысила допустимую.



Поле restartPolycy


При проверке backoffLimit поды у нас перезагружались. При этом в манифесте указан параметр restartPolycy: Never. Но когда мы смотрели, как работает опция backoffLimit, поды перезагружались. Здесь нет противоречия: если вы посмотрите на весь yaml-файл, то заметите, что этот параметр относится не к Job, а к спецификации контейнера, который запускается внутри пода.


apiVersion: batch/v1kind: Jobmetadata:  name: hellospec:  backoffLimit: 2  activeDeadlineSeconds: 60  template:    spec:      containers:      - name: hello        image: busybox        args:         - /bin/sh        - -c        - date; echo Hello from the Kubernetes cluster      restartPolicy: Never

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


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


Если вы укажете только backoffLimit, но забудете указать restartPolicy, то Job будет выполняться бесконечно.
Поэтому в Job надо всегда указывать:
  • backoffLimit (количество попыток),
  • activeDeadlineSeconds (общее время),
  • restartPolycy: Never (сказать kubelet, чтобы он никогда не перезапускал контейнер в поде; если контейнер в поде упал, то и сам под считается упавшим, то есть завершённым. Пусть Job-контроллер разбирается, что произошло).

Инструкция по Jobам в документации Kubernetes


CronJob: создание объектов Job по расписанию


Job позволяет выполнить разовые задачи, но на практике постоянно возникает потребность выполнять что-то по расписанию. И вот здесь Kubernetes предлагает CronJob.


CronJob это yaml-манифест, на основании которого по расписанию создаются Jobы, которые в свою очередь создают поды, а те делают полезную работу.


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


В манифесте CronJob указывают расписание и ещё несколько важных параметров.


  • startingDeadlineSeconds,
  • concurrencyPolicy.

И два параметра, которые влияют на историю выполнения.


  • successfulJobsHistoryLimit,
  • failedJobsHistoryLimit.

Посмотрим на манифест CronJob и поговорим о каждом параметре подробнее.


apiVersion: batch/v1beta1kind: CronJobmetadata:  name: hellospec:  schedule: "*/1 * * * *"  concurrencyPolicy: Allow  jobTemplate:    spec:      backoffLimit: 2      activeDeadlineSeconds: 100      template:        spec:          containers:          - name: hello            image: busybox            args:            - /bin/sh            - -c            - date; echo Hello from the Kubernetes cluster          restartPolicy: Never

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


concurrencyPolicy этот параметр отвечает за одновременное выполнение заданий. Бывает трёх видов: Allow, Forbid, Replace.


Allow позволяет подам запускаться. Если за минуту Job не отработал, все равно будет создан ещё один. Одновременно могут выполняться несколько Jobов.


Например, если один Job выполняется 100 сек., а Cron выполняется раз в минуту, то запускается Job, выполняется 61 сек., в это время запускается ещё один Job. В итоге в кластере одновременно работают два Joba, которые выполняют одну и ту же работу. Возникает положительная обратная связь: чем больше Jobов запущено, тем больше нагрузка на кластер, тем медленнее они работают, тем дольше они работают и тем больше одновременных подов запускается в итоге всё застывает под бешеной нагрузкой.


Replace заменяет запущенную нагрузку: старый Job убивается, запускается новый. На самом деле это не самый лучший вариант, когда прерываем то, что уже выполнялось, и начинаем ту же самую задачу выполнять заново. В каких-то случаях это возможно, в каких-то неприемлемо.


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


jobTemplate это шаблон, из которого создаётся объект Job. Ну а всё остальное мы уже видели в манифесте Job.


Применим манифест.


kubectl apply -f cronjob.yaml

Посмотрим, что получилось:


k get cronjobs.batch

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



Раздел Suspend временная приостановка CronJob. В данном случае указано значение False. Это значит, что CronJob выполняется. Можно отредактировать манифест и поставить опцию True, и тогда он не будет выполняться, пока мы его снова не запустим.


Active сколько Jobов создано, Last Schedule когда последний раз исполнялся.


Теперь можно посмотреть статистику по Jobам и подам.


kubectl get job

Видно, что создан один Job.


kubectl get pod

Под создан, он выполнил полезную работу.



Что получается: CronJob создал Job, Job создал под, под отработал, завершился всё здорово.


Ещё раз посмотрим на CronJob:



Last Schedule был 19 секунд назад. Если посмотреть на Job, то увидим, что у нас появился следующий Job и следующий под.


Возникает вопрос: а что будет, если CronJob отработает хотя бы пару недель? Неужели у нас в кластере будет столько же Jobов и подов в статусе Completed, сколько в этой паре недель минут?


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


Снова откроем манифест и посмотрим, что было добавлено:


k get cronjobs.batch hello -o yaml



Появились опции failedJobHistorLimit со значением 1 и successfulJobHistoryLimit со значением 3. Они отвечают за количество Jobов, которые остаются одновременно в кластере. То есть CronJob не только создаёт новые Jobы, но и удаляет старые.


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


И на сладкое про startingDeadlineSeconds


В параметре startingDeadlineSeconds указывают количество секунд, на которое можно просрочить запуск Job. Если по каким-то причинам Job не создался и с момента, когда его надо было создать, прошло больше секунд, чем указано в этом параметре, то он и не будет создан. А если меньше, то хоть и с опозданием, Job будет создан.


Тут есть небольшая ловушка, если concurrencyPolicy разрешают одновременное создание Job, то при большом значении параметра startingDeadlineSeconds возможен одновременный запуск десятков пропущенных Job одновременно. Для уменьшения всплеска нагрузки в код Kubernetes захардкожены лимиты и запрещающие процедуры:


Если параметр startingDeadlineSeconds не указан в манифесте:
CronJob контроллер при создании Job смотрит на время последнего запуска значение LastscheduleTime в status: и считает, сколько времени прошло с последнего запуска. И если это время достаточно велико, а точнее за этот промежуток времени CronJob должен был отработать 100 раз или больше, но у нее этого не получилось:


  • например конкурент полиси стоит форбид, крон запускается раз в минуту, и один из запусков подзавис и работал 2 часа, то есть 120 минут;
  • или у нас один мастер в кластере, который упал на пару часов.

В этом случае происходит нечто странное: CronJob перестает работать, новые Jobы больше не создаются. Сообщение об этом приходит в Events, но хранится там недолго. Для восстановления работы приходится удалять CronJob и создавать его заново.


И еще более странное:
Если установлен параметр startingDeadlineSeconds, то поведение немного меняется. 100 пропущенных запусков должны уложиться в количество секунд, указанных в этом параметре. т. е. если в расписании стоит выполняться раз в минуту, а startingDeadlineSeconds меньше 6000 секунд, тогда CronJob будет работать всегда, но в этом случае при политике Allow возможен одновременный запуск множества Job.


И наконец, любопытный side-эффект:
Если установить опцию startingDeadlineSeconds равной нулю, Jobы вообще перестают создаваться.


Если вы используете опцию startingDeadlineSeconds, указывайте её значение меньше, чем интервал выполнения в расписании, но не ноль.
Применяйте политику Forbid. Например, если было пропущено 5 вызовов и наступило очередное время исполнения, то контроллер не будет 5 раз запускать пропущенные задачи, а создаст только один Job. Это логичное поведение.

Особенность работы CronJob


Цитата из документации Kubernetes:


A cron job creates a job object about once per execution time of its schedule. We say "about" because there are certain circumstances where two jobs might be created, or no job might be created. We attempt to make these rare, but do not completely prevent them. Therefore, jobs should be idempotent.


Вольный перевод на русский:


CronJob создаёт объект Job примерно один раз на каждое время исполнения по расписанию. Мы говорим примерно, потому что иногда бывают случаи, когда создаются два Jobа одновременно или ни одного. Мы делаем всё, чтобы сделать подобные случаи как можно более редкими, но полностью избежать этого не получается. Следовательно, Jobы должны быть идемпотенты.


Идемпотенты должны выполняться на одной и той же среде несколько раз и всегда возвращать одинаковый результат.


В общем, используйте CronJob на свой страх и риск.


В качестве альтернативы CronJob можно использовать под, в котором запущен самый обычный crond. Без выкрутасов. Старый добрый cron работает без проблем, и мы всегда знаем, что задачи будут выполнены один раз. Надо только побеспокоиться, чтобы внутри пода с кроном не выполнялись одновременно несколько задач.


Изучить продвинутые абстракции и попрактиковаться в работе с Kubernetes можно с помощью видеокурса Kubernetes База. В октябре 2020 мы обновили курс, подробности здесь.


Автор статьи: Сергей Бондарев практикующий архитектор Southbridge, Certified Kubernetes Administrator, один из разработчиков kubespray с правами на принятие pull request.

Подробнее..

Перевод Jobы и Cronjobы Kubernetes

10.03.2021 18:12:48 | Автор: admin

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

В Kubernetes есть несколько контроллеров для управления подами: ReplicaSets, DaemonSets, Deployments и StatefulSets. У каждого из них есть свой сценарий использования. Однако их всех объединяет гарантия того, что их поды всегда работают. В случае сбоя пода контроллер перезапускает его или переключает его на другой узел (node), чтобы гарантировать постоянную работу программы, которая размещена на подах.

Что, если мы не хотим, чтобы под работал постоянно? Есть сценарии, когда вы не хотите, чтобы процесс продолжался непрерывно. Резервное копирование (backup, создание бэкапа) - это создание копии данных на отдельном носителе, предназначенной для восстановления данных в случае их потери. Данный процесс не должен выполняться постоянно. Напротив, он запускается на выполнение, и после завершения возвращает соответствующий код завершения (exit status), который сообщает, является ли результат успешным или неудачным.

Варианты использования Jobов Kubernetes

Наиболее подходящие варианты использования Jobов Kubernetes:

1. Пакетная обработка данных: допустим вы запускаете пакетную задачу с периодичностью один раз в день/неделю/месяц или по определенному расписанию. Примером этого может служить чтение файлов из хранилища или базы данных и передача их в сервис для обработки файлов.

2. Команды/специфические задачи: например, запуск скрипта/кода, который выполняет очистку базы данных.

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

Ваш первый Kubernetes Job

Создание Jobа Kubernetes, как и других ресурсов Kubernetes, осуществляется с помощью файла определения (definition file). Откройте новый файл, вы можете назвать его job.yaml. Добавьте в файл следующее:

apiVersion: batch/v1kind: Jobmetadata:name: hello-worldspec:template:  metadata:    name: hello-world  spec:    containers:    - name: hello-world      image: busybox      command: ["echo", "Running a hello-world job"]    restartPolicy: OnFailure

Как и в случае с другими ресурсами Kubernetes, мы можем применить это определение к работающему кластеру Kubernetes с помощью kubectl. Выглядит это следующим образом:

$ kubectl apply -f job.yamljob.batch/hello-world created

Давайте посмотрим, какие поды были созданы:

$ kubectl get podsNAME              READY    STATUS          RESTARTS      AGEhello-world-mstmc        0/1 ContainerCreating   0          1s

Проверим статус jobа с помощью kubectl:

kubectl get jobs hello-world-mstmcNAME      COMPLETIONS   DURATION   AGEhello-world-mstmc  1/1      21s    27s

Подождем несколько секунд и снова запустить ту же команду:

$ kubectl get podsNAME              READY   STATUS  RESTARTS       AGEhello-world-mstmc       0/1 Completed          0           25s

Статус пода говорит, что он не запущен. Статус Completed, поскольку job был запущен и выполнен успешно. Job, который мы только что определили, имел простую задачу :) - echo Running a hello-world job в стандартный вывод.

Прежде чем двигаться дальше, давайте убедимся, что job действительно выполнил то, что мы от него хотели:

kubectl logs -f hello-world-mstmcRunning a hello-world job

В логах отображается то, что мы задали, т.е. вывести Running a hello-world job. Job успешно выполнен.

Запуск CronJob вручную

Бывают ситуации, когда вам нужно выполнить cronjob на разовой основе. Вы можете сделать это, создав job из существующего cronjobа.

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

kubectl create job --from=cronjob/kubernetes-cron-job manual-cron-job

--from=cronjob/kubernetes-cron-job скопируйте шаблон cronjobа и создайте job с именем manual-cron-job

Удаление Jobа Kubernetes и очистка (Cleanup)

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

Job можно удалить с помощью kubectl следующим образом:

kubectl delete jobs job_name

Приведенная выше команда удаляет конкретный Job и все его дочерние поды. Как и в случае с другими контроллерами Kubernetes, вы можете удалить Job, только покидая его поды, используя флаг cascade=false. Например:

kubectl delete jobs job_name cascade=false

Есть еще несколько ключевых параметров, которые вы можете использовать с jobами/cronjobами kubernetes в зависимости от ваших потребностей. Давайте рассмотрим каждый из них.

1. failedJobHistoryLimit и successfulJobsHistoryLimit: Удаления истории успешных и неудавшихся jobов основано на заданном Вами количестве сохранений. Это очень удобно для отбрасывания всех вхождений, завершенных неудачей, когда вы пытаетесь вывести список jobов. Например,

failedJobHistoryLimit: 5successfulJobsHistoryLimit: 10

2. backoffLimit: общее количество повторных попыток в случае сбоя пода.

RestartPolicy Jobа Kubernetes

В параметре restartPolicy (политика перезапуска) нельзя установить политику always (всегда). Job не должен перезапускать под после успешного завершения по определению. Таким образом, для параметра restartPolicy доступны варианты Never (никогда) и OnFailure (в случае неудачи).

Ограничение выполнения Jobа Kubernetes по времени

Если вы заинтересованы в выполнении Jobа в течение определенного периода времени, независимо от того, успешно ли завершился процесс, то у Jobов Kubernetes есть параметр spec.activeDeadlineSeconds. Установка для этого параметра приведет к немедленному прекращению работы Jobа по истечении заданного количества секунд.

Обратите внимание, что этот параметр переопределяет .spec.backoffLimit, т.е. если под завершается неудачей и Job достигает своего временного ограничения, неудавшийся под не перезапускается. Все сразу же останавливается.

В следующем примере мы создаем Job как с параметром backoff limit, так и с deadline:

apiVersion: batch/v1kind: Jobmetadata: name: data-consumerspec: backoffLimit: 5 activeDeadlineSeconds: 20 template:spec:    containers:    - name: consumer        image: busybox        command: ["/bin/sh", "-c"]        args: ["echo 'Consuming data'; sleep 1; exit 1"]    restartPolicy: OnFailure

Завершения Jobов и параллелизм

Мы рассмотрели, как можно выполнить одну задачу, определенную внутри объекта Job, что более известно как шаблон run-once. Однако в реальных сценариях используются и другие шаблоны.

Несколько одиночных Jobов

Например, у нас может быть очередь сообщений, которая требует обработки. Мы должны порождать пользовательские jobы, которые извлекают сообщения из очереди, пока она не опустеет. Чтобы реализовать этот шаблон с помощью Jobов Kubernetes, мы устанавливаем в параметр .spec.completions какое-либо число (должно быть ненулевым, а положительным числом). Job начинает создавать поды пока не достигнет заданного числа завершений (completions). Job считается завершенным, когда все поды завершаются с успешным с кодом завершения. Приведем пример. Измените наш файл определения, чтобы он выглядел следующим образом:

apiVersion: batch/v1kind: Jobmetadata:name: data-consumerspec:completions: 5template: metadata:   name: data-consumer spec:   containers:   - name: data-consumer     image: busybox     command: ["/bin/sh","-c"]     args: ["echo 'consuming a message from queue'; sleep 5"]   restartPolicy: OnFailure
  • Мы указываем параметр completions равным 5.

Несколько параллельно запущенных Jobов (Work Queue)

Другой шаблон может включать в себя необходимость запуска нескольких jobов, но вместо того, чтобы запускать их один за другим, нам нужно запускать несколько из них параллельно. Параллельная обработка сокращает общее время выполнения и находит применение во многих областях, например в data science и искусственном интеллекте.

Измените файл определения, чтобы он выглядел следующим образом:

apiVersion: batch/v1kind: Jobmetadata:name: data-consumerspec:parallelism: 5template: metadata:   name: data-consumer spec:   containers:   - name: data-consumer     image: busybox     command: ["/bin/sh","-c"]     args: ["echo 'consuming a message from queue'; sleep $(shuf -i 5-10 -n 1)"]   restartPolicy: OnFailure

В этом примере мы не задали параметр .spec.completions. Вместо этого мы указали параметр parallelism. Параметр completions в нашем случае по умолчанию равен parallelism (5). Теперь Job делает следующее:

Одновременно будут запущены 5 подов, все они будут выполнять один и то же Job. Когда один из подов завершается успехом, это будет означать, что весь Job готов. Поды больше не создаются, и Job в конечном итоге завершается.

В этом сценарии Kubernetes Job одновременно порождает 5 подов. Знать, завершили ли остальные поды свои задачи, в компетенции самих подов. В нашем примере мы предполагаем, что получаем сообщения из очереди сообщений (например, AWS SQS). Когда сообщений для обработки больше нет, Job получает уведомление о том, что он должен завершиться. После успешного завершения первого пода:

  • Поды больше не создаются.

  • Существующие поды завершают свою работу и тоже завершаются.

В приведенном выше примере мы изменили команду, которую выполняет под, чтобы перевести его в спящий режим на случайное количество секунд (от 5 до 10), прежде чем он завершится. Таким образом, мы примерно моделируем, как несколько подов могут работать вместе с внешним источником данных, таким как очередь сообщений или API.

Ограничения CronJobов

Cronjob создает объект job примерно по одному разу за сеанс выполнения своего расписания (schedule). Мы говорим примерно, потому что при определенных обстоятельствах могут быть созданы два jobа или ни одного. Мы пытаемся сделать подобные случаи как можно более редкими, но не можем предотвратить их полностью. Следовательно, jobы должны быть идемпотентными.

Если параметр startingDeadlineSeconds установлен на большое значение или не задан (по умолчанию) и параметр concurrencyPolicy установлен на Allow, job всегда будут запускаться как минимум один раз.

Для каждого CronJobа контроллер CronJob проверяет, сколько расписаний он пропустил за период с последнего запланированного времени до настоящего момента. Если пропущенных расписаний больше 100, то job не запускается и в логи вносится сообщение об ошибке.

Cannot determine if job needs to be started. Too many missed start time (> 100). Set or decrease .spec.startingDeadlineSeconds or check clock skew.

Важно отметить, что если параметр startingDeadlineSeconds установлен (т.е. не nil), то контроллер считает сколько произошло пропущенных jobов исходя из значения параметра startingDeadlineSeconds, начиная с последнего заданного времени до сих пор. Например, если параметр startDeadlineSeconds установлен на 200, контроллер подсчитывает, сколько пропущенных jobов произошло за последние 200 секунд.

CronJob считается пропущенным, если его не удалось создать в установленное время. Например, если для параметра concurrencyPolicy задано значение Forbid и была предпринята попытка запланировать CronJob во время выполнения предыдущего расписания, оно будет считаться пропущенным.

Например, предположим, что CronJob настроен запускать новый Job каждую минуту начиная с 08:30:00, а его параметр startingDeadlineSeconds не установлен. Если контроллер CronJob не работал с 08:29:00 до 10:21:00, job не запустится, так как количество пропущенных jobов в расписании превышает 100.

Чтобы проиллюстрировать эту концепцию с другой стороны, предположим, что CronJob запрограммирован планировать новый Job в расписании каждую минуту начиная с 08:30:00 и его параметр startingDeadlineSeconds устанавливается на 200 секунд. Если контроллер CronJob не работает в течение того же периода, что и в предыдущем примере (с 08:29:00 до 10:21:00), Job все равно запустится в 10:22:00. Это происходит, поскольку контроллер теперь проверяет, сколько пропущенных расписаний было за последние 200 секунд (т. е. 3 пропущенных расписания), вместо того, чтобы считать с последнего заданного времени до настоящего момента.

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

TL;DR (Too Long; Didnt Read)

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

  • Jobы Kubernetes по умолчанию не нуждаются в селекторах подов; Job автоматически обрабатывает их лейблы и селекторы.

  • Параметр restartPolicy для Job принимает значения Never или OnFailure

  • Jobы используют параметры completions и parallelism для управления шаблонами, которые определяют порядок работы подов. Поды Jobов могут выполняться как одна задача, несколько последовательных задач или несколько параллельных задач, в которых первая завершенная задача дает указание остальным подам завершиться.

  • Вы можете контролировать количество попыток Jobа перезапустить неудавшиеся поды, используя параметр .spec.backoffLimit. По умолчанию этот лимит равен шести.

  • Вы можете контролировать время работы jobа, используя параметр .spec.activeDeadlineSeconds. Этот лимит отменяет backoffLimit. Таким образом, Job не пытается перезапустить неудавшийся под, если дедлайн достигнут.

  • Jobы и их поды не удаляются автоматически после завершения. Вы должны удалить их вручную или использовать контроллер ttlSecondsAfterFinished, который на момент написания этой статьи все еще находится в статусе alpha.


Перевод статьи подготовлен в преддверии старта курса "Инфраструктурная платформа на основе Kubernetes". Также приглашаем всех желающих записаться на бесплатный демо-урок по теме: "Работа с NoSQL базами в k8s (на примере Apache Cassandra)"

Подробнее..

Kubernets 1.21 неожиданно много изменений

12.04.2021 16:18:43 | Автор: admin

Новая эмблема символизирует распределение членов команды выпуска релиза по земному шару от UTC-8 до UTC+8 (похоже, ни японцев, ни корейцев в команде нет). Эмблему нарисовал Aravind Sekar, независимый дизайнер из Индии. На мой взгляд, котики были круче.

Но давайте перейдем к чтению changelog и особенно моему любимому разделу Urgent Upgrade Notes.


CronJob

Сообщение в блоге гласит, что CronJob объявлены stable, но далее есть небольшое уточнение стабильным объявлена версия API, то есть структура манифеста kind: cronJob, а вот с контроллером, который и отвечает за реализацию логики работы, все намного интереснее.

В версии 1.20 был добавлен CronJob контроллер версии 2. В новой версии 1.21 его перевели в стадию бета и включили по умолчанию. В версии 1.22 планируется удалить код старого CronJob контроллера. Очень, очень быстрые изменения, обычно не свойственные циклам релизов в Kubernetes.

Причем в документации все Cronjob limitations, о которых я писал на Хабре, остались без изменений.

Зачем тогда делали новый контроллер, если все проблемы с кронджобами остались нерешенными? Ответ есть в этой статье старый контроллер излишне нагружал API Kubernetes и не успевал создавать Job, если в кластере было больше 1000 Cronjob манифестов. Новая версия контроллера написана согласно последним гайдлайнам и намного быстрее.

Immutable Secret and ConfigMap

Добавили возможность создавать защищенные от изменений секреты и конфиг мапы. Видимо, защита от джунов, которые "pushed bad configuration". На мой взгляд, ConfigMap надо деплоить через helm чарты, а секреты хранить в Vault. Там, где есть история изменений, а ваш CI/CD не должен позволять выкатывать нерабочие конфиги на прод.

IPv4/IPv6 Dual-Stack support

Поддержка IPv6 теперь включена по умолчанию, единственная тонкость ваш CNI также должен уметь в Dual-Stack. Calico умеет)

Graceful Node Shutdown

Kubelet научился определять ситуацию, когда узел выключается командой shutdown, и теперь посылает подам sigterm. TODO: Протестировать, не завершается ли container runtime быстрее kubelet, и что будет при простом systemctl shutdown kubelet.

PodSecurityPolicy Deprecation

Еще одна неоднозначная новость. PSP объявлены устаревшими и запланированы к удалению в версии 1.25. Но при этом PSP Replacement Policy (полиси для замены полиси) находятся в состоянии проекта, а альфа-версию обещают показать только в Kubernetes 1.22. Кратко ознакомиться, что же там проектируется, можно в KEP #2582. Самое странное из того, что там написано, на мой взгляд, это предложение использовать namespace label, чтобы определять, по каким правилам проверять манифесты подов. Получается, что, выдав кому-либо права на редактирование неймспейса, вы даете ему и простой способ получить права администратора кластера.

Подождем и посмотрим, что же будет в итоге, а пока нам предлагают плавно переходить на использование стандартных PSP, аналоги которых в виде встроенных профилей будут захардкожены в новый admission plugin PSPv2.

Или переходить на использование сторонних решений, таких как Open Policy Agent Gatekeeper.

Urgent Upgrade Notes

По умолчанию теперь используется cgroupDriver systemd. Не забывайте проверять настройки своего containerd при установке нового кластера или добавлении узлов. Но и это еще не все. В версии 1.22 обещают принудительную смену cgroup driver в kubelet на systemd при обновлении кластера, поэтому пора уже почитать руководство по миграции и начать смену драйвера.

Много мелких изменений в районе CSI и PV: перестают использоваться старые метки, флаги и метрики. В принципе ничего страшного, скорее всего, надо будет просто обновить используемые CSI драйверы.

Команды kubeadm kubeconfig user, certs и debug переведены из экспериментальных в постоянные, и теперь их надо указывать без слова alpha.

Продолжают урезать функционал команды kubectl run. Убрали целый набор ключей для создания service и cronjob, объявили устаревшими ключи для установки реквестов и лимитов, сервисаккаунта и использования hostport. В общем активно заставляют использовать только готовые yaml-манифесты для создания объектов кластера.

К большому моему сожалению, окончательно убрали поддержку ключа kubectl --export. А как было удобно с помощью этого ключа получать из готового объекта кластера манифест для создания его копии, например, секрет с TLS сертификатом скопировать в другой namespace.

Всем, кто использует vSphere версии меньшей, чем 67u3, рекомендуют обновиться, время есть до выхода kubernetes 1.24.

Интересные мелкие новшества

В NetworkPolicy добавили поле endPort для поддержки диапазонов портов. Радуйтесь, любители запускать Asterisk в кластере.

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

В команды kubectl exec и portforward добавили keepalive пинги, и теперь промежуточные HTTP-балансировщики не будут обрывать соединение, если в нем нет активности.

В Job добавили поле suspend и написали про это целую статью в блоге. Вот только я не понял, какой в этом смысл имитация работы Kafka или Rabbitmq?

Появилась возможность выбирать неймспейсы по их имени. Просто в манифест неймспейса автоматически добавляют метку kubernetes.io/metadata.name.

В Service добавили поле InternalTrafficPolicy. Если указать в нем значение Local, трафик будет направляться только на поды, расположенные на том же узле кластера, что и под, отправивший запрос. Пока в альфа-статусе, для использования надо включить featureGate = ServiceInternalTrafficPolicy.

Наконец-то включили TTL Controller, который позволяет удалять манифесты завершившихся Job.

В манифест подов добавили аннотацию kubectl.kubernetes.io/default-container, с помощью которой можно указать, в какой контейнер пода делать exec, чьи логи смотреть и тому подобное, если при вызове не указан ключ -c.

Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru