джоб что это в программировании
Job — шаблон проектирования для новичков и опытных Go программистов
Я начал программировать на Go после достаточно продолжительного периода программирования на PHP. Полагаю судя по последним тенденциям мой случай далеко не единичный. Go в целом набирает популярность среди Web разработчиков.
Поэтому в начале, разумеется, был определенный дискомфорт и ломка шаблонов. Я не понимал как использовать тот же context.Context, зачем он? Мои шаблоны готовы уже были треснуть, но матерого PHP программиста без хрена не съешь. Пора что-то с этим делать и писать свой велосипед! А именно то решение, которое мне, как PHP программисту, казалось достаточно очевидным для решения задача связанных с параллельным программирование, краеугольным камнем Go. Усевшись за рабочий ноутбук я почти месяц корпел над работой. Я хотел не просто сделать реализацию сферического коня в вакууме, а показать на конкретном примере возможность использования данного подхода. Так сказать, proof of concept. Ну и заодно набить руку на Go, чего греха таить.
Как видят решение программисты
А если без шуток, то это, наверное, тема для отдельной статьи.
В качестве примера использования данного шаблона был реализован простой прокси-сервер, выполняющий роль балансировщика уровня L4; клиент, который сканируют указанную директорию на наличие изображений и отправляет все найденные изображения на backend сервер для изменения их размера; backend сервер, который обрабатывает запросы по изменению размера изображений. В основе всех трех приложений лежит компонент Job. Код так же доступен в репозитории на Github.
Parallel Processing Where the commands are written as tasks to a shared resource and executed by many threads in parallel (possibly on remote machines; this variant is often referred to as the Master/Worker pattern)
Задачи разделяют данные и оркестрируют свое выполнение с помощью так называемой ping/pong синхронизации. Я приведу здесь лишь небольшой кускок кода, чтобы дать общее представление — полностью библиотека доступна в репозитории на Github по ссылке.
Всем хороших выходных и да прибудет с нами сила пэхэпе в мире сусликов.
Джоб что это в программировании
Задания ( jobs ) — это наиболее часто используемое средство автоматизации административных операций. Задания можно определить как именованные наборы действий, которые можно запланировать для выполнения по расписанию (а можно выполнять и вручную). На многих рабочих серверах на предприятиях существует сложная система заданий, при помощи которых выполняется множество операций: резервное копирование, проверка целостности, дефрагментация и перестроение индексов, загрузка и выгрузка данных, генерация страниц HTML и т. п. Задания могут создаваться и в автоматическом режиме, например, при настройке доставки журналов или репликации.
Рис. 8.1. Окно свойств задания SQL Server Agent
Очень часто возникает необходимость скопировать задания с одного сервера на другой, чтобы упростить автоматизацию выполнения схожих операций. Проще всего сделать это так: в контекстном меню для созданного задания нужно выбрать команду Script Job As | CREATE TO | New Query Editor Window (Отскриптовать задание как | Создать | Новое окно редактора запросов). В результате в окно редактора кода будет загружен скрипт с командами на создание задания с аналогичными параметрами. Вам останется только исправить некоторые параметры в этом скрипте, сохранить его и запустить на выполнение на другом сервере.
Теперь рассмотрим параметры, которые можно настроить для создаваемого задания. На вкладке General вы можете настроить или просмотреть общие параметры для задания:
q Category (Категория) — этот параметр ни на что не влияет. Используется для группировки заданий и для их сортировки при отображении в Management Studio ;
q Description (Описание) — простое описание задания, например заметки, которые администратор делает для самого себя, чтобы не забыть, для чего предназначено это задание;
q Enabled (Включено) — если задание в настоящее время вам не нужно, но может потребоваться потом, вы можете просто снять этот флажок. Отключенное задание выполняться не будет. Можно также отключить расписание для этого задания;
q Source (Источник) — в этом параметре можно просмотреть сервер, который запускает данное задание на выполнение ( master server ). Параметр используется только для мультисерверных заданий (которые могут выполняться на разных серверах). Подробнее про мультисерверные задания будет рассказываться в разд. 8.1.6;
q Created (Создано), Last modified (Изменено в последний раз), Last Executed (Запускалось в последний раз) — это, соответственно, время создания, время последнего изменения и время последнего запуска на выполнение для задания.
На вкладке Steps (Этапы) производится самая важная часть настройки заданий. Здесь нужно будет определить этапы ( steps ), т. е. действия, из которых состоит задание. Создание этапа производится при помощи кнопки New (Новый). Вам потребуется указать имя создаваемого этапа и выбрать его тип. Если отбросить типы этапов, которые начинаются с префикса Replication (задания с такими этапами практически всегда создаются автоматически при настройке репликации), то в вашем распоряжении следующие варианты:
Отметим некоторые моменты, связанные с этим типом этапов:
· если вам нужны программные конструкции — циклы, проверки значений и т. п., то нужно использовать этот тип этапа;
· лучше всего, конечно, вначале написать и отладить скрипт при помощи специализированного средства (например, Sapien PrimalScript ), а затем скопировать его в окно свойств этапа;
q SQL Server Integration Services Package (Пакет SQL Server Integration Services ) — этот тип этапов позволяет выполнить по расписанию пакет SSIS ( DTS ). Это новый тип этапа, которого не было в SQL Server 2000 (в нем пакеты DTS можно было запускать из этапа CmdExec при помощи утилиты командной строки dtsrun ). Функциональность пакетов SSIS также очень велика. Подробнее про работу с SSIS будет рассказано в модуле 10;
q SQL Server Analysis Services Command (Команда SQL Server Analysis Services ) и SQL Server Analysis Services Query (Запрос SQL Server Analysis Services ) — эти типы задания позволяют выполнить, соответственно, команду или запрос к Analysis Services (ядру баз данных OLAP на SQL Server ).
Другие важные свойства для этапов можно определить при помощи вкладки Advanced :
q On success action (Действие при успехе) и On failure action (Действие при сбое) — эти параметры позволяют определить, что должно произойти, соответственно, после успешного или неуспешного (возникла ошибка) выполнения этого этапа. В вашем распоряжении три варианта (они одинаковые как для успешного завершения, так и для неуспешного):
· Go to the next step (Перейти к следующему этапу) — этот вариант используется по умолчанию для успешно завершившихся этапов;
· Quit the job reporting success (Выйти из задания, просигнализировав об успешном выполнении) — этот вариант предлагается использовать для последнего этапа, если он завершился успешно;
· Quit the job reporting failure (Выйти из задания, просигнализировав об ошибке) — это значение по умолчанию предлагается выбирать при возникновении ошибки в ходе выполнения этапа.
Рис. 8.2. Экран выбора первого этапа при запуске задания
q Retry attempts (Попытки повтора) — сколько раз SQL Server Agent будет пытаться повторить выполнение данного этапа, если его не удалось выполнить сразу;
q Retry intervals (Интервал повтора) — какая пауза будет сделана SQL Server Agent между попытками повторного выполнения (в минутах);
q Append output to existing file (Дописать результат к существующему файлу) — обычно этот параметр используется в тех случаях, когда вы хотите накапливать результаты выполнений этапов в каком-то файле;
q Append output to existing entry in table (Добавлять вывод к существующим записям в таблице) — если этот флажок установлен, то при каждом запуске задания в таблицу sysjobstepslogs будут добавляться новые строки. Если он снят, то при запуске задания будут удаляться все существовавшие до этого записи;
После того как вы определили нужные этапы задания, можно просмотреть их на вкладке Steps окна свойств задания. Обратите внимание, что вы можете менять этапы местами при помощи стрелок в нижней части экрана, а также определить этап, который будет запускаться первым (при запуске по расписанию).
Обычно следующее, что нужно сделать при создании задания, — подумать, нужно ли вам запускать это задание по расписанию. Если да, то нужно настроить расписание на вкладке Schedules (Расписания) свойств задания. Сама настройка расписания вряд ли требует каких-либо комментариев. Отметим только следующие моменты:
q в SQL Server 2005 предусмотрено четыре типа расписания (тип выбирается при помощи списка Schedule Type (Тип расписания) в окне свойств расписания):
· Recurring — повторяющееся действие, которое будет выполняться, например, каждый день, или каждую неделю, или каждый месяц;
· One time — действие будет выполнено только один раз;
· Start automatically when SQL Server Agent starts — задание будет запускаться автоматически каждый раз при запуске SQL Server Agent ;
· Start whenever the CPU become idle — задание будет запускаться во время простоя центрального процессора. Состояние простоя определяется на вкладке Advanced свойств SQL Server Agent ;
q чтобы задание сработало в соответствии с настроенным расписанием, необходимо, чтобы служба SQL Server Agent находилась в рабочем состоянии. Проверьте, настроен для нее режим автозапуска;
q с расписаниями можно работать отдельно от заданий. Фактически, это отдельный набор объектов. Чтобы открыть список всех имеющихся расписаний (с возможностью создания новых расписаний, изменения существующих и т. п.), можно воспользоваться командой Manage Schedules (Управлять расписаниями) контекстного меню для контейнера SQL Server Agent | Jobs ;
q если у вас уже есть подходящее расписание, но определенное для другого задания, создавать его заново не нужно. Достаточно воспользоваться кнопкой Pick (Выбрать) на вкладке Schedules и выбрать существующее расписание. Однако необходимо учитывать, что у расписания и у задания должен быть один и тот же владелец, поэтому таким способом можно выбрать только те расписания, владельцы которых совпадают с владельцем задания. Для выбора расписания достаточно выбрать в открывшемся окне Pick Schedule (Выбрать расписание) нужную строчку и нажать OK ;
q для одного задания можно настроить несколько расписаний. Во многих случаях это может быть очень удобно;
Часто бывает необходимо сделать так, чтобы задание по завершении само отчиталось о своем выполнении. Например, если у вас ночью проводится резервное копирование многих баз данных на нескольких серверах, то вы можете настроить соответствующие задания так, чтобы по окончании резервного копирования каждой базы данных отчет о нем отправлялся на почтовый ящик администратора. Просмотреть почтовые сообщения в почтовом ящике обычно проще, чем смотреть логи резервного копирования на всех серверах.
Настроить параметры «отчета» задания о своем завершении можно при помощи вкладки Notifications (Уведомления) свойств задания. На этой вкладке вы можете настроить следующие параметры:
· When the job fails — отправлять предупреждение только тогда, когда при выполнении задания возникла ошибка. Этот вариант выбирается по умолчанию;
· When the job succeeds — предупреждение будет отправляться только при успешном выполнении задания;
· When the job completes — предупреждение будет отправляться в любом случае;
q Write to the Windows Application event log (Записывать информацию о выполнении задания в журнал событий приложений Windows ) — такое решение можно использовать, например, если журналы событий Windows с разных серверов автоматически переносятся в базу данных или, например, в вашей сети работает приложение, которое автоматически отслеживает появление определенных записей в журналах событий Windows и предпринимает для них какие-либо действия (например, GFI SELM или EventTracker );
q Automatically delete job (Автоматически удалять задание) — при выборе этого варианта задание после выполнения само себя удалит. Обычно такое решение используется для заданий, которые выполняют разовые операции, например, создание базы данных-копии, в которую будут передаваться данные средствами доставки журналов или репликации.
На вкладке Targets (Назначения) свойств задания вы можете определить, на каких именно серверах будет выполняться это задание. Эта возможность доступна только для заданий на серверах, на которых настроен режим мультисерверного выполнения. Подробно про настройку этого режима говорится в разд. 8.1.6.
Продвинутые абстракции Kubernetes: Job, CronJob
Что такое 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 вместе с подами и их логами.
Манифест
Посмотрим на пример Job-манифеста.
В начале указаны название api-группы, тип сущности, имя и дальше — спецификация.
В спецификации указаны таймауты и темплейт пода, который будет запускаться. Опции backoffLimit: 2 и activeDeadlineSeconds: 60 значат, что Job будет пытаться выполнить задачу не более двух раз и в общей сложности не дольше 60 секунд.
template — это описание пода, который будет выполнять задачу; в нашем случае запускается простой контейнер busybox, который выводит текущую дату и передаёт привет из Kubernetes.
Практические примеры
И посмотрим, что получилось.
Видим, что контейнер поднялся и завершился в статусе Completed. В отличие от приложений, которые всегда работают и имеют статус Running.
Статистику по Job можно посмотреть следующей командой.
Видим, что завершились все задания, время выполнения — 5 секунд.
Ненужный Job обязательно надо удалять. Потому что, если мы не удалим его руками, Job и под будут висеть в кластере всегда — никакой garbage collector не придёт и не удалит их.
Если у вас запускаются по 10-20 заданий в час, и никто их не удаляет, они копятся и в кластере появляется много абстракций, которые никому не нужны, но место занимают. А как я уже говорил выше, каждый под в состоянии Completed — это, как минимум, два остановленных контейнера на узле. А докер демон начинает притормаживать, если на узле оказывается несколько сотен контейнеров, и не важно, работают они или остановлены.
Команда для удаления:
Что будет, если сломать Job
Job, который выполняется без проблем, не очень интересен. Давайте мы над ним немного поиздеваемся.
Поправим yaml: добавим в темплейт контейнера exit 1. То есть скажем Job’у, чтобы он завершался с кодом завершения 1. Для Kubernetes это будет сигналом о том, что Job завершился неуспешно.
Применяем и смотрим, что происходит: один контейнер создался и упал с ошибкой, затем ещё и ещё один. Больше ничего не создаётся.
В статистике подов видим, что создано три пода, у каждого статус Error. Из статистики Job следует, что у нас создан один Job и он не завершился.
Если посмотреть описание, то увидим, что было создано три пода, и Job завершился, потому что был достигнут backoffLimit.
Обратите внимание! В yaml лимит равен 2. То есть, если следовать документации, Job должен был остановиться после двух раз, но мы видим три пода. В данном случае «после выполнения двух раз» значит 3 попытки. Когда мы проделываем то же самое на интенсиве с сотней студентов, то примерно у половины создаётся два пода, а у оставшихся три. Это надо понять и простить.
Проверка ограничения по времени
Сделаем бесконечный цикл и посмотрим, как работает ограничение по времени — activeDeadlineSeconds.
Ограничения оставим теми же (60 секунд), изменим описание контейнера: сделаем бесконечный цикл.
Если посмотреть в логи, то увидим, что каждую секунду у нас появляется новый «Hello» — всё как надо.
Через 60 секунд под оказывается в статусе Terminating (иногда это происходит через ± 10 сек).
Вспомним, как в Kubernetes реализована концепция остановки подов. Когда приходит время остановить под, то есть все контейнеры в поде, контейнерам посылается sigterm-сигнал и Kubernetes ждёт определённое время, чтобы приложение внутри контейнера отреагировало на этот сигнал.
В нашем случае приложение — это простой bash-скрипт с бесконечным циклом, реагировать на сигнал некому. Kubernetes ждёт время, которое задано в параметре graceful shutdown. По дефолту — 30 секунд. То есть если за 30 секунд приложение на sigterm не среагировало, дальше посылается sigkill и процесс с pid 1 внутри контейнера убивается, контейнер останавливается.
Спустя чуть более 100 секунд под удалился. Причем ничего в кластере не осталось, потому что единственный способ остановить что-то в контейнере — это послать sigterm и sigkill. После этого приходит garbage collector, который удаляет все поды в статусе Terminating, чтобы они не засоряли кластер.
В описании Job мы увидим, что он был остановлен, так как активность превысила допустимую.
Поле restartPolicy
Этот параметр говорит kubelet, что делать с контейнером после того, как он был завершён с ошибкой. По умолчанию стоит политика Always, то есть если у нас контейнер в поде завершился, kubelet этот контейнер перезапускает. Причем, все остальные контейнеры в поде продолжают работать, а перезапускается только упавший контейнер.
Это политика по умолчанию, и если её применить в Job, то Job-контроллер не сможет получить информацию о том, что под был завершён с ошибкой. С его точки зрения под будет очень долго выполняться, а то, что kubelet перезапускает упавший контейнер, Job-контроллер не увидит.
CronJob: создание объектов Job по расписанию
Job позволяет выполнить разовые задачи, но на практике постоянно возникает потребность выполнять что-то по расписанию. И вот здесь Kubernetes предлагает CronJob.
CronJob — это yaml-манифест, на основании которого по расписанию создаются Job’ы, которые в свою очередь создают поды, а те делают полезную работу.
На первый взгляд, всё вроде бы просто, но, как и в предыдущем случае, тут есть куча мест, где можно испытать боль.
В манифесте CronJob указывают расписание и ещё несколько важных параметров.
И два параметра, которые влияют на историю выполнения.
Посмотрим на манифест CronJob и поговорим о каждом параметре подробнее.
schedule — это расписание в виде строчки, которая имеет обычный cron-формат. Строчка в примере говорит о том, что наш Job должен выполняться раз в минуту.
concurrencyPolicy — этот параметр отвечает за одновременное выполнение заданий. Бывает трёх видов: Allow, Forbid, Replace.
Allow позволяет подам запускаться. Если за минуту Job не отработал, все равно будет создан ещё один. Одновременно могут выполняться несколько Job’ов.
Например, если один Job выполняется 100 сек., а Cron выполняется раз в минуту, то запускается Job, выполняется 61 сек., в это время запускается ещё один Job. В итоге в кластере одновременно работают два Job’a, которые выполняют одну и ту же работу. Возникает положительная обратная связь: чем больше Job’ов запущено, тем больше нагрузка на кластер, тем медленнее они работают, тем дольше они работают и тем больше одновременных подов запускается — в итоге всё застывает под бешеной нагрузкой.
Replace заменяет запущенную нагрузку: старый Job убивается, запускается новый. На самом деле это не самый лучший вариант, когда прерываем то, что уже выполнялось, и начинаем ту же самую задачу выполнять заново. В каких-то случаях это возможно, в каких-то неприемлемо.
Forbid запрещает запуск новых Job’ов, пока не отработает предыдущий. С этой политикой можно быть уверенным, что всегда запускается только один экземпляр задачи. Поэтому Forbid используют наиболее часто.
jobTemplate — это шаблон, из которого создаётся объект Job. Ну а всё остальное мы уже видели в манифесте Job.
Посмотрим, что получилось:
Увидим название CronJob, расписание, параметр запуска, количество активных Job’ов и сколько времени они работают.
Раздел Suspend — временная приостановка CronJob. В данном случае указано значение False. Это значит, что CronJob выполняется. Можно отредактировать манифест и поставить опцию True, и тогда он не будет выполняться, пока мы его снова не запустим.
Active — сколько Job’ов создано, Last Schedule — когда последний раз исполнялся.
Теперь можно посмотреть статистику по Job’ам и подам.
Видно, что создан один Job.
Под создан, он выполнил полезную работу.
Что получается: CronJob создал Job, Job создал под, под отработал, завершился — всё здорово.
Ещё раз посмотрим на CronJob:
Last Schedule был 19 секунд назад. Если посмотреть на Job, то увидим, что у нас появился следующий Job и следующий под.
Возникает вопрос: а что будет, если CronJob отработает хотя бы пару недель? Неужели у нас в кластере будет столько же Job’ов и подов в статусе Completed, сколько в этой паре недель минут?
Когда CronJob’ы только появились и были на стадии альфа-тестирования, примерно это и происходило: делали CronJob раз в минуту, смотрели — работает, всё здорово, а через неделю кластер становился неработоспособным, потому что количество остановленных контейнеров на узлах было ошеломляющим. Теперь же ситуация изменилась.
Снова откроем манифест и посмотрим, что было добавлено:
Появились опции 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 раз или больше, но у нее этого не получилось:
В этом случае происходит нечто странное: CronJob перестает работать, новые Job’ы больше не создаются. Сообщение об этом приходит в Events, но хранится там недолго. Для восстановления работы приходится удалять CronJob и создавать его заново.
И еще более странное:
Если установлен параметр startingDeadlineSeconds, то поведение немного меняется. 100 пропущенных запусков должны уложиться в количество секунд, указанных в этом параметре. т. е. если в расписании стоит выполняться раз в минуту, а startingDeadlineSeconds меньше 6000 секунд, тогда CronJob будет работать всегда, но в этом случае при политике Allow возможен одновременный запуск множества Job.
И наконец, любопытный side-эффект:
Если установить опцию startingDeadlineSeconds равной нулю, Job’ы вообще перестают создаваться.
Если вы используете опцию startingDeadlineSeconds, указывайте её значение меньше, чем интервал выполнения в расписании, но не ноль.
Применяйте политику Forbid. Например, если было пропущено 5 вызовов и наступило очередное время исполнения, то контроллер не будет 5 раз запускать пропущенные задачи, а создаст только один Job. Это логичное поведение.
Особенность работы CronJob
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.