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

Cron

Когда 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 и про проблемы, с которыми мы сталкивались.

Подробнее..

Перевод Как и зачем в Lyft улучшали Kubernetes CronJobs

15.09.2020 16:23:12 | Автор: admin
Прим. перев.: эту статью, в оригинале состоящую из двух частей, написал Kevin Yang software engineer из компании Lyft, которая хорошо известна в Kubernetes-сообществе как минимум благодаря созданию Envoy. В новом материале автор делится интересным опытом миграции большого числа традиционных cron-задач из Linux на CronJobs в K8s. Можно в деталях узнать о том, к каким проблемам в масштабах Lyft это привело и как они были решены инженерами компании.



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

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

Сначала будут описаны недостатки Kubernetes CronJobs, с которыми мы столкнулись при их использовании в Lyft. Затем (во второй части) расскажем, как устранили эти недостатки в стеке Kubernetes, повысили удобство работы и улучшили надежность.

Часть 1. Введение


Кому будет полезны данные статьи?


  • Пользователям Kubernetes CronJob.
  • Всякому, кто создает платформу на базе Kubernetes.
  • Любому, что заинтересован в выполнении распределенных задач в Kubernetes по расписанию.
  • Всем, кто интересуется применением Kubernetes в реальных, высоконагруженных проектах.
  • Contributor'ам Kubernetes.

Что вы узнаете и чему научитесь?


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

Предварительные условия:


  • Базовые представления о работе cron'а.
  • Базовое понимание того, как работает CronJob, в частности, взаимоотношений между контроллером CronJob, создаваемыми им Job'ами и Pod'ами, в которых все происходит. Оно позволит лучше уяснить внутреннюю механику CronJob и разобраться в сравнениях с Unix cron'ом далее в этой статье.
  • Общее представление о паттерне sidecar-контейнеров и о том, для чего они нужны. Мы в Lyft используем механизм упорядочивания запуска sidecar-контейнеров с тем, чтобы runtime-зависимости вроде Envoy, statsd и т.д., упакованные в sidecar-контейнеры, запускались и приступали к работе до того, как запустился контейнер самого приложения.

История вопроса и терминология


  • сronjobcontroller это фрагмент кода в управляющем слое Kubernetes, отвечающий за CronJob'ы.
  • Говорят, что cron вызывается, когда он выполняется некими системными механизмами (обычно в соответствии с расписанием).
  • Lyft Engineering использует модель платформенной инфраструктуры, в рамках которой выделяются инфраструктурная команда (далее называемая платформенной, инженерами эксплуатации платформы, платформенной инфраструктурой) и потребители остальные инженеры в Lyft (далее именуемые разработчиками, разработчиками сервисов, пользователями или потребителями). Наши инженеры владеют, эксплуатируют и поддерживают свой код, поэтому слова с корнем эксплуат- повсеместно используются в этой статье.

CronJob'ы в Lyft


Сегодня в нашей multi-tenant (многопользовательской) production-среде насчитывается почти 500 cron-задач, вызываемых более 1500 раз в час.

Повторяющиеся, запланированные задачи активно используются в Lyft для различных целей. До перехода на Kubernetes они выполнялись прямо на Linux-машинах с помощью обычного cron'а Unix. Команды разработчиков отвечали за написание crontab-определений и подготовку экземпляров, которые выполняли их с помощью пайплайнов в виде Infrastructure As Code (IaC), а за их обслуживание отвечала инфраструктурная команда.

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

Представьте себе cron-задачу, которая запускается раз в неделю на 15 минут. В нашей старой среде машина, выделенная под эту задачу, простаивала бы 99,85% времени. В случае же Kubernetes вычислительные ресурсы (CPU, память) используются только во время вызова. В остальное время незадействованные мощности можно использовать для запуска других CronJob'ов, или просто уменьшить (scale-down) кластер. Учитывая прошлый способ запуска cron-задач, мы многое выиграли бы от перехода на модель, в которой job'ы эфемерны.


Границы зон ответственности разработчиков и платформенных инженеров в стеке Lyft

После перехода на платформу Kubernetes команды разработчиков перестали заниматься выделением и эксплуатацией собственных вычислительных экземпляров. Теперь за поддержку и эксплуатацию вычислительных ресурсов и runtime-зависимостей в стеке Kubernetes отвечает платформенная команда. Кроме того, она занимается созданием самих CronJob-объектов. Разработчикам остается только настроить расписание задач и код приложения.

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

* Хотя CronJob имел и по-прежнему имеет (по состоянию на Kubernetes v1.18) бета-статус, мы обнаружили, что на тот момент он вполне удовлетворял нашим потребностям и, кроме того, прекрасно вписывался в остальной инфраструктурный инструментарий Kubernetes, существовавший у нас.

Чем отличается Kubernetes CronJob от Unix cron'а?



Упрощенная последовательность событий и программных компонентов K8s, участвующих в работе Kubernetes CronJob

Чтобы лучше объяснить, почему работа с CronJob'ами Kubernetes в production-среде связана с определенными трудностями, давайте сначала определим, чем же они отличаются от классических. Предполагается, что CronJob'ы работают так же, как задачи cron в Linux или Unix; однако на самом деле есть как минимум пара серьезных отличий в их поведении: скорость запуска и обработка сбоев.

Скорость запуска


Задержку запуска (start delay) мы определим как время, прошедшее с запланированного запуска cron'а до момента фактической начала работы кода приложения. Другими словами, если запуск cron'а запланирован на 00:00:00, а приложение начинает выполняться в 00:00:22, то задержка запуска этого конкретного cron'а составит 22 секунды.

В случае классических Unix cron'ов задержка запуска минимальна. Когда подходит время, указанные команды просто выполняются. Давайте подтвердим это на следующем примере:

# запускает команду date каждые сутки в полночь0 0 * * * date >> date-cron.log

С такой конфигурацией cron мы, скорее всего, получим следующий результат в date-cron.log:

Mon Jun 22 00:00:00 PDT 2020Tue Jun 23 00:00:00 PDT 2020

С другой стороны, у CronJob'ов Kubernetes могут быть значительные задержки запуска, поскольку работе приложения предшествует целый ряд событий. Вот некоторые из них:

  1. cronjobcontroller обрабатывает и решает вызвать CronJob;
  2. cronjobcontroller создает Job на основе спецификации задания CronJob;
  3. jobcontroller замечает новый Job и создает Pod;
  4. Admission Controller вставляет данные sidecar-контейнера в спецификацию Pod'а*;
  5. kube-scheduler планирует Pod на kubelet;
  6. kubelet запускает Pod (извлекая все образы контейнеров);
  7. kubelet запускает все sidecar-контейнеры*;
  8. kubelet запускает контейнер приложения*.

* Эти этапы уникальны для Kubernetes-стека Lyft.

Мы обнаружили, что самый значительный вклад в задержку вносят пункты 1, 5 и 7, как только мы достигаем определенного масштаба CronJob'ов в Kubernetes-окружении.

Задержка, вызванная работой cronjobcontroller'а


Чтобы лучше понять, откуда берется задержка, давайте изучим исходный код встроенного cronjobcontroller'а. В Kubernetes 1.18 cronjobcontroller просто проверяет все CronJob'ы каждые 10 секунд и выполняет некоторую логику над каждым.

Реализация cronjobcontroller'а делает это синхронно, осуществляя по крайней мере один дополнительный вызов API для каждого CronJob'а. Когда число CronJob'ов превышает определенное количество, эти API-вызовы начинают страдать от ограничений на клиентской стороне.

10-секундный цикл опроса и ограничения на число обращений к API со стороны клиента приводят к значительному увеличению задержки запуска CronJob'ов.

Планирование Pod'ов с cron'ами


Из-за особенностей расписания cron'ов большинство из них запускаются в начале минуты (XX:YY:00). Например, @hourly (почасовой) cron запускается в 01:00:00, 02:00:00 и т.д. В случае multi-tenant cron-платформы со множеством cron'ов, выполняющихся каждый час, каждую четверть часа, каждые 5 минут и т.д., это приводит к возникновению узких мест (горячих точек), когда множество cron'ов запускаются одновременно. Мы в Lyft заметили, что одним из таких мест является начало часа (XX:00:00). Эти горячие точки создают нагрузку и приводят к дополнительному ограничению частоты запросов в компонентах управляющего слоя, участвующих в выполнении CronJob'а, таких как kube-scheduler и kube-apiserver, что приводит к заметному увеличению задержки запуска.

Кроме того, если вы не выделяете вычислительную мощность с учетом пиковых нагрузок (и/или используете вычислительные экземпляры облачного сервиса), а вместо этого применяете механизм автомасштабирования кластера для динамического масштабирования узлов, то время, затрачиваемое на запуск узлов, вносит дополнительный вклад в задержку запуска pod'ов CronJob.

Запуск pod'а: вспомогательные контейнеры


Как только pod CronJob'а был успешно запланирован в kubelet, последний должен извлечь и запустить образы контейнеров всех sidecar'ов и самого приложения. Из-за специфики запуска контейнеров в Lyft (sidecar-контейнеры стартуют до контейнеров приложений), промедление при старте любого sidecar'а неминуемо отражается на результате, приводя к дополнительной задержке запуска задания.

Таким образом, проволочки при запуске, предшествующие выполнению нужного кода приложения, вкупе с большим количеством CronJob'ов в условиях multi-tenant-среды приводят к заметным и непредсказуемым задержкам запуска. Как мы увидим чуть позже, в реальных условиях подобная задержка может негативно сказаться на поведении CronJob'а, грозя пропуском запусков.

Обработка сбоев контейнеров


В общем случае рекомендуется следить за работой cron'ов. Для Unix-систем это сделать довольно просто. Unix cron'ы интерпретируют заданную команду с помощью указанной оболочки $SHELL, а после завершения работы (exit) команды (успешного или нет) этот конкретный вызов считается завершенным. Отслеживать выполнение cron'а в Unix можно с помощью простейшего скрипта вроде такого:

#!/bin/shmy-cron-commandexitcode=$?if [[ $exitcode -ne 0 ]]; then    # stat-and-log is pseudocode for emitting metrics and logs    stat-and-log "failure"else    stat-and-log "success"fiexit $exitcode

В случае Unix cron'а stat-and-log будет выполняться ровно один раз для каждого вызова cron'а независимо от $exitcode. Поэтому данные метрики можно использовать для организации простейших оповещений о неудачных вызовах.

В случае CronJob'а Kubernetes, где по умолчанию определены повторные попытки при неудачах, а сама неудача может быть вызвана различными причинами (сбой Job'а или сбой контейнера), мониторинг не столь прост и однозначен.

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

Можно реализовать оповещения на уровне Job'а, а не на уровне контейнера приложения. Для этого доступны метрики API-уровня для сбоев Job'ов, такие как kube_job_status_failed из kube-state-metrics. Недостаток такого подхода в том, что дежурный инженер узнает о проблеме только после того, как Job достигнет окончательной стадии отказа и упрется в предел BackoffLimit, что может случиться намного позже первого сбоя контейнера приложения.

Причины периодических сбоев CronJob'ов


Существенная задержка старта и циклы повторных запусков вносят дополнительную задержку, которая может помешать повторному выполнению CronJob'ов Kubernetes. В случае часто вызываемых CronJob'ов или тех, у которых время работы значительно превышает время простоя, эта дополнительная задержка может привести к проблемам при следующем запланированном вызове. Если для CronJob'а установлена политика ConcurrencyPolicy: Forbid, запрещающая параллельную работу, то задержка приводит к тому, что будущие вызовы не выполняются вовремя и откладываются.


Пример хронологии (с точки зрения cronjobcontroller'а), в которой startingDeadlineSeconds превышается для конкретного почасового CronJob'а: он пропускает свой запланированный запуск и не будет вызван до следующего запланированного времени

Есть и более неприятный сценарий (с ним мы столкнулись в Lyft), из-за которого CronJob'ы могут полностью пропускать вызовы, это когда для CronJob'а установлен startingDeadlineSeconds. В этом сценарии, если задержка запуска превышает startingDeadlineSeconds, CronJob полностью пропустит запуск.

Кроме того, если ConcurrencyPolicy для CronJob'а установлена в Forbid, цикл перезапусков-в-случае-сбоя предыдущего вызова также может помешать очередному вызову CronJob'а.

Проблемы при эксплуатации CronJob'ов Kubernetes в реальных условиях


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

Разработчики пытались эксплуатировать свои CronJob'ы и настраивать их, но в результате приходили к нам со множеством жалоб и вопросов вроде этих:

  • Почему мой cron не работает?
  • Похоже, мой cron перестал работать. Как подтвердить, что он действительно выполняется?
  • Я не знал, что cron не работает, и думал, что все в порядке.
  • Как мне исправить пропущенный cron? Я не могу просто войти по SSH и запустить команду самостоятельно.
  • Вы можете сказать, почему этот cron, похоже, пропустил несколько запусков в период с X по Y?
  • У нас Х (большое число) cron'ов, каждый со своими оповещениями, и обслуживать их всех становится довольно утомительно/тяжело.
  • Pod, Job, sidecar что это вообще за ерунда такая?

Как платформенная команда, мы были не в состоянии ответить на вопросы вроде:

  • Как количественно оценить производительность cron-платформы Kubernetes?
  • Как включение дополнительных CronJob'ов отразится на нашей Kubernetes-среде?
  • Насколько отличается производительность Kubernetes CronJob'ов (выполняющихся в режиме multi-tenant) от single-tenant cron'а Unix?
  • С чего начать формулирование Service-Level-Objectives (SLOs целевых показателей доступности) для наших клиентов?
  • За чем мы, как операторы платформы, следим и что делаем, чтобы решать проблемы в масштабе всей платформы быстро и с минимальным влиянием на наших потребителей?

Отладка сбоев CronJob нелегкая задача. Часто требуется интуиция, чтобы понять, на каком этапе происходят сбои и где искать улики. Иногда эти улики получить довольно трудно как, например, логи cronjobcontroller'а, которые записываются, только если включен высокий уровень детализации. Кроме того, следы могут просто исчезать после определенного промежутка времени, что делает отладку похожей на игру Прибей крота (речь об этом прим. перев.), например, Kubernetes Events для CronJob'ов, Job'ов и Pod'ов, которые по умолчанию хранятся лишь в течение часа. Ни один из этих методов нельзя назвать простым в использовании, и ни один из них не масштабируется нормально с точки зрения поддержки с ростом числа CronJob'ов на платформе.

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

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

Часть 2. Введение


Стало понятно, что CronJob'ы Kubernetes в неизменном виде не смогут стать простой и удобной заменой для их Unix-аналогов. Чтобы уверенно перенести все наши cron'ы в Kubernetes, нам было нужно не только устранить технические недостатки CronJob'ов, но и повысить удобство работы с ними. А именно:

1. Выслушать разработчиков, чтобы понять, ответы на какие вопросы о cron'ах волнуют их больше всего. Например: Запустился ли мой cron? (Был ли выполнен код приложения?) Прошел ли запуск успешно? Какое время заняла работа cron'а? (Как долго выполнялся код приложения?)

2. Упростить обслуживание платформы, сделав CronJob'ы более понятными, их жизненный цикл более прозрачным, а границы платформы/приложения более ясными.

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

4. Разработать инструментарий для простого восстановления после сбоев и тестирования новых конфигураций CronJob'ов.

5. Исправить давние технические проблемы в Kubernetes, такие как баг с TooManyMissedStarts, который требует ручного вмешательства для устранения и приводит к тому, что падение в рамках одного важного сценария сбоя (когда значение startingDeadlineSeconds не задано) происходит незаметно.

Решение


Все эти проблемы мы решили следующим образом:

  1. Повысили наблюдаемость (observability). Это позволило разработчикам не только проводить отладку своих CronJob'ов, но и открыло перед платформенными инженерами путь к определению целевых уровней обслуживания (Service Level Objectives, SLOs) и их контролированию.
  2. Разработали инструмент для упрощения вызова CronJob'ов по запросу в нашем стеке Kubernetes.
  3. Исправили давние проблемы в самом Kubernetes.

Метрики и оповещения для CronJob'ов



Пример панели, сгенерированной платформой для мониторинга конкретного CronJob'а

Мы дополнили стек Kubernetes следующими метриками (они определены для всех CronJob'ов в Lyft):

1. started.count этот счетчик увеличивается тогда, когда контейнер приложения впервые запускается при вызове CronJob'а. Он помогает ответить на вопрос: Выполнялся ли код приложения?.

2. {success, failure}.count эти счетчики увеличиваются, когда конкретный вызов CronJob'а достигает терминального состояния (то есть Job закончил свою работу и jobcontroller более не пытается его выполнить). Они отвечают на вопрос: Прошел ли запуск успешно?.

3. scheduling-decision.{invoke, skip}.count эти счетчики позволяют узнать о решениях, которые принимает cronjobcontroller при вызове CronJob'а. В частности, skip.count помогает ответить на вопрос: Почему мой cron не работает?. В качестве его параметров выступают следующие метки reason:

  • reason = concurrencyPolicy cronjobcontroller пропустил вызов CronJob'а, поскольку в ином случае это привело бы к нарушению его ConcurrencyPolicy;
  • reason = missedDeadline cronjobcontroller отказался от вызова CronJob'а, поскольку тот пропустил окно вызова, заданное .spec.startingDeadlineSeconds;
  • reason = error это общий параметр для всех остальных ошибок, возникающих при попытке вызвать CronJob.

4. app-container-duration.seconds этот таймер измеряет время существования контейнера приложения. Он помогает ответить на вопрос: Как долго выполнялся код приложения?. В этот таймер мы умышленно не включили время, требующееся на планирование pod'а, запуск sidecar-контейнеров и т.д., поскольку они входят в зону ответственности платформенной команды и включаются в задержку запуска.

5. start-delay.seconds этот таймер измеряет задержку запуска. Эта метрика при агрегации по всей платформе позволяет инженерам, ее обслуживающим, не только оценивать, отслеживать и настраивать производительность платформы, но и выступает некой основой для определения SLO для таких параметров, как задержка запуска и максимальная частота расписания cron'ов.

На основе этих метрик мы создали оповещения по умолчанию. Они уведомляют разработчиков, когда:

  • Их CronJob не запустился по расписанию (rate(scheduling-decision.skip.count) > 0);
  • Их CronJob завершился неудачно (rate(failure.count) > 0).

Больше разработчикам не нужно определять собственные оповещения и метрики для cron'ов в Kubernetes платформа предоставляет их готовые аналоги.

Запуск cron'ов при необходимости


Мы адаптировали kubectl create job test-job --from=cronjob/<your-cronjob> под наш внутренний CLI-инструмент. Инженеры в Lyft используют его для взаимодействия со своими сервисами на Kubernetes, чтобы при необходимости вызывать CronJob'ы для:

  • восстановления от периодических сбоев CronJob'ов;
  • воспроизведения и отладки runtime-сбоев во время, отличное от 3:00 утра (более удобное время, когда можно исследовать происходящее с CronJob'ами, Job'ами и Pod'ами в реальном времени), вместо того, чтобы пытаться поймать проблему в процессе;
  • тестирования runtime-конфигурации при разработке нового CronJob'а или миграции существующего Unix cron'а, не ожидая, когда подойдет время его вызова по расписанию.

Исправление TooManyMissedStarts


Мы исправили баг с TooManyMissedStarts, так что теперь CronJob'ы не зависают после 100 подряд пропущенных стартов. Этот патч не только устраняет необходимость в ручном вмешательстве, но и позволяет реально отслеживать, когда время startingDeadlineSeconds превышено. Благодарим Vallery Lancey за проектирование и создание этого патча, Tom Wanielista за помощь в разработке алгоритма. Мы открыли PR, чтобы внести этот патч в основную ветку Kubernetes (однако он так и не был принят, а закрыт из-за неактивности прим. перев.).

Реализация мониторинга cron'ов



На каких стадиях жизненного цикла CronJob'ов Kubernetes мы добавили механизмы экспорта метрик

Оповещения, которые не зависят от расписаний cron'ов


Самая хитрая часть реализации оповещений о пропущенных вызовах cron'ов это обработка их расписаний (для их расшифровки нам очень пригодился crontab.guru). Давайте, например, рассмотрим следующее расписание:

# Через каждые 5 минут*/5 * * * *

Можно сделать так, чтобы счетчик для этого cron'а увеличивался каждый раз, когда он завершается (или использовать cron-обвязку). Тогда в системе оповещений можно прописать условное выражение вида: Посмотри на 60 предыдущих минут и сообщи мне, если счетчик увеличится меньше чем на 12. Проблема решена, верно?

Но что, если у вас расписание следующего вида:

# На нулевой минуте каждого часа с 9 до 17# в каждый день недели с понедельника по пятницу.# Другими словами, в рабочие часы (9-17, Пн-Пт)0 917 * * 15

В этом случае придется повозиться с условием (хотя, может быть, у вашей системы есть функция оповещения только по рабочим часам?). Как бы то ни было, эти примеры иллюстрируют, что привязывание оповещений к расписаниям cron'ов имеет несколько недостатков:

  1. При смене расписания приходится вносить изменения в логику оповещения.
  2. Некоторые расписания cron'ов требуют для репликации довольно сложных запросов с использованием временных рядов.
  3. Необходим некий период ожидания для cron'ов, которые не начинают свою работу точно по времени, чтобы минимизировать ложные срабатывания.

Один лишь п. 2 делает генерацию оповещений по умолчанию для всех cron'ов на платформе очень сложной задачей, а п. 3 особенно актуален для распределенных платформ вроде Kubernetes CronJob, в которых задержка запуска является весомым фактором. Кроме того, есть решения, использующие переключатели мертвеца, что опять возвращает нас к необходимости привязывать оповещение к расписанию cron'а, и/или алгоритмы обнаружения аномалий, которые требуют некоторого обучения и не работают сразу для новых CronJob'ов или изменений в их расписании.

Другой способ взглянуть на проблему заключается в том, чтобы спросить себя: что означает, что cron должен был запуститься, но этого не произошло?

В Kubernetes если на минутку забыть о багах в cronjobcontroller'е или возможности падения самого control plane (хотя вы должны сразу это увидеть, если правильным образом отслеживаете состояние кластера) это означает, что cronjobcontroller оценил CronJob и решил (в соответствии с расписанием cron'а), что он должен быть вызван, но по какой-то причине намеренно решил этого не делать.

Звучит знакомо? Это именно то, чем занимается наша метрика scheduling-decision.skip.count! Теперь нам достаточно отслеживать изменение rate(scheduling-decision.skip.count), чтобы оповестить пользователя, что его CronJob должен был сработать, но этого не произошло.

Это решение отвязывает расписание cron'а от самого оповещения, обеспечивая несколько преимуществ:

  • Теперь не нужно перенастраивать оповещения при изменении расписаний.
  • Отпадает необходимость в сложных временных запросах и условиях.
  • Можно легко сгенерировать оповещения по умолчанию для всех CronJob'ов на платформе.

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

Реализация таймера задержки запуска


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

  • T1: когда cron должен быть запущен (в соответствии с его расписанием).
  • T2: когда код приложения фактически начнет выполняться.

В этом случае start delay (задержка запуска) = Т2 Т1. Чтобы зафиксировать момент Т1, мы включили код в логику вызова cron'а в самом cronjobcontroller'е. Он записывает ожидаемое время старта как .metadata.Annotation у объектов Job, которые cronjobcontroller создает при вызове CronJob'а. Теперь его можно извлечь в помощью любого API-клиента, выполнив обычный запрос GET Job.

С Т2 все оказалось сложнее. Поскольку нам нужно получить значение, максимально близкое к реальному, Т2 должен совпадать с моментом, когда контейнер с приложением запустится в первый раз. Если снимать Т2 при любом запуске контейнера (включая перезапуски), то задержка запуска в этом случае включит время работы самого приложения. Поэтому мы решили присваивать еще одну .metadata.Annotation объекту Job всякий раз, когда обнаруживали, что контейнер приложения для данного Job'а впервые получил статус Running. Тем самым, по существу, создавалась распределенная блокировка, и будущие старты контейнера приложения для данного Job'а игнорировались (сохранялся момент только первого старта).

Результаты


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

  • больше не должны ломать голову над своими собственными инструментами мониторинга и оповещениями;
  • могут быть уверены, что их CronJob'ы выполняются, т.к. наши alert'ы оповестят их, если это не так;
  • могут легко восстанавливать работоспособность после сбоев и тестировать новые CronJob'ы в этой среде, используя наш инструмент вызова CronJob'ов по запросу;
  • могут отслеживать производительность кода своего приложения (используя метрику таймера app-container-duration.seconds).

Кроме того, у инженеров по обслуживанию платформы теперь появился новый параметр (start delay) для оценки пользовательского опыта и производительности платформы.

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

Заключение


Оркестрация распределенных, запланированных задач непростое занятие. CronJob'ы Kubernetes это всего лишь один из способов его организации. Хотя они далеки от идеала, CronJob'ы вполне способны работать в глобальных проектах, если, конечно, вы готовы вложить время и усилия в их усовершенствование: повысить наблюдаемость, разобраться в причинах и специфике сбоев и дополнить инструментами, упрощающими использование.

Примечание: существует открытое Kubernetes Enhancement Proposal (KEP) по устранению недостатков CronJob'ов и перевода их обновленной версии в GA.

Выражаю благодарность Rithu John, Scott Lau, Scarlett Perry, Julien Silland и Tom Wanielista за их помощь в проверке данного цикла статей.

P.S. от переводчика


Читайте также в нашем блоге:

Подробнее..

Перевод 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)"

Подробнее..

Автоматическая очистка корзины Yandex.Disk без участия человека

18.01.2021 22:06:54 | Автор: admin

Озаботившись бэкапами на своем скромном сервере я столкнулся с очевидным дальнейшим шагом а где все это добро хранить?


Выбора особо нет да и у меня есть халявный Яндекс Диск на котором со всеми бонусами у меня аж 63 Гб, грех не воспользоваться.


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


Давайте потратим минут 10-15 и на год забудем о проблеме, поехали.


Вводные данные на чем у меня все работает:


Ubuntu 18.04Yandex.Disk консольный клиент для одноименного дистрибутива

  1. Зайдем под тем логином из по которого работает ваш ЯД по адресу https://oauth.yandex.ru/ и нажмем кнопочку Зарегистрировать новое приложение



  2. Заполняем поля как указано на скриншоте



  3. В пункте Доступы выберите Яндекс Диск REST API и поставьте галочки как на скриншоте



  4. Спускаемся в самый низ страницы и жмем кнопку Создать приложение



  5. В полученной странице есть все необходимые нам данные, обязательно сохраните их (PS мои реальные данные я удалил но сохранил вам на скрине для наглядности, у вас они будут другие)



  6. Получим токен, зайдем браузером по адресу https://oauth.yandex.ru/authorize?response_type=token&display=popup&client_id=ВАШid где в самом конце укажем ID полученный в шаге 5


  7. На этой странице жмем Разрешить



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



  9. Зайдем в консоль вашего сервера под рутом и создадим скрипт


    nano /root/yadisk.sh
    

    В котором пропишем следующие команды


    #!/bin/sh/usr/bin/curl -s -H "Authorization: OAuth ваш_токен" -X "DELETE" https://cloud-api.yandex.net/v1/disk/trash/resources/?path=
    

    где на месте ваш_токен внесем данные из шага 8


  10. Сохраним скрипт и сделаем его исполняемым


    chmod 700 /root/yadisk.sh
    

  11. Дадим команду crontab -e
    в открывшемся окне напишем


    0 3 * * * /root/yadisk.sh > /dev/null 2>&1
    

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



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


Если хотите убедиться что вы все сделали правильно то можете просто запустить эту команду в консоли


/usr/bin/curl -s -H "Authorization: OAuth ваш_токен" -X "DELETE" https://cloud-api.yandex.net/v1/disk/trash/resources/?path=

(не забудьте в эту строку внести ваш_токен)
и убедиться что корзина вашего аккаунта пуста.


На этом разрешите откланяться.

Подробнее..

Категории

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

  • Имя: Макс
    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