Перейти до вмісту

Модуль 2.4: Jobs та CronJobs

Hands-On Lab Available
K8s Cluster intermediate 30 min
Launch Lab ↗

Opens in Killercoda in a new tab

Складність: [ШВИДКИЙ] — прості пакетні робочі навантаження

Час на проходження: 30-40 хвилин

Передумови: Модуль 2.1 (Pods)


Що ви зможете зробити

Розділ «Що ви зможете зробити»

Після цього модуля ви зможете:

  • Реалізувати Jobs та CronJobs з відповідними налаштуваннями completions, parallelism, політики перезапуску та повторних спроб.
  • Діагностувати невдалі Jobs, пов’язуючи між собою статус Job, події Pod’ів, поведінку завершення контейнера та логи.
  • Налаштувати планування CronJob, політику конкурентності, збереження історії, призупинення та ручний запуск для експлуатаційного використання.
  • Порівняти Jobs, CronJobs та Deployments, щоб обрати правильний контролер для пакетної чи довготривалої роботи.

Чому цей модуль важливий

Розділ «Чому цей модуль важливий»

Гіпотетичний сценарій: ви розгортаєте оновлення застосунку, яке потребує міграції бази даних, перш ніж нові Pod’и зможуть безпечно обслуговувати трафік. Якщо цю міграцію запакувати як Deployment, Kubernetes намагатиметься виконувати її нескінченно, і за успішною міграцією можуть піти повторні дублювальні спроби, що небезпечно для бази даних. Якщо ж запускати її вручну з термінала, робота залежить від того, чи залишиться живою людська сесія і чи пам’ятає хтось, як саме аналізувати збій, коли той станеться. Job дає Kubernetes правильний контракт для такого випадку: виконуй це завдання, доки не настане потрібна кількість успішних завершень, повторюй спроби лише в межах контрольованого бюджету та зберігай достатньо статусу й логів для подальшого налагодження.

CronJobs розв’язують повторювану, періодичну версію тієї самої проблеми. Резервні копії, генерація звітів, оновлення кешу, процедури очищення, перевірки сертифікатів та пакетні імпорти зазвичай не потребують ані стабільної адреси Service, ані безперервних реплік, що завжди працюють. Натомість їм потрібні розклад, шаблон для завдання та свідомий вибір політик для запізнілих або накладених один на одного запусків. CronJob акуратно огортає ці рішення навколо шаблону Job, тож кожне заплановане виконання стає звичайним Job зі звичайними Pod’ами, логами, подіями, повторними спробами та поведінкою очищення, яку ви вже вмієте інспектувати.

Іспит CKA схильний перевіряти цю тему через практичну роботу, а не через теоретичні дрібниці чи запам’ятовування означень. Вас можуть попросити створити Job імперативно, отримати YAML за допомогою виводу dry-run, скоригувати parallelism, усунути несправність невдалого пакетного завдання або вручну запустити CronJob у відповідь на конкретну умову. Глибша навичка, яку перевіряють під усім цим, — це розпізнавання контракту контролера: Deployments підтримують бажану кількість довготривалих реплік, Jobs прагнуть успішного завершення, а CronJobs створюють Jobs за календарним розкладом. Щойно цей контракт стає зрозумілим, команди й окремі поля стають набагато легшими для осмислення в умовах браку часу, бо ви вже знаєте, якої поведінки очікувати від кожного об’єкта.


Jobs: завершення — це бажаний стан

Розділ «Jobs: завершення — це бажаний стан»

Job — це контролер Kubernetes для скінченної роботи. Від Pod’а під управлінням Deployment очікують, що він продовжуватиме обслуговування, доки щось його не замінить, тож чисте завершення трактується як проблема, яку треба зцілити. Від Pod’а під управлінням Job очікують, що він завершиться, і важливе питання полягає в тому, чи достатньо Pod’ів завершилося успішно. Саме ця відмінність пояснює, чому Pod’и Job вимагають restartPolicy: Never або restartPolicy: OnFailure; Always суперечив би самій ідеї завершення, перезапускаючи контейнери навіть після того, як завдання вже виконане.

Уявіть Job радше як заявку в черзі на обслуговування, ніж як працівника, закріпленого за постійним робочим місцем. Контролер Job створює один або кілька Pod’ів, спостерігає за їхніми фазами та результатами завершення і позначає Job завершеним, коли потрібна робота виконана успішно. Якщо Pod зазнає невдачі, контролер вирішує, чи дозволена ще одна спроба. Якщо ж раніше вичерпається бюджет повторних спроб або дедлайн, Job стає невдалим і зберігає достатньо стану, щоб ви могли дослідити, що сталося.

Цей зв’язок із контролером також пояснює, чому для пакетної роботи зазвичай не створюють Pod безпосередньо, навіть коли це здається швидшим. Окремий Pod може виконати команду й завершитися, але в нього немає об’єкта вищого рівня, який вирішував би, чи потрібна ще одна спроба, чи достатньо спроб виявилися успішними, чи слід тепер вважати всю операцію невдалою. Job додає саме цей відсутній намір над голим Pod’ом. Він володіє Pod’ами через мітки та посилання-власники (owner references), фіксує статус на рівні контролера й дає вам один стабільний об’єкт, на який можна чекати, навіть якщо самі Pod’и під ним можуть з’являтися та зникати протягом виконання.

┌────────────────────────────────────────────────────────────────┐
│ Job Lifecycle │
│ │
│ Job Created │
│ │ │
│ ▼ │
│ Pod Created ─────────────────────────────────────────┐ │
│ │ │ │
│ ▼ │ │
│ Pod Running │ │
│ │ │ │
│ ├───► Exit 0 (Success) ──► Job Complete │ │
│ │ │ │
│ └───► Exit ≠ 0 (Fail) ──► Retry? ──────────────►┘ │
│ (based on backoffLimit) │
│ │
└────────────────────────────────────────────────────────────────┘

Найменший корисний Job має шаблон Pod’а та політику перезапуску. Приклад нижче обчислює цифри числа пі за допомогою контейнера, який завершується після виконання команди. backoffLimit — це не налаштування продуктивності; це налаштування бюджету помилок для невдалих спроб. Низьке значення швидко виявляє системні проблеми, тоді як вище значення дає тимчасовим завантаженням образів, перериванням роботи вузлів чи нестабільним зовнішнім залежностям більше шансів, перш ніж Job буде визнано невдалим.

До шаблону Pod’а всередині Job слід ставитися з тією самою ретельністю, що й до будь-якого іншого шаблону робочого навантаження. Образи слід закріплювати згідно з практикою релізів вашої команди, команди мають чітко завершуватися помилкою, коли бракує вхідних даних, а залежності від змінних середовища чи томів мають бути явними. Job робить завдання повторюваним лише тоді, коли повторюваним є шаблон. Якщо людині доводиться пам’ятати про додаткову змінну оболонки чи вручну завантажувати файл перед кожним запуском, маніфест ще не є надійним експлуатаційним інструментом.

apiVersion: batch/v1
kind: Job
metadata:
name: pi-calculation
spec:
template:
spec:
containers:
- name: pi
image: perl
command: ["perl", "-Mbignum=bpi", "-wle", "print bpi(2000)"]
restartPolicy: Never # Required for Jobs
backoffLimit: 4 # Retry up to 4 times on failure

Імперативне створення корисне на іспиті та під час швидких експлуатаційних перевірок, але ви все одно маєте розуміти YAML, який воно породжує. kubectl create job створює одноразовий шаблон Job з образу та команди, що йдуть після роздільника --. Форма dry-run особливо цінна, бо дозволяє згенерувати валідний початковий маніфест, додати поля на кшталт completions чи ttlSecondsAfterFinished, а потім застосувати відредаговану версію.

Terminal window
# Create job imperatively
kubectl create job pi --image=perl -- perl -Mbignum=bpi -wle "print bpi(100)"
# Generate YAML
kubectl create job pi --image=perl --dry-run=client -o yaml -- perl -Mbignum=bpi -wle "print bpi(100)"

Повсякденні команди інспекції повторюють той самий патерн, який ви вже знаєте за Pod’ами та Deployments: перелічити контролер, описати його для отримання умов і подій, а потім прочитати логи з Pod’а або через ярлик Job. kubectl logs job/pi-calculation зручний, коли є один очевидний Pod для вибору, але під час збоїв вам часто потрібні й самі імена Pod’ів. Невдалі Pod’и можуть залишатися після того, як Job припинив повторні спроби, і саме ці старі Pod’и часто є найкориснішим доказом.

Тут є корисна дисципліна: інспектуйте ззовні всередину. Job каже вам, чи вважає контролер, що бажаного стану досягнуто. Pod’и кажуть вам, скільки спроб відбулося і яких фаз вони сягнули. Контейнери кажуть вам причину успіху чи невдачі на рівні застосунку. Якщо почати одразу з логів контейнера, можна пропустити збій планування, дедлайн чи ліміт повторних спроб, який пояснює, чому логи неповні.

Terminal window
# List jobs
kubectl get jobs
# Watch job progress
kubectl get jobs -w
# Describe job
kubectl describe job pi-calculation
# Get job logs
kubectl logs job/pi-calculation
# Delete job (also deletes pods)
kubectl delete job pi-calculation

Зробіть паузу й передбачте: Job має restartPolicy: Never та backoffLimit: 4. Контейнер зазнає невдачі за кожної спроби. Перш ніж читати далі, що ви очікуєте побачити в kubectl get pods після того, як Job здасться, і чим це відрізнятиметься від restartPolicy: OnFailure?

Відповідь залежить від того, де відбувається повторна спроба. З Never Kubernetes зазвичай створює свіжий Pod для кожної невдалої спроби, тож початковий невдалий Pod залишається видимим, а додаткові невдалі Pod’и з’являються, доки Job не сягне свого ліміту. З OnFailure kubelet перезапускає контейнер усередині того самого Pod’а, тож докази зосереджені в лічильнику перезапусків Pod’а та попередніх логах. Обидві політики можуть бути правильними, але Never часто легше пояснювати та інспектувати, бо кожна спроба — це окремий об’єкт.

spec:
template:
spec:
restartPolicy: Never # New pod per failure
# restartPolicy: OnFailure # Restart same pod
ПолітикаПоведінка
NeverЗазвичай створює новий Pod після невдачі
OnFailureПерезапускає контейнер у тому самому Pod’і при невдачі

Найпоширеніша помилка початківця — трактувати backoffLimit як гарантію того, скільки саме Pod’ів існуватиме. Безпечніше трактувати його як політику контролера, а потім інспектувати статус і події, щоб побачити, що сталося насправді. Заміна Pod’ів, поведінка перезапуску контейнера, дедлайни та тайминг контролера — усе це може впливати на те, що ви спостерігаєте в конкретний момент. Для іспиту практичний урок простіший: задайте навмисний бюджет повторних спроб, знайте, яку політику перезапуску ви обрали, і використовуйте логи Pod’ів плюс умови Job для підтвердження результату.

Ще одна практична деталь — іменування. Імена Job стають частиною згенерованих імен Pod’ів та міток, тож короткі описові імена допомагають під час усунення несправностей. Ім’я на кшталт backup чи report-daily легше фільтрувати, ніж довге речення, закодоване з пунктуацією. Для ручних повторних запусків обирайте унікальні імена, бо імена об’єктів Kubernetes діють у межах простору імен. Якщо ви повторно використаєте ім’я, поки ще існує старий Job, API відхилить запит на створення, і ця затримка може коштувати дорогоцінних іспитових хвилин.

Completions, Parallelism та форма роботи

Розділ «Completions, Parallelism та форма роботи»

Багато Jobs не зводяться до «виконати один Pod один раз». Пакетний імпорт може мати сто незалежних файлів, генератор звітів може потребувати п’яти сегментів, а відновлення даних може потребувати кількох виконавців, що тягнуть із черги. Kubernetes дає вам дві важливі ручки для цієї форми: completions, яка каже, скільки успішних завершень Pod’ів потрібно, та parallelism, яка каже, скільки Pod’ів можуть виконуватися одночасно. Контролер продовжує створювати замінні Pod’и, доки не буде досягнуто цільової кількості успіхів або політика невдач не зупинить Job.

Ці ручки розділені, бо вони відповідають на два окремі питання. completions відповідає на питання «скільки успішних одиниць роботи роблять цей Job завершеним?», тоді як parallelism відповідає на питання «скільки роботи може відбуватися одночасно?». Корисна аналогія тут — з рестораном: можливо, потрібно приготувати сто талонів на страви, але на кухні доступні лише шість конфорок. Друк більшої кількості талонів не додає конфорок, а вмикання більшої кількості конфорок не зменшує кількості страв, які все ще треба приготувати. Jobs використовують ту саму відмінність між обсягом і темпом для пакетних Pod’ів.

apiVersion: batch/v1
kind: Job
metadata:
name: batch-job
spec:
completions: 5 # Job succeeds when 5 pods complete successfully
parallelism: 2 # Run 2 pods at a time
template:
spec:
containers:
- name: worker
image: busybox
command: ["sh", "-c", "echo Processing item; sleep 5"]
restartPolicy: Never

Зв’язок між completions та parallelism легко уявити як набір смуг руху. Якщо completions дорівнює п’яти, а parallelism дорівнює двом, Kubernetes може тримати два Pod’и активними, але йому все одно потрібно п’ять успішних завершень, перш ніж Job стане завершеним. Невдалий Pod не зараховується до успішних завершень, тож контролер створює ще одну спробу, якщо політика невдач це все ще дозволяє. Ця модель допомагає уникнути помилки задавати високий parallelism і вважати, що це означає швидше виконання завдання, незалежно від того, що насправді робить робота.

┌────────────────────────────────────────────────────────────────┐
│ Completions=5, Parallelism=2 │
│ │
│ Time ─────────────────────────────────────────────────► │
│ │
│ Slot 1: [Pod 1 ✓] [Pod 3 ✓] [Pod 5 ✓] │
│ Slot 2: [Pod 2 ✓] [Pod 4 ✓] │
│ │
│ 2 pods run concurrently, until 5 completions achieved │
│ │
└────────────────────────────────────────────────────────────────┘
ПатернcompletionsparallelismПоведінка
Один Pod1 (за замовч.)1 (за замовч.)Один Pod виконується до завершення
Фіксовані completionsNMM Pod’ів виконуються паралельно, доки N не успішних
Черга роботине заданоNN Pod’ів виконуються, доки один не успішний

Parallelism допомагає лише тоді, коли завдання можна безпечно розділити. Якщо кожен Pod пише той самий вихідний файл, мігрує той самий рядок схеми або споживає API, яке не витримує сплесків, збільшення parallelism може перетворити пакетне завдання на стан гонитви. Якщо ж кожен Pod обробляє незалежний сегмент або тягне окрему роботу з черги, parallelism — це правильний важіль масштабування. Контролер Job не розуміє ваших бізнес-правил ідемпотентності, тож команда Pod’а та опорна система мають зробити дублювальні чи замінні спроби безпечними.

В експлуатації кластера parallelism також конкурує з квотою та плановою місткістю. Простір імен може мати достатньо CPU для двох виконавців, але не для десяти, а кластер може затримувати Pod’и, якщо кожен виконавець запитує велику кількість пам’яті. Коли Job виглядає повільнішим, ніж очікувалося, не вважайте, що контролер проігнорував parallelism. Перевірте події Pod’ів на предмет очікування планування, помилок квоти ресурсів, затримки завантаження образу та тиску на вузли. Job може запитувати конкурентність, але планувальник усе ще вирішує, де кожен Pod справді може виконуватися.

Terminal window
# Scale parallelism of an existing job (completions is immutable)
kubectl create job batch --image=busybox -- sh -c "echo done; sleep 30"
kubectl patch job batch -p '{"spec":{"parallelism":3}}'
# Or create with YAML
cat << 'EOF' | kubectl apply -f -
apiVersion: batch/v1
kind: Job
metadata:
name: parallel-job
spec:
completions: 10
parallelism: 3
template:
spec:
containers:
- name: worker
image: busybox
command: ["sh", "-c", "echo Task complete; sleep 2"]
restartPolicy: Never
EOF
# Wait for completion
kubectl wait --for=condition=complete job/parallel-job --timeout=90s
kubectl get jobs parallel-job

Зверніть увагу на коментар у тому блоці команд: completions фактично є частиною робочого контракту Job, тож зазвичай ви обираєте його до створення. parallelism — безпечніше коригування під час виконання, бо воно контролює, скільки виконавців дозволено одночасно. Під час інциденту зменшення parallelism може сповільнити галасливе пакетне завдання, не видаляючи Job, тоді як збільшення може допомогти осушити безпечне накопичення роботи. Навичка — це не запам’ятовування незмінності; це розпізнавання, чи ви змінюєте обсяг роботи, чи лише темп роботи.

Jobs у стилі черги роботи заслуговують на особливу обережність, бо саме черга, а не Job, вирішує, який елемент обробляє кожен Pod. Це може бути потужним рішенням, коли виконавці претендують на елементи атомарно й позначають їх завершеними в зовнішній системі. Це також може заплутати під час іспиту, бо сам маніфест не показує кількості елементів, що чекають у черзі. Для завдань у стилі CKA фіксовані completions зазвичай легше осмислити, якщо тільки умова явно не описує робоче навантаження на основі черги.

Для більш просунутих пакетних рішень Kubernetes також підтримує індексовані Jobs, де кожен Pod отримує індекс завершення. Цей патерн корисний, коли виконавець 0 має обробляти сегмент 0, виконавець 1 має обробляти сегмент 1 тощо. Для базових завдань CKA в цьому модулі індексовані Jobs вам не потрібні, але знання про їхнє існування допомагає пояснити, чому звичайні Jobs навмисно прості. Почніть зі звичайних completions та parallelism, а до індексів вдавайтеся лише тоді, коли робота вимагає стабільної ідентичності для кожного завершення.

Якщо ви проєктуєте справжню пакетну платформу, вирішіть, де фіксується прогрес, перш ніж обирати форму Job. Фіксовані completions фіксують прогрес у Kubernetes, рахуючи успішні Pod’и, тоді як виконавці черги зазвичай фіксують прогрес у черзі чи базі даних. Індексовані Jobs ділять різницю навпіл, даючи Kubernetes стабільний індекс для кожного завершення, але застосунок усе одно має відображати цей індекс у змістовну роботу. Найчистіше рішення — те, у якому невдалий Pod можна повторити без того, щоб оператор гадав, що той уже змінив.

Перш ніж це запустити, який вивід ви очікуєте від Job із completions: 10, parallelism: 3 та командою, яка спить дві секунди? Ви маєте очікувати не більше трьох активних Pod’ів одночасно, кілька завершених Pod’ів за час життя Job та фінальний статус Job 10/10, щойно всі успішні завершення будуть зараховані. Якщо ваш кластер створює менше активних Pod’ів, шукайте обмеження планування, затримку завантаження образу, квоту простору імен або нижче значення parallelism, ніж ви хотіли.

Обробка невдач, дедлайни та докази для налагодження

Розділ «Обробка невдач, дедлайни та докази для налагодження»

Обробка невдач — це те, де Jobs стають по-справжньому експлуатаційно цікавими, бо реальні завдання зазнають невдач із багатьох незалежних причин. Пакетне завдання може зазнати невдачі, бо образ не вдається завантажити, команда завершується ненульовим кодом, відсутній ConfigMap, зникає вузол, перевищено дедлайн або зовнішня залежність відмовляє в трафіку. Контролер Job не розв’язує ці проблеми застосунку за вас, але він дає структуроване місце, куди дивитися, замість здогадів: умови Job кажуть вам, чи вважає контролер роботу завершеною чи невдалою, Pod’и кажуть, що сталося під час кожної окремої спроби, а події пояснюють проблеми планування чи рівня образу, яких немає в логах самого застосунку.

Хороша політика невдач починається з очікуваного режиму невдачі. Якщо команда хибна, більше повторних спроб лише повторюють ту саму помилку й заповнюють простір імен невдалими Pod’ами. Якщо віддалене API іноді повертає тимчасову помилку, кілька повторних спроб можуть бути саме тим, що вам потрібно. Якщо переривання роботи вузла вбиває Pod, заміна доречна, доки завдання можна безпечно відновити. Маніфест має виражати ці очікування, а не копіювати значення повторних спроб з іншого робочого навантаження.

apiVersion: batch/v1
kind: Job
metadata:
name: failing-job
spec:
backoffLimit: 3 # Retry 3 times, then fail
template:
spec:
containers:
- name: fail
image: busybox
command: ["sh", "-c", "exit 1"] # Always fails
restartPolicy: Never

backoffLimit — це перший запобіжник, бо він зупиняє зламане завдання від нескінченного створення спроб. Задавайте його низьким, коли невдача ймовірно детермінована, як-от хибна команда, відсутній файл чи невалідний аргумент. Задавайте його вищим, коли очікуються тимчасові невдачі, а завдання ідемпотентне. Це значення не є заміною оповіщень чи інспекції логів; це рішення на рівні контролера про те, коли Kubernetes має припинити витрачати місткість кластера на спроби, що не успішні.

Поведінка відкату також впливає на те, як швидко ви побачите фінальну умову невдачі. Kubernetes не обов’язково повторює спроби в щільному циклі; контролери використовують патерни відкату, щоб не бомбардувати кластер негайними замінами. Це означає, що Job з кількома дозволеними невдачами може зазнавати невдачі довше, ніж припускало б лише середовище виконання контейнерів. Під час усунення несправностей стежте і за Job, і за його Pod’ами, щоб відрізнити «усе ще повторює спроби» від «застряг, бо жоден Pod не вдається запланувати».

apiVersion: batch/v1
kind: Job
metadata:
name: timeout-job
spec:
activeDeadlineSeconds: 60 # Kill job after 60 seconds
template:
spec:
containers:
- name: long-task
image: busybox
command: ["sleep", "120"] # Tries to run 2 minutes
restartPolicy: Never

activeDeadlineSeconds — це інший вид захисту. Він накладає обмеження за настінним часом на Job, тож навіть не вичерпаний бюджет повторних спроб не може тримати Job живим після дедлайну. Це корисно, коли завдання цінне лише в межах часового вікна, як-от звіт, що має завершитися до робочих годин, чи процедура очищення, що не повинна заходити в наступне вікно обслуговування. Якщо існують і ліміт відкату, і дедлайн, то результат визначає та умова зупинки, яка стає істинною першою.

Дедлайни особливо важливі для CronJobs, бо запланована робота може концептуально накопичуватися, навіть коли об’єкти не накладаються. Щоденне завдання, що зазвичай триває десять хвилин, але раптом виконується годинами, усе ще може бути активним, коли починається наступне експлуатаційне вікно. Дедлайн змушує завдання оголосити невдачу замість того, щоб мовчки споживати час. Поєднуйте його з корисними логами та оповіщеннями, бо дедлайн без доказів лише каже вам, що час вичерпався, а не чому завдання не змогло завершитися.

Зробіть паузу й передбачте: Job із activeDeadlineSeconds: 60 та backoffLimit: 10 виконує контейнер, що триває п’ятнадцять секунд за спробу й завжди зазнає невдачі. Який запобіжник, на вашу думку, спрацює першим, і яка додаткова затримка може зробити точну кількість Pod’ів відмінною від простої арифметичної оцінки?

Правильне передбачення починається з дедлайну, а не з лічильника повторних спроб. Чотири п’ятнадцятисекундні спроби вже споживають цілу хвилину, перш ніж ви врахуєте час планування, запуск образу, затримку відкату та узгодження контролера. У справжньому кластері ви не повинні обіцяти точну кількість Pod’ів лише з маніфесту. Ви маєте інспектувати умову Job, перелічити Pod’и за job-name та прочитати події, щоб підтвердити, чи зупинився контролер тому, що сягнув дедлайну, ліміту відкату чи іншого режиму невдачі.

Terminal window
# Job status
kubectl get job myjob
# NAME COMPLETIONS DURATION AGE
# myjob 3/5 2m 5m
# Detailed status
kubectl describe job myjob | grep -A5 "Pods Statuses"
# Check failed pods
kubectl get pods -l job-name=myjob --field-selector=status.phase=Failed
ПроблемаСимптомКоманда налагодження
Збій завантаження образуPod у ImagePullBackOffkubectl describe pod <pod>
Збій командиJob може не завершитися успішноkubectl logs job/<job-name>
Тайм-аутJob убитоПеревірте activeDeadlineSeconds
Забагато повторних спробКілька невдалих Pod’івПеревірте backoffLimit

Робочий процес налагодження має рухатися від контролера до Pod’а й до виводу контейнера. Почніть з kubectl get job, бо він каже вам кількість завершень та умову високого рівня. Використовуйте kubectl describe job, бо він збирає події контролера, підрахунки статусу Pod’ів та повідомлення про невдачі. Потім перелічіть Pod’и з міткою job-name, бо невдалий Job може мати кілька Pod’ів, і найновіший не завжди той, що має найчіткіший доказ.

Terminal window
# 1. Check job status
kubectl get job myjob
kubectl describe job myjob
# 2. Find pods created by job
kubectl get pods -l job-name=myjob
# 3. Check pod logs
kubectl logs <pod-name>
kubectl logs job/myjob # Auto-selects a pod
# 4. If still running, exec into pod
kubectl exec -it <pod-name> -- /bin/sh
# 5. Check events
kubectl get events --field-selector involvedObject.name=myjob

Логи необхідні, але недостатні. Збій завантаження образу може не мати логів застосунку, бо контейнер ніколи не запускався, тоді як проблема планування на вузлі може з’явитися лише в подіях. Команда, що швидко завершується, може залишити Pod у стані Error з корисними логами, але політика перезапуску OnFailure може вимагати kubectl logs --previous на Pod’і, щоб прочитати останню невдалу спробу контейнера. Тож хороше налагодження Job — це послідовність: статус контролера, Pod’и за міткою, опис Pod’а, логи та події.

Селектор міток — ваш друг, коли згенеровані імена Pod’ів важко запам’ятати. Jobs автоматично позначають свої Pod’и іменем Job, тож kubectl get pods -l job-name=myjob дає вам набір спроб без копіювання довгих імен Pod’ів з пам’яті. Саме тому видалення чи перепозначення Pod’ів вручну може ускладнити налагодження. Дайте контролеру володіти об’єктами й використовуйте мітки, щоб спостерігати зв’язок, який Kubernetes уже підтримує для вас.

Коли важливе очищення, віддавайте перевагу ttlSecondsAfterFinished над ручними звичками. Завершені Jobs та їхні Pod’и залишаються за замовчуванням, щоб ви могли їх інспектувати, але завантажений простір імен може накопичити багато завершених об’єктів. Контролер TTL може видаляти завершені Jobs після обраної кількості секунд, що також видаляє їхні залежні Pod’и. Використовуйте його після того, як ви впевнені, що логи відправляються кудись у надійне сховище або що доказів від завершеного Pod’а вже не потрібно для звичайного усунення несправностей.

Політика очищення — це компроміс спостережуваності, а не лише прибирання. Зберігання кожного завершеного Job назавжди робить kubectl get jobs галасливим і може сповільнити людську діагностику під час інциденту. Негайне видалення тримає простір імен охайним, але прибирає зручний доступ до логів та статусу Pod’ів. Збалансований підхід зберігає достатньо нещодавньої історії для звичайних питань і відправляє важливі логи в надійну систему. У іспитовому просторі імен ручне очищення цілком прийнятне; у промисловому просторі імен робіть очищення навмисним.

CronJobs: планування Jobs без приховування Job

Розділ «CronJobs: планування Jobs без приховування Job»

CronJob — це розклад плюс шаблон Job, і не більше. У кожен запланований час контролер CronJob створює Job, а той Job, своєю чергою, створює Pod’и точно так само, як одноразові Jobs, які ви вже інспектували в попередніх розділах. Це нашарування важливе саме тому, що воно запобігає плутанині під час налагодження. Якщо розклад не спрацював узагалі, інспектуйте CronJob. Якщо ж запланований запуск спрацював, але робота зазнала невдачі, інспектуйте Job та Pod’и, створені для цього конкретного запуску. CronJob вирішує лише те, коли створювати роботу; Job вирішує, чи ця робота справді завершилася.

Згенеровані Jobs — це звичайні Jobs Kubernetes, тобто ваші наявні навички роботи з Job усе ще застосовні. Ви можете описувати їх, чекати на завершення, інспектувати їхні Pod’и та читати їхні логи. Основна відмінність — у володінні та іменуванні: CronJob володіє розкладом і створює Jobs зі згенерованими іменами. Коли ви налагоджуєте, тримайте ланцюжок «батько-дитина» чітким у своїх нотатках: розклад CronJob, згенерований Job, згенерований Pod, команда контейнера.

┌────────────────────────────────────────────────────────────────┐
│ CronJob │
│ │
│ Schedule: "0 * * * *" (hourly) │
│ │
│ 1:00 ──► Creates Job ──► Creates Pod ──► Completes │
│ 2:00 ──► Creates Job ──► Creates Pod ──► Completes │
│ 3:00 ──► Creates Job ──► Creates Pod ──► Completes │
│ ... │
│ │
└────────────────────────────────────────────────────────────────┘

Розклади CronJob у Kubernetes використовують знайомий п’ятипольовий формат cron, той самий, що й системний cron на Linux. Найлівіше поле — це хвилина, потім година, день місяця, місяць та, нарешті, день тижня. Цей порядок полів — класичне джерело помилок, бо люди часто спершу промовляють «друга година ночі», тоді як cron записує хвилину перед годиною. Коли ви читаєте вираз 0 2 * * *, перекладайте його вголос як «хвилина нуль, година два, щодня». Ця проста звичка ловить багато помилок розкладу ще до того, як вони сягнуть кластера й створять не той Job не в той час.

Розклади також мають бути реалістичними щодо тривалості завдання. Розклад «щохвилини» чудовий для лабораторної, бо ви можете швидко побачити запуск, але він рідко доречний для дорогої роботи. Якщо завдання регулярно триває вісім хвилин, п’ятихвилинний розклад змушує вас приймати рішення про конкурентність кожного запуску. Іноді правильне виправлення — це не Forbid чи Replace; це зміна інтервалу розкладу так, щоб у завдання було достатньо часу на завершення за звичайних умов.

┌───────────── minute (0 - 59)
│ ┌───────────── hour (0 - 23)
│ │ ┌───────────── day of month (1 - 31)
│ │ │ ┌───────────── month (1 - 12)
│ │ │ │ ┌───────────── day of week (0 - 6) (Sunday = 0)
│ │ │ │ │
* * * * *
РозкладОпис
* * * * *Щохвилини
0 * * * *Щогодини
0 0 * * *Щодня опівночі
0 0 * * 0Щонеділі опівночі
*/5 * * * *Кожні 5 хвилин
0 9-17 * * 1-5Щогодини з 9 до 17, пн-пт

У Kubernetes 1.35 вам також варто знати про часові пояси CronJob. Поле timeZone дозволяє вказати іменований часовий пояс для інтерпретації розкладу, тоді як старіші кластери часто залежали від локального часу controller-manager. Для портативної іспитової роботи найбезпечніше міркувати в стилі UTC, якщо тільки завдання явно не вимагає часового поясу. Для промислової роботи вказуйте часовий пояс, коли важливий місцевий діловий час, бо зміни переходу на літній час та конфігурацію контролера не варто залишати на здогад.

Ще одне пов’язане з розкладом поле, startingDeadlineSeconds, контролює, наскільки пізно запуск CronJob може стартувати, перш ніж Kubernetes порахує його пропущеним. Це має значення, коли контролер не працює, кластер перевантажений або планування затримується поза корисним вікном для завдання. Резервна копія все ще може бути цінною, якщо вона стартує на десять хвилин пізніше, тоді як звіт до відкриття ринку може бути марним після початку наради. Це поле перетворює таке експлуатаційне судження на політику контролера.

apiVersion: batch/v1
kind: CronJob
metadata:
name: backup
spec:
schedule: "0 2 * * *" # Daily at 2 AM
jobTemplate:
spec:
template:
spec:
containers:
- name: backup
image: busybox
command: ["sh", "-c", "echo Backup started; sleep 10; echo Backup done"]
restartPolicy: OnFailure
successfulJobsHistoryLimit: 3 # Keep 3 successful job records
failedJobsHistoryLimit: 1 # Keep 1 failed job record

Секцію jobTemplate багато хто з тих, хто навчається, проскакує. Це не шаблон Pod’а безпосередньо під CronJob; це шаблон Job, який потім містить шаблон Pod’а. Саме через це вкладення ліміти історії, політика конкурентності та призупинення належать CronJob, тоді як політика перезапуску та команда контейнера належать шаблону Pod’а всередині Job. Якщо маніфест відхилено, перевірте відступи та розташування полів, перш ніж припускати, що об’єкт API недоступний.

Редагуючи YAML CronJob під тиском, читайте відступи як шлях. spec.schedule належить CronJob. spec.jobTemplate.spec.template.spec.containers належить Pod’у, який створить згенерований Job. Неправильно розташоване поле може бути або відхилене, або мовчки не зробити того, що ви хотіли, бо воно сидить під неправильним об’єктом. Це одна з причин, чому вивід dry-run корисний: він дає вам правильний каркас, перш ніж ви додасте поля політики.

Terminal window
# Create CronJob imperatively
kubectl create cronjob backup --image=busybox --schedule="0 2 * * *" -- sh -c "echo Backup done"
# Generate YAML
kubectl create cronjob backup --image=busybox --schedule="*/5 * * * *" --dry-run=client -o yaml -- echo "hello"

Базові команди віддзеркалюють інші контролери. kubectl get cronjobs показує розклад, призупинення, активні запуски та час останнього розкладу. kubectl describe cronjob дає події та згенерований шаблон Job. Ручний запуск за допомогою kubectl create job --from=cronjob/name — одна з найкорисніших експлуатаційних команд, бо вона дозволяє виконати точний запланований шаблон негайно, не чекаючи на наступний тік cron.

Ручний запуск також безпечніший за копіювання команди контейнера в спеціальний Pod. Якщо шаблон CronJob містить змінні середовища, налаштування сервісного акаунта, томи, контекст безпеки чи секрети завантаження образів, --from=cronjob/name зберігає ці деталі. Ручний Job усе ще потребує власного імені, і він не змінить наступний запланований запуск CronJob. Трактуйте його як одноразове виконання того самого шаблону, а не як зміну розкладу.

Terminal window
# List CronJobs
kubectl get cronjobs
kubectl get cj # Short form
# Describe
kubectl describe cronjob backup
# Manually trigger a job from CronJob
kubectl create job --from=cronjob/backup backup-manual
# Suspend CronJob
kubectl patch cronjob backup -p '{"spec":{"suspend":true}}'
# Resume CronJob
kubectl patch cronjob backup -p '{"spec":{"suspend":false}}'
# Delete CronJob (also deletes Jobs it created)
kubectl delete cronjob backup

Зупиніться й подумайте: у вас є CronJob, який виконує резервне копіювання бази даних щогодини, але іноді резервне копіювання триває дев’яносто хвилин. Із політикою за замовчуванням concurrencyPolicy: Allow два завдання резервного копіювання можуть накластися. Що може піти не так із конкурентними резервними копіями, і яку політику конкурентності ви б обрали натомість?

Накладання не є автоматично хибним, але воно небезпечне, коли завдання торкається спільного стану. Дві резервні копії можуть конкурувати за пропускну здатність диска, утримувати блокування довше, ніж очікувалося, завантажувати об’єкт з тим самим іменем чи захоплювати неузгоджені знімки. Forbid зазвичай є консервативним вибором для резервних копій, бо він пропускає новий запуск, поки попередній усе ще активний. Replace краще підходить для завдань, де найновіший запуск цінніший за завершення застарілої роботи, як-от оновлення кешу, яке можна безпечно перезапустити.

spec:
concurrencyPolicy: Allow # Default - allow concurrent jobs
# concurrencyPolicy: Forbid # Skip if previous still running
# concurrencyPolicy: Replace # Kill previous, start new
ПолітикаПоведінка
AllowКілька Jobs можуть виконуватися одночасно
ForbidПропустити новий Job, якщо попередній усе ще виконується
ReplaceЗупинити поточний Job, запустити новий

Ліміти історії — це компаньйон очищення до політики конкурентності. successfulJobsHistoryLimit та failedJobsHistoryLimit вирішують, скільки завершених об’єктів Job зберігає CronJob. Зберігання кількох успішних записів допомагає підтвердити нормальну роботу, а зберігання невдалих записів дає вам докази, коли спрацьовує оповіщення. Зберігання надто багатьох записів створює безлад, тоді як незбереження жодного може ускладнити аналіз першопричини, якщо логи та події не захоплено десь в іншому місці.

Значення історії мають відповідати тому, як часто виконується CronJob і як швидко хтось помітить проблему. Зберігання трьох успішних запусків для щоденної резервної копії дає вам кілька днів видимого підтвердження. Зберігання трьох успішних запусків для завдання рівня хвилини показує лише кілька хвилин історії. Для невдалих запусків зберігання принаймні одного часто варте невеликої вартості об’єкта, бо воно зберігає точний Job, що зазнав невдачі, включно з мітками Pod’ів та подіями.

Призупинення — це ще один експлуатаційний контроль, що належить вашій ментальній моделі. Установлення spec.suspend: true ставить майбутні виконання розкладу на паузу, але не обов’язково видаляє Jobs, що вже існують. Це робить його корисним під час вікон обслуговування, коли ви хочете зупинити нову пакетну роботу без зміни шаблону Job. Коли ви відновлюєте CronJob, перевірте наступний запланований час і стежте за згенерованими Jobs, а не вважайте, що пропущена робота поводитиметься точно як черга.

Призупинення не є механізмом відкату. Якщо хибний шаблон уже породив хибний Job, призупинення CronJob запобігає новим запланованим Jobs, але не лагодить невдалий. Вам усе одно потрібно інспектувати чи видалити згенерований Job, виправити шаблон і вирішити, чи потрібен ручний запуск. Ця відмінність корисна в реагуванні на інциденти, бо вона відокремлює зупинку майбутньої шкоди від прибирання поточних доказів.

Патерни та антипатерни

Розділ «Патерни та антипатерни»

Найсильніший патерн для Jobs — зробити завдання ідемпотентним, перш ніж робити його паралельним чи додавати повторні спроби. Ідемпотентність означає, що повторна чи дубльована спроба не псує систему й не залишає її в напівзміненому стані: імпорт може пропускати вже оброблені рядки, резервна копія може писати в унікальний шлях об’єкта, а міграція може фіксувати, яка версія схеми вже застосована. Kubernetes може повторювати контейнери та замінювати Pod’и за вас, але він не може знати, чи безпечно виконувати саме вашу команду двічі поспіль. Цю безпеку доводиться свідомо закладати в проєкт робочого навантаження, а не очікувати її від контролера.

Ще один тривкий патерн — тримати пакетну команду нудною. Складну оркестрацію, приховану всередині однорядкового скрипту оболонки, важко правильно процитувати, важко протестувати й важко прочитати в маніфесті. Покладіть суттєву логіку в образ, скрипт чи застосунок, що має власні тести, а потім дайте маніфесту Job описувати, як Kubernetes має його виконувати. Маніфест має прояснювати поведінку контролера, потреби в ресурсах та вибір політик, а не ставати крихкою мовою програмування.

ПатернКоли використовуватиЧому це працюєМіркування щодо масштабування
Одноразовий JobМіграції, звіти, разові перевіркиЗавершення явне та інспектованеТримайте backoffLimit низьким, коли невдача детермінована
Job з фіксованими completionsВідома кількість сегментів чи файлівcompletions задає ціль успіхуНалаштовуйте parallelism під місткість та зовнішні ліміти
CronJob з ForbidРезервні копії чи очищення, що не мають накладатисяПропускає небезпечні конкурентні запускиСтежте за пропущеними розкладами й коригуйте тривалість чи інтервал
CronJob з ручним запускомАварійний повторний запуск чи валідаціяПовторно використовує точний запланований шаблонВикористовуйте унікальні імена Job для кожного ручного запуску

Найшкідливіший антипатерн — використання Deployment для скінченної роботи, бо він «уже виконує контейнери». Deployment продовжуватиме узгоджувати репліки, тож команду, що успішно завершується, може бути перезапущено так, ніби вона зазнала невдачі. Ще один поширений антипатерн — закладання складної логіки планування всередину довготривалого Pod’а, ігноруючи політику CronJob. Це приховує стан розкладу від Kubernetes і робить так, що збої виглядають як поведінка застосунку, а не контролера.

Команди також потрапляють у халепу, коли трактують CronJobs як невидимі фонові доручення. Заплановане завдання може бути таким самим критичним, як і сервіс, орієнтований на користувача, особливо коли воно обробляє резервні копії, експорт рахунків чи очищення, що захищає місткість кластера. Дайте важливим CronJobs чіткі імена, навмисну історію, оповіщення про збій чи надмірну тривалість та задокументовані кроки ручного запуску. Те, що завдання заплановане, не робить його самокерованим.

АнтипатернЩо йде не такКраща альтернатива
Deployment для міграціїУспішне завершення перезапускається або трактується як нездоровеВикористайте Job із чітким бюджетом повторних спроб
Високий parallelism без ідемпотентностіДубльовані записи, конкуренція за блокування чи неузгоджений вивідДоведіть безпечність повторних спроб, потім масштабуйте конкурентність
CronJob Allow для спільного стануЗаплановані запуски накладаються й конкуруютьВикористайте Forbid чи подовжіть інтервал розкладу
Нульова збережена історія без відправки логівДокази для налагодження зникають надто швидкоСпершу зберігайте обмежену історію чи централізуйте логи

Сценарій вправи: ви володієте нічним очищенням, що видаляє прострочені записи й іноді триває довше за звичайне. Хороший проєкт починається з операції над даними, а не з маніфесту. Якщо видалення того самого простроченого запису двічі нешкідливе, повторні спроби безпечні. Якщо два виконавці можуть сканувати той самий діапазон і битися за блокування, зменшіть parallelism чи розділіть роботу. Якщо очищення не повинно накладатися із завтрашнім запуском, задайте concurrencyPolicy: Forbid та оповіщайте, коли запуск триває довше за інтервал свого розкладу.

Для готовності до іспиту тренуйтеся перекладати речення в контролер та поля, перш ніж торкатися клавіатури. «Виконати раз і повторити двічі» вказує на Job із backoffLimit. «Виконати десять одиниць з трьома виконавцями» вказує на completions та parallelism. «Виконувати щодня, але ніколи не накладатися» вказує на розклад CronJob плюс concurrencyPolicy: Forbid. Цей крок перекладу зменшує синтаксичні помилки, бо маніфест тепер є вираженням рішення, яке ви вже прийняли.

Фреймворк прийняття рішень

Розділ «Фреймворк прийняття рішень»

Обирайте контролер, питаючи себе насамперед, що саме означає «здоровий» для цього робочого навантаження. Якщо здоровий означає «процес продовжує обслуговувати запити», використовуйте Deployment чи інший контролер довготривалих робочих навантажень. Якщо здоровий означає «завдання сягнуло успішного завершення певну кількість разів і зупинилося», використовуйте Job. Якщо здоровий означає «цей шаблон Job має створюватися за розкладом», використовуйте CronJob. Це формулювання через визначення здоров’я надійніше за вибір на основі образу, довжини команди чи суб’єктивного відчуття того, наскільки важливе завдання.

Наступне питання — скільки доказів вам потрібно після завершення роботи. Одноразовий діагностичний Job у лабораторній можна видалити одразу після прочитання його логів. Промисловий експорт зарплати може потребувати збереженого статусу Job, централізованих логів та процедури ручного повторного запуску. Часте очищення через CronJob може потребувати агресивних лімітів історії, щоб тримати простір імен читабельним. Вибір контролера починає проєктування, але збереження доказів робить проєкт експлуатованим.

ПотребаВикористатиКлючові поляЕксплуатаційне питання
Виконати команду разJobrestartPolicy, backoffLimitСкільки невдалих спроб прийнятно?
Виконати кілька незалежних одиницьJobcompletions, parallelismЧи можна роботу безпечно повторити чи продублювати?
Виконати за календаремCronJobschedule, jobTemplateЯкий часовий пояс та політику історії застосувати?
Уникнути накладання запланованої роботиCronJobconcurrencyPolicy: ForbidЧи пропуск безпечніший за накладання?
Віддати перевагу найновішій запланованій роботіCronJobconcurrencyPolicy: ReplaceЧи прийнятне завершення старої роботи?
Тримати сервіс запущенимDeploymentreplicas, проби, поля викочуванняЧи варто перезапускати чисте завершення процесу?

Який підхід ви б обрали тут і чому? Звіт має виконуватися щоранку в будні, але аналітикам також потрібно перезапускати його вручну після виправлення вхідних даних. Запланована частина належить CronJob, а повторний запуск має бути ручним Job, створеним із цього шаблону CronJob. Цей вибір тримає визначення звіту в одному місці, робить ручне виконання аудитованим як Job та уникає копіювання довгої команди в сесію оболонки під час напруженого ранку.

Коли рішення близьке, дивіться на очищення та спостережуваність. Jobs та CronJobs залишають об’єкти Kubernetes, які пояснюють, чи пакетна робота завершилася, зазнала невдачі, наклалася чи була призупинена. Deployments залишають стан викочування та реплік, що чудово для сервісів, але незручно для завершених завдань. Команда оболонки, запущена з вашого ноутбука, залишає в стані кластера майже нічого. На іспиті та в промисловому середовищі віддавайте перевагу контролеру, який робить бажаний стан видимим для Kubernetes.

Нарешті, перевірте, чи потребує робоче навантаження стабільної мережевої ідентичності. Jobs та CronJobs зазвичай не стоять за Services, бо клієнти не повинні викликати їх безперервно. Якщо іншим системам потрібно подавати роботу, вам може знадобитися довготривалий сервіс, що пише в чергу, та виконавець у стилі Job, що її осушує. Контролери Kubernetes — це будівельні блоки, а не ярлики важливості. Правильна архітектура часто поєднує Deployment для обробки запитів із Jobs чи CronJobs для скінченного фонового виконання.

  • CronJobs у Kubernetes перейшли до стабільного API batch/v1 у Kubernetes 1.21, замінивши старіший бета-шлях API.
  • Підтримка часових поясів CronJob є стабільною в сучасному Kubernetes, і Kubernetes 1.35 продовжує підтримувати .spec.timeZone для іменованих часових поясів.
  • Завершені Jobs за замовчуванням не видаляються автоматично; ttlSecondsAfterFinished делегує очищення контролеру TTL-after-finished.
  • Індексовані Jobs можуть призначати кожному завершенню стабільний індекс, що корисно для орієнтованої на сегменти пакетної роботи, де ідентичність Pod’а має значення.
ПомилкаЧому вона трапляєтьсяЯк її виправити
Використання restartPolicy: AlwaysТой, хто навчається, копіює шаблон Pod’а у стилі Deployment у JobВикористайте Never чи OnFailure, потім вирішіть, де мають бути видимі невдалі спроби
Забування backoffLimitЗавдання виглядає простим, тож поведінку повторних спроб залишають неявноюЗадайте бюджет повторних спроб, що відповідає тому, чи невдача тимчасова, чи детермінована
Трактування parallelism як цілі успіхуНазва звучить як «кількість завдань»Використовуйте completions для потрібних успіхів, а parallelism для конкурентних виконавців
Використання CronJob Allow для резервних копійЗамовчування приймається без урахування тривалості виконанняВикористовуйте Forbid, коли накладання може зіпсувати вивід чи перевантажити спільні системи
Перевірка лише kubectl logs job/nameЯрлик приховує, який Pod надав логиПерелічіть Pod’и за job-name, потім інспектуйте конкретний невдалий Pod та його події
Розміщення полів CronJob усередині jobTemplate.spec.templateВкладені шаблони Job та Pod легко сплутатиТримайте розклад, призупинення, конкурентність та історію на spec CronJob
Видалення невдалих Jobs до прочитання доказівОчищення відчувається як прогрес під час інцидентуОпишіть Job, перелічіть Pod’и, захопіть логи та перегляньте події перед видаленням
Питання 1: Розробник створює Job із `restartPolicy: Always`, і API відхиляє його. Він каже, що повтор має означати перезапуск назавжди. Як ви поясните відхилення, і яку політику перезапуску ви б обрали для легкої інспекції невдач?

restartPolicy: Always невалідна для Job, бо Job потребує завершення Pod’а, щоб представляти прогрес до завершення чи невдачі. Контейнер, який завжди перезапускається, ніколи не може чітко сказати «це завдання завершилося успішно». Для легкої інспекції невдач обирайте Never, бо кожна невдала спроба зазвичай залишає окремий невдалий Pod із власним статусом та логами. OnFailure теж може бути валідною, але вона перезапускає контейнер у тому самому Pod’і, тож вам можуть знадобитися лічильники перезапусків та попередні логи, щоб зрозуміти повторювані невдачі.

Питання 2: Ваш конвеєр даних має обробити сто незалежних об'єктів, і кожен об'єкт займає близько тридцяти секунд. Ви хочете завершити менш ніж за десять хвилин, не перевантажуючи опорне API. Які `completions` та `parallelism` ви б задали для початку, і що станеться, якщо один Pod зазнає невдачі?

Використайте completions: 100, бо ціль успіху — сто завершених одиниць роботи. Початковий parallelism шість чи вісім розумний, якщо опорне API витримує стільки конкурентних виконавців; він має завершитися менш ніж за десять хвилин, залишаючи місце для накладних витрат планування. Якщо Pod зазнає невдачі, ця невдала спроба не зараховується до ста завершень, і контролер Job створює ще одну спробу, якщо бюджет повторних спроб це дозволяє. Застосунок усе одно має зробити повторні спроби безпечними, бо Kubernetes не може запобігти дубльованим побічним ефектам усередині вашої бізнес-логіки.

Питання 3: Раннього ранку CronJob резервного копіювання з розкладом `0 2 * * *` показує `LAST SCHEDULE: `. Як ви досліджуєте проблему розкладу і як запустите резервне копіювання негайно, поки продовжуєте налагодження?

Спершу інспектуйте CronJob, а не рівень Pod’а, бо схоже, що жодного запланованого Job не було створено. Перевірте, чи spec.suspend дорівнює true, звірте вираз cron, підтвердьте очікуваний часовий пояс і прочитайте kubectl describe cronjob backup на предмет подій контролера. Щоб запустити резервне копіювання негайно, створіть Job із шаблону CronJob за допомогою kubectl create job --from=cronjob/backup backup-manual. Цей ручний Job дає вам звичайний статус та логи Job, зберігаючи запланований шаблон для виправлення першопричини.

Питання 4: CronJob агрегації метрик виконується кожні п'ять хвилин, але деякі запуски тривають сім хвилин. Із `Allow` ви бачите дубльовані дані. Ви перемикаєтесь на `Forbid`, і тепер деякі розклади пропускаються. Як ви оцінюєте `Forbid` проти `Replace`?

Forbid запобігає накладанню, пропускаючи новий запланований запуск, коли попередній Job усе ще активний, тож він захищає спільний стан, але може проґавити тіки розкладу. Replace завершує активний Job і запускає новий, тож він віддає перевагу найновішому запуску, але відкидає роботу, що вже триває. Для агрегації метрик Forbid зазвичай безпечніший, якщо кожен довгий запуск зрештою охоплює свій намічений інтервал. Краще довгострокове виправлення — скоротити завдання, збільшити інтервал чи зробити агрегацію достатньо ідемпотентною, щоб накладання більше не створювало дублікатів.

Питання 5: Job позначено невдалим, а `kubectl logs job/import` показує лише коротку помилку від одного Pod'а. Команда хоче видалити й перестворити його. Що ви маєте інспектувати перед видаленням і чому?

Перелічіть Pod’и за допомогою kubectl get pods -l job-name=import, щоб побачити кожну спробу, а не лише Pod, вибраний ярликом логів. Опишіть Job, щоб прочитати умови, лічильники повторних спроб та події контролера, потім опишіть невдалі Pod’и на предмет деталей образу, планування, тома та команди. Якщо контейнери перезапускалися на місці, перевірте також попередні логи на Pod’і. Видалення Job прибирає залежні Pod’и, тож захоплення доказів першими не дасть вам стерти інформацію, потрібну для виправлення маніфесту чи команди.

Питання 6: Завдання очищення наразі виконується як Deployment з однією реплікою. Контейнер видаляє старі записи й завершується з кодом нуль, потім Kubernetes запускає його знову. Порівняйте вибір контролерів і запропонуйте безпечніший проєкт.

Deployment хибний, бо його бажаний стан — це безперервно запущена репліка, тож чисте завершення спричиняє узгодження, а не завершення. Job — правильний контролер, якщо очищення має виконуватися раз на вимогу, бо успіх представлено завершеними завершеннями Pod’ів. CronJob — правильний контролер, якщо очищення має виконуватися за розкладом, бо він створює Jobs із шаблону й дозволяє вам налаштувати накладання та історію. Безпечніший проєкт — це CronJob з ідемпотентною командою очищення, навмисним бюджетом повторних спроб та Forbid, якщо накладені видалення можуть конкурувати за блокування.

Сценарій вправи: ви готуєте простір імен для практики пакетних робочих навантажень. Ви створите простий Job, масштабуєте completions та parallelism, поспостерігаєте за контрольованою невдачею, створите запланований CronJob та вручну запустите запланований шаблон. Тримайте об’єкти малими, щоб вправа працювала на локальному чи тренувальному кластері, і видаляйте кожен об’єкт після інспекції, щоб подальші кроки було легше читати.

Terminal window
kubectl create namespace jobs-lab
kubectl config set-context --current --namespace=jobs-lab

Завдання 1: Створення та інспекція простого Job

Розділ «Завдання 1: Створення та інспекція простого Job»

Це перше завдання доводить базовий життєвий цикл. Створіть Job, що виводить повідомлення, дочекайтеся його завершення, прочитайте його логи й видаліть його. Важливе спостереження — те, що завершення є бажаним станом, тож Pod, що завершується з кодом нуль, є успіхом, а не циклом збоїв.

Terminal window
kubectl create job hello --image=busybox -- echo "Hello from job"
kubectl wait --for=condition=complete job/hello --timeout=60s
kubectl get jobs
kubectl logs job/hello
kubectl delete job hello
Нотатки до розв'язання

Job має швидко сягнути стану complete, а kubectl logs job/hello має вивести повідомлення з контейнера. Якщо команда очікування завершується тайм-аутом, опишіть Job та Pod, перш ніж щось видаляти. Найімовірніші причини на тренувальному кластері — затримка завантаження образу, квота простору імен чи помилка в команді.

Завдання 2: Створення Job із completions та parallelism

Розділ «Завдання 2: Створення Job із completions та parallelism»

Тепер створіть Job, що вимагає п’яти успішних завершень, виконуючи два Pod’и одночасно. Поспостерігайте за Pod’ами недовго й підтвердьте, що успішні завершення накопичуються, доки Job не сягне цілі. Це найменший практичний приклад розділення «скільки роботи» від «скільки виконавців одночасно».

Terminal window
cat << 'EOF' | kubectl apply -f -
apiVersion: batch/v1
kind: Job
metadata:
name: batch-processor
spec:
completions: 5
parallelism: 2
template:
spec:
containers:
- name: processor
image: busybox
command: ["sh", "-c", "echo Processing $(hostname); sleep 3"]
restartPolicy: Never
EOF
kubectl wait --for=condition=complete job/batch-processor --timeout=90s
kubectl get jobs batch-processor
kubectl get pods -l job-name=batch-processor
kubectl delete job batch-processor
Нотатки до розв'язання

Ви маєте побачити п’ять успішних завершень за час життя Job, але не більше двох активних Pod’ів одночасно. Якщо ви бачите лише один Pod за раз, перевірте parallelism, а потім інспектуйте події планування. Якщо Job завершується, але Pod’и залишаються, це нормально; Jobs зберігають Pod’и для інспекції, доки Job не буде видалено або політика очищення TTL їх не прибере.

Завдання 3: Створення та діагностика Job, що зазнає невдачі

Розділ «Завдання 3: Створення та діагностика Job, що зазнає невдачі»

Це завдання навмисно створює команду, що завершується зі статусом один. Дайте Job зазнати невдачі, потім інспектуйте Job, Pod’и та логи перед очищенням. Мета не в тому, щоб змусити його пройти; мета — попрактикуватися в зборі доказів у правильному порядку.

Terminal window
cat << 'EOF' | kubectl apply -f -
apiVersion: batch/v1
kind: Job
metadata:
name: failing-job
spec:
backoffLimit: 2
template:
spec:
containers:
- name: fail
image: busybox
command: ["sh", "-c", "echo 'About to fail'; exit 1"]
restartPolicy: Never
EOF
kubectl wait --for=condition=failed job/failing-job --timeout=60s
kubectl get jobs failing-job
kubectl get pods -l job-name=failing-job # Multiple failed pods
kubectl logs job/failing-job
kubectl delete job failing-job
Нотатки до розв'язання

Job має зазнати невдачі після вичерпання бюджету повторних спроб. Оскільки політика перезапуску — Never, ви маєте очікувати кілька невдалих Pod’ів, а не один Pod із багатьма перезапусками. Якщо kubectl logs job/failing-job вибирає лише один Pod, перелічіть Pod’и за міткою та інспектуйте логи окремих Pod’ів, щоб порівняти спроби.

Завдання 4: Створення CronJob та спостереження за запланованим запуском

Розділ «Завдання 4: Створення CronJob та спостереження за запланованим запуском»

Створіть CronJob, що виконується щохвилини, дочекайтеся достатньо довго, щоб з’явився принаймні один Job, і прочитайте логи згенерованого Job. Розклад навмисно частий для практики; не використовуйте розклад «щохвилини» для дорогої промислової роботи, якщо тільки завдання не спроєктоване для цього.

Terminal window
kubectl create cronjob minute-job --image=busybox --schedule="*/1 * * * *" -- date
# Wait for it to run
sleep 70
kubectl get cronjobs
kubectl get jobs
JOB_NAME=$(kubectl get jobs -o name | grep minute-job | head -n 1)
kubectl logs $JOB_NAME
kubectl delete cronjob minute-job
Нотатки до розв'язання

kubectl get cronjobs має показати розклад та час останнього розкладу після того, як контролер створить Job. Ім’я згенерованого Job містить ім’я CronJob плюс суфікс, тому команда захоплює його динамічно. Якщо жодного Job не з’являється, опишіть CronJob, звірте розклад і перевірте, чи нормально працює controller manager вашого кластера.

Завдання 5: Ручний запуск шаблону CronJob

Розділ «Завдання 5: Ручний запуск шаблону CronJob»

Створіть щоденний CronJob, який не запуститься природно під час вправи, потім запустіть його шаблон Job вручну. Це той експлуатаційний хід, який ви використовуєте, коли запланованому завданню потрібен негайний повторний запуск після того, як ви виправили вхідні дані чи відновилися після пропущеного розкладу.

Terminal window
kubectl create cronjob backup --image=busybox --schedule="0 0 * * *" -- echo "backup"
# Trigger manually
kubectl create job --from=cronjob/backup backup-now
kubectl get jobs
kubectl wait --for=condition=complete job/backup-now --timeout=60s
kubectl logs job/backup-now
kubectl delete job backup-now
kubectl delete cronjob backup
Нотатки до розв'язання

Ручний Job має завершитися, навіть попри те, що природний розклад CronJob — це опівніч. Важливий момент — те, що --from=cronjob/backup копіює шаблон Job CronJob, тож ручний запуск задіює ту саму команду та образ. Використовуйте унікальне ім’я ручного Job щоразу, бо імена Job діють у межах простору імен.

Завдання 6: Виклик — повний робочий процес Job

Розділ «Завдання 6: Виклик — повний робочий процес Job»

Створіть Job, що виконує чотири completions, виконує два Pod’и одночасно, виводить ім’я свого хоста, спить три секунди, має ліміт відкату два й автоматично видаляється через шістдесят секунд. Спробуйте написати маніфест із пам’яті, перш ніж відкривати розв’язання. Виклик поєднує кількість завершень, конкурентність, бюджет повторних спроб та очищення в одному малому об’єкті.

Terminal window
# Create the challenge Job manifest, apply it, and wait for completion.
Розв'язання
Terminal window
cat << 'EOF' | kubectl apply -f -
apiVersion: batch/v1
kind: Job
metadata:
name: challenge-job
spec:
completions: 4
parallelism: 2
backoffLimit: 2
ttlSecondsAfterFinished: 60
template:
spec:
containers:
- name: worker
image: busybox
command: ["sh", "-c", "echo $HOSTNAME; sleep 3"]
restartPolicy: Never
EOF
kubectl wait --for=condition=complete job/challenge-job --timeout=60s
kubectl get job challenge-job

Додаткові тренувальні вправи

Розділ «Додаткові тренувальні вправи»

Ці вправи зберігають форми команд, які ви, найімовірніше, використовуватимете під час підготовки до іспиту. Виконуйте їх лише після того, як основні завдання стануть зрозумілими, бо швидкість без діагностики може приховати слабкі місця. Кожна вправа має закінчуватися очищенням, а кожну вправу, орієнтовану на невдачу, слід інспектувати перед видаленням.

Terminal window
# Create job
kubectl create job quick --image=busybox -- echo "done"
# Wait for completion
kubectl wait --for=condition=complete job/quick --timeout=60s
# Check logs
kubectl logs job/quick
# Cleanup
kubectl delete job quick
Terminal window
cat << 'EOF' | kubectl apply -f -
apiVersion: batch/v1
kind: Job
metadata:
name: parallel
spec:
completions: 6
parallelism: 3
template:
spec:
containers:
- name: worker
image: busybox
command: ["sh", "-c", "echo Pod: $HOSTNAME; sleep 5"]
restartPolicy: Never
EOF
# Watch
kubectl get pods -l job-name=parallel -w &
kubectl get job parallel -w &
sleep 30
kill %1 %2 2>/dev/null
# Cleanup
kubectl delete job parallel
Terminal window
cat << 'EOF' | kubectl apply -f -
apiVersion: batch/v1
kind: Job
metadata:
name: timeout-test
spec:
activeDeadlineSeconds: 10
template:
spec:
containers:
- name: long-task
image: busybox
command: ["sleep", "60"]
restartPolicy: Never
EOF
# Watch job timeout
kubectl get job timeout-test -w &
sleep 15
kill %1 2>/dev/null
# Check status
kubectl get job timeout-test -o jsonpath='{range .status.conditions[*]}{.type}{"="}{.status}{" reason="}{.reason}{"\n"}{end}'
# Cleanup
kubectl delete job timeout-test
Terminal window
# Create CronJob
kubectl create cronjob every-minute --image=busybox --schedule="*/1 * * * *" -- date
# Verify
kubectl get cronjob every-minute
# Wait for first run
sleep 70
# Check jobs created
kubectl get jobs
# Cleanup
kubectl delete cronjob every-minute
Terminal window
# Create CronJob (won't run for a while)
kubectl create cronjob daily --image=busybox --schedule="0 0 * * *" -- echo "daily task"
# Trigger manually
kubectl create job --from=cronjob/daily daily-manual-run
# Check
kubectl get jobs
kubectl wait --for=condition=complete job/daily-manual-run --timeout=60s
kubectl logs job/daily-manual-run
# Cleanup
kubectl delete job daily-manual-run
kubectl delete cronjob daily
Terminal window
# Create intentionally broken job
cat << 'EOF' | kubectl apply -f -
apiVersion: batch/v1
kind: Job
metadata:
name: broken
spec:
backoffLimit: 2
template:
spec:
containers:
- name: app
image: busybox
command: ["sh", "-c", "cat /nonexistent/file"]
restartPolicy: Never
EOF
# Diagnose
kubectl get job broken
kubectl get pods -l job-name=broken
kubectl describe job broken
kubectl logs job/broken
# Answer: What's the error? How would you fix it?
# Cleanup
kubectl delete job broken

Критерії успіху:

  • Можу створювати Jobs імперативно та декларативно
  • Можу пояснити й поспостерігати за completions та parallelism
  • Можу діагностувати невдалі Jobs за допомогою статусу, Pod’ів, подій та логів
  • Можу створювати CronJobs з розкладом та політикою історії
  • Можу вручну запускати CronJobs через згенерований Job
  • Можу обирати між Job, CronJob та Deployment для пакетного робочого навантаження

Модуль 2.5: Керування ресурсами навчає, як requests, limits та класи QoS формують планування та поведінку під час виконання для Pod’ів, створених контролерами на кшталт Jobs, CronJobs та Deployments.