Job

Job – є одноразовим завданням, що виконується до моменту його завершення.

Завдання (Job) створює один або кілька Podʼів і буде продовжувати повторювати виконання Podʼів, доки не буде досягнуто вказану кількість успішних завершень. При успішному завершенні Podʼів завдання відстежує ці успішні завершення. Коли досягнута вказана кількість успішних завершень, завдання (тобто Job) завершується. Видалення завдання буде видаляти Podʼи, які воно створило. При призупиненні завдання буде видаляти активні Podʼи, доки завдання знову не буде відновлене.

У простому випадку можна створити один обʼєкт Job для надійного запуску одного Podʼа до завершення. Обʼєкт Job буде запускати новий Pod, якщо перший Pod зазнає невдачі або видаляється (наприклад, через відмову апаратного забезпечення вузла або перезавантаження вузла).

Також можна використовувати Job для запуску кількох Podʼів паралельно.

Якщо ви хочете запустити завдання (або одне завдання, або декілька паралельно) за розкладом, див. CronJob.

Запуск прикладу Job

Ось конфігурація прикладу Job. Тут обчислюється число π з точністю до 2000 знаків і виконується його вивід. Виконання зазвичай займає близько 10 секунд.

apiVersion: batch/v1
kind: Job
metadata:
  name: pi
spec:
  template:
    spec:
      containers:
      - name: pi
        image: perl:5.34.0
        command: ["perl",  "-Mbignum=bpi", "-wle", "print bpi(2000)"]
      restartPolicy: Never
  backoffLimit: 4

Ви можете запустити цей приклад за допомогою наступної команди:

kubectl apply -f https://kubernetes.io/examples/controllers/job.yaml

Вивід буде схожим на це:

job.batch/pi created

Перевірте статус Job за допомогою kubectl:


Name:           pi
Namespace:      default
Selector:       batch.kubernetes.io/controller-uid=c9948307-e56d-4b5d-8302-ae2d7b7da67c
Labels:         batch.kubernetes.io/controller-uid=c9948307-e56d-4b5d-8302-ae2d7b7da67c
                batch.kubernetes.io/job-name=pi
                ...
Annotations:    batch.kubernetes.io/job-tracking: ""
Parallelism:    1
Completions:    1
Start Time:     Mon, 02 Dec 2019 15:20:11 +0200
Completed At:   Mon, 02 Dec 2019 15:21:16 +0200
Duration:       65s
Pods Statuses:  0 Running / 1 Succeeded / 0 Failed
Pod Template:
  Labels:  batch.kubernetes.io/controller-uid=c9948307-e56d-4b5d-8302-ae2d7b7da67c
           batch.kubernetes.io/job-name=pi
  Containers:
   pi:
    Image:      perl:5.34.0
    Port:       <none>
    Host Port:  <none>
    Command:
      perl
      -Mbignum=bpi
      -wle
      print bpi(2000)
    Environment:  <none>
    Mounts:       <none>
  Volumes:        <none>
Events:
  Type    Reason            Age   From            Message
  ----    ------            ----  ----            -------
  Normal  SuccessfulCreate  21s   job-controller  Created pod: pi-xf9p4
  Normal  Completed         18s   job-controller  Job completed


apiVersion: batch/v1
kind: Job
metadata:
  annotations: batch.kubernetes.io/job-tracking: ""
             ...  
  creationTimestamp: "2022-11-10T17:53:53Z"
  generation: 1
  labels:
    batch.kubernetes.io/controller-uid: 863452e6-270d-420e-9b94-53a54146c223
    batch.kubernetes.io/job-name: pi
  name: pi
  namespace: default
  resourceVersion: "4751"
  uid: 204fb678-040b-497f-9266-35ffa8716d14
spec:
  backoffLimit: 4
  completionMode: NonIndexed
  completions: 1
  parallelism: 1
  selector:
    matchLabels:
      batch.kubernetes.io/controller-uid: 863452e6-270d-420e-9b94-53a54146c223
  suspend: false
  template:
    metadata:
      creationTimestamp: null
      labels:
        batch.kubernetes.io/controller-uid: 863452e6-270d-420e-9b94-53a54146c223
        batch.kubernetes.io/job-name: pi
    spec:
      containers:
      - command:
        - perl
        - -Mbignum=bpi
        - -wle
        - print bpi(2000)
        image: perl:5.34.0
        imagePullPolicy: IfNotPresent
        name: pi
        resources: {}
        terminationMessagePath: /dev/termination-log
        terminationMessagePolicy: File
      dnsPolicy: ClusterFirst
      restartPolicy: Never
      schedulerName: default-scheduler
      securityContext: {}
      terminationGracePeriodSeconds: 30
status:
  active: 1
  ready: 0
  startTime: "2022-11-10T17:53:57Z"
  uncountedTerminatedPods: {}

Щоб переглянути завершені Podʼи Job, використовуйте kubectl get pods.

Щоб вивести всі Podʼи, які належать Job у машинночитаній формі, ви можете використовувати таку команду:

pods=$(kubectl get pods --selector=batch.kubernetes.io/job-name=pi --output=jsonpath='{.items[*].metadata.name}')
echo $pods

Вивід буде схожим на це:

pi-5rwd7

Тут, селектор збігається з селектором, який використовується в Job. Параметр --output=jsonpath зазначає вираз з назвою з кожного Pod зі списку.

kubectl logs $pods

Іншим варіантом є використання kubectl logs для виводу логів з кожного Pod.

kubectl logs job/pi

Вивід буде схожим на це:

3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679821480865132823066470938446095505822317253594081284811174502841027019385211055596446229489549303819644288109756659334461284756482337867831652712019091456485669234603486104543266482133936072602491412737245870066063155881748815209209628292540917153643678925903600113305305488204665213841469519415116094330572703657595919530921861173819326117931051185480744623799627495673518857527248912279381830119491298336733624406566430860213949463952247371907021798609437027705392171762931767523846748184676694051320005681271452635608277857713427577896091736371787214684409012249534301465495853710507922796892589235420199561121290219608640344181598136297747713099605187072113499999983729780499510597317328160963185950244594553469083026425223082533446850352619311881710100031378387528865875332083814206171776691473035982534904287554687311595628638823537875937519577818577805321712268066130019278766111959092164201989380952572010654858632788659361533818279682303019520353018529689957736225994138912497217752834791315155748572424541506959508295331168617278558890750983817546374649393192550604009277016711390098488240128583616035637076601047101819429555961989467678374494482553797747268471040475346462080466842590694912933136770289891521047521620569660240580381501935112533824300355876402474964732639141992726042699227967823547816360093417216412199245863150302861829745557067498385054945885869269956909272107975093029553211653449872027559602364806654991198818347977535663698074265425278625518184175746728909777727938000816470600161452491921732172147723501414419735685481613611573525521334757418494684385233239073941433345477624168625189835694855620992192221842725502542568876717904946016534668049886272327917860857843838279679766814541009538837863609506800642251252051173929848960841284886269456042419652850222106611863067442786220391949450471237137869609563643719172874677646575739624138908658326459958133904780275901

Створення опису Job

Як і з будь-якою іншою конфігурацією Kubernetes, у Job мають бути вказані поля apiVersion, kind та metadata.

При створенні панеллю управління нових Podʼів для Job, поле .metadata.name Job є частиною основи для надання імен цим Podʼам. Імʼя Job повинно бути дійсним значенням DNS-піддомену, але це може призводити до неочікуваних результатів для імен хостів Podʼів. Для найкращої сумісності імʼя повинно відповідати більш обмеженим правилам для DNS-мітки. Навіть якщо імʼя є DNS-піддоменом, імʼя повинно бути не довше 63 символів.

Також у Job повинен бути розділ .spec.

Мітки для Job

Мітки Job повинні мати префікс batch.kubernetes.io/ для job-name та controller-uid.

Шаблон Podʼа

.spec.template — єдине обовʼязкове поле .spec.

.spec.template — це шаблон Podʼа. Він має точно таку ж схему, як Pod, окрім того, що він вкладений і не має apiVersion чи kind.

Окрім обовʼязкових полів для Podʼа , шаблон Podʼа в Job повинен вказати відповідні мітки (див. вибір Podʼа) та відповідну політику перезапуску.

Дозволяється лише RestartPolicy, рівний Never або OnFailure.

Селектор Podʼа

Поле .spec.selector є необовʼязковим. У майже всіх випадках ви не повинні його вказувати. Дивіться розділ вказування вашого власного селектора Podʼа.

Паралельне виконання для Jobs

Існують три основних типи завдань, які підходять для виконання в якості Job:

  1. Непаралельні Jobs

    • зазвичай запускається лише один Pod, якщо Pod не вдається.
    • Job завершується, як тільки його Pod завершується успішно.
  2. Паралельні Jobs із фіксованою кількістю завершень

    • вкажіть ненульове позитивне значення для .spec.completions.
    • Job являє собою загальне завдання і завершується, коли є .spec.completions успішних Podʼів.
    • при використанні .spec.completionMode="Indexed", кожен Pod отримує різний індекс у діапазоні від 0 до .spec.completions-1.
  3. Паралельні Jobs із чергою завдань

    • не вказуйте .spec.completions, типово встановлюється .spec.parallelism.
    • Podʼи повинні координувати серед себе або зовнішній сервіс для визначення над чим кожен з них має працювати. Наприклад, Pod може отримати набір з N елементів з черги завдань.
    • кожен Pod може незалежно визначити, чи завершилися всі його партнери, і, таким чином, що весь Job завершений.
    • коли будь-який Pod з Job завершується успішно, нові Podʼи не створюються.
    • як тільки хоча б один Pod завершився успішно і всі Podʼи завершені, тоді Job завершується успішно.
    • як тільки будь-який Pod завершився успішно, жоден інший Pod не повинен виконувати роботу для цього завдання чи записувати будь-який вивід. Вони всі мають бути в процесі завершення.

Для непаралельного Job ви можете залишити як .spec.completions, так і .spec.parallelism не встановленими. Коли обидва не встановлені, обидва типово встановлюються на 1.

Для Job із фіксованою кількістю завершень вам слід встановити .spec.completions на кількість необхідних завершень. Ви можете встановити .spec.parallelism чи залишити його не встановленим, і він буде встановлено типово на 1.

Для Job із чергою завдань ви повинні залишити .spec.completions не встановленим і встановити .spec.parallelism на не відʼємне ціле число.

За докладною інформацією про використання різних типів Job, див. розділ шаблони Job.

Контроль паралелізму

Запитаний паралелізм (.spec.parallelism) може бути встановлений на будь-яке не відʼємне значення. Якщо значення не вказано, стандартно воно дорівнює 1. Якщо вказано як 0, то Job ефективно призупинено до тих пір, поки воно не буде збільшене.

Фактична паралельність (кількість Podʼів, які працюють в будь-який момент) може бути більше чи менше, ніж запитаний паралелізм, з різних причин:

  • Для Job із фіксованою кількістю завершень, фактична кількість Podʼів, які працюють паралельно, не буде перевищувати кількість залишених завершень. Значення .spec.parallelism більше чи менше ігнорується.
  • Для Job із чергою завдань, жоден новий Pod не запускається після успішного завершення будь-якого Podʼа — залишені Podʼи мають право завершити роботу.
  • Якщо у контролера Job не було часу відреагувати.
  • Якщо контролер Job не зміг створити Podʼи з будь-якої причини (нестача ResourceQuota, відсутність дозволу і т.д.), тоді може бути менше Podʼів, ніж запитано.
  • Контролер Job може обмежувати створення нових Podʼів через ексцесивні попередні відмови Podʼів у цьому ж Job.
  • Коли Pod завершується відповідним чином, на його зупинку потрібен час.

Режим завершення

СТАН ФУНКЦІОНАЛУ: Kubernetes v1.24 [stable]

Для завдань з фіксованою кількістю завершень — тобто завдань, які мають не нульове .spec.completions — може бути встановлено режим завершення, який вказується в .spec.completionMode:

  • NonIndexed (стандартно): завдання вважається завершеним, коли є .spec.completions успішно завершених Podʼів. Іншими словами, завершення кожного Podʼа гомологічне одне одному. Зверніть увагу, що завдання, у яких .spec.completions дорівнює null, неявно є NonIndexed.

  • Indexed: Podʼи завдання отримують асоційований індекс завершення від 0 до .spec.completions-1. Індекс доступний через чотири механізми:

    • Анотація Podʼа batch.kubernetes.io/job-completion-index.
    • Мітка Podʼа batch.kubernetes.io/job-completion-index (для v1.28 і новіших). Зверніть увагу, що для використання цієї мітки механізм feature gate PodIndexLabel повинен бути увімкнений, і він є типово увімкненим.
    • Як частина імені хоста Podʼа, за шаблоном $(job-name)-$(index). Коли ви використовуєте Індексоване Завдання у поєднанні з Service, Podʼи всередині завдання можуть використовувати детерміністичні імена хостів для адресації одне одного через DNS. Докладні відомості щодо того, як налаштувати це, див. Завдання з комунікацією від Podʼа до Podʼа.
    • З контейнера завдання, в змінній середовища JOB_COMPLETION_INDEX.

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

Обробка відмов Podʼа та контейнера

Контейнер у Podʼі може вийти з ладу з ряду причин, таких як завершення процесу з ненульовим кодом виходу або примусове припинення роботи контейнера через перевищення ліміту памʼяті та таке інше. Якщо це стається і .spec.template.spec.restartPolicy = "OnFailure", тоді Pod залишається на вузлі, але контейнер перезапускається. Отже, ваш застосунок повинен обробляти випадок, коли він перезапускається локально, або вказувати .spec.template.spec.restartPolicy = "Never". Докладні відомості про restartPolicy див. в розділі життєвий цикл Podʼа.

Весь Pod також може вийти з ладу з ряду причин, таких як коли Pod видаляється з вузла (вузол оновлюється, перезавантажується, видаляється тощо), або якщо контейнер Podʼа виходить з ладу і .spec.template.spec.restartPolicy = "Never". Коли Pod виходить з ладу, контролер завдання запускає новий Pod. Це означає, що ваш застосунок повинен обробляти випадок, коли він перезапускається у новому Podʼі. Зокрема, він повинен обробляти тимчасові файли, блокування, неповний вивід та інше, викликане попередніми запусками.

Типово кожна відмова Podʼа враховується в ліміті .spec.backoffLimit, див. політика відмови Podʼа. Однак ви можете налаштовувати обробку відмов Podʼа, встановлюючи [політику відмови Podʼа] (#pod-failure-policy) для завдання.

Додатково ви можете вирішити враховувати відмови Podʼа незалежно для кожного індексу в Індексованому Завданні, встановлюючи поле .spec.backoffLimitPerIndex (докладні відомості див. ліміт затримки на індекс).

Зверніть увагу, що навіть якщо ви вказали .spec.parallelism = 1 і .spec.completions = 1 і .spec.template.spec.restartPolicy = "Never", той самий програмний код може іноді бути запущений двічі.

Якщо ви вказали як .spec.parallelism, так і .spec.completions більше ніж 1, то може бути запущено кілька Podʼів одночасно. Таким чином, ваші Podʼи також повинні бути стійкими до конкурентності.

Коли вмикаються feature gates PodDisruptionConditions та JobPodFailurePolicy, і поле .spec.podFailurePolicy встановлено, контролер завдання не вважає завершення Podʼа (Pod, який має встановлене поле .metadata.deletionTimestamp) за відмову, поки цей Pod не є термінальним (його .status.phase — Failed або Succeeded). Однак контролер завдання створює новий Pod, як тільки стає очевидним завершення. Якщо Pod завершується, контролер завдання оцінює .backoffLimit та .podFailurePolicy для відповідного завдання, враховуючи цей тепер вже завершений Pod.

Якщо хоча б одна з цих вимог не виконана, контролер завдання рахує завершення Podʼа негайною відмовою, навіть якщо цей Pod пізніше завершить фазою "Succeeded".

Політика відмови Podʼа backoff

Є ситуації, коли ви хочете щоб завдання зазнало збою після певної кількості спроб через логічну помилку в конфігурації тощо. Для цього встановіть .spec.backoffLimit, щоб вказати кількість спроб перед тим, як вважати завдання таким, що не вдалось. Ліміт затримки стандартно встановлено на рівні 6. Помилкові Podʼи, повʼязані з завданням, відновлюються контролером завдань з експоненційною затримкою (10 с, 20 с, 40 с …) з верхнім обмеженням у шість хвилин.

Кількість спроб обчислюється двома способами:

  • Кількість Podʼів з .status.phase = "Failed".
  • При використанні restartPolicy = "OnFailure" кількість спроб у всіх контейнерах Podʼів з .status.phase, що дорівнює Pending або Running.

Якщо хоча б один із розрахунків досягне значення .spec.backoffLimit, завдання вважається невдалим.

Ліміт затримки для кожного індексу

СТАН ФУНКЦІОНАЛУ: Kubernetes v1.29 [beta]

Коли ви запускаєте індексоване Завдання, ви можете вибрати обробку спроб для відмов Podʼа незалежно для кожного індексу. Для цього встановіть .spec.backoffLimitPerIndex, щоб вказати максимальну кількість невдалих спроб Podʼа для кожного індексу.

Коли ліміт затримки для кожного індексу перевищується для певного індексу, Kubernetes вважає цей індекс невдалим і додає його до поля .status.failedIndexes. Індекси, які виконались успішно, реєструються в полі .status.completedIndexes, незалежно від того, чи ви встановили поле backoffLimitPerIndex.

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

Ви також можете обмежити максимальну кількість позначених невдалих індексів, встановивши поле .spec.maxFailedIndexes. Коли кількість невдалих індексів перевищує значення поля maxFailedIndexes, контролер завдань запускає завершення всіх залишаються запущеними Podʼами для цього завдання. Щойно всі Podʼи будуть завершені, контролер завдань позначає все Завдання як невдале, встановлюючи умову Failed в статусі Завдання.

Ось приклад маніфесту для завдання, яке визначає backoffLimitPerIndex:

apiVersion: batch/v1
kind: Job
metadata:
  name: job-backoff-limit-per-index-example
spec:
  completions: 10
  parallelism: 3
  completionMode: Indexed  # обов'язково для функціоналу
  backoffLimitPerIndex: 1  # максимальна кількість невдач на один індекс
  maxFailedIndexes: 5      # максимальна кількість невдалих індексів перед припиненням виконання Job
  template:
    spec:
      restartPolicy: Never # обов'язково для функціоналу
      containers:
      - name: example
        image: python
        command:           # Робота завершується невдачею, оскільки є принаймні один невдалий індекс
                           # (всі парні індекси невдаються), але всі індекси виконуються,
                           # оскільки не перевищено максимальну кількість невдалих індексів.
        - python3
        - -c
        - |
          import os, sys
          print("Привіт, світ")
          if int(os.environ.get("JOB_COMPLETION_INDEX")) % 2 == 0:
            sys.exit(1)          

У вищенаведеному прикладі контролер завдань дозволяє один перезапуск для кожного з індексів. Коли загальна кількість невдалих індексів перевищує 5, тоді все завдання припиняються.

Після завершення роботи стан завдання виглядає наступним чином:

kubectl get -o yaml job job-backoff-limit-per-index-example
  status:
    completedIndexes: 1,3,5,7,9
    failedIndexes: 0,2,4,6,8
    succeeded: 5          # 1 succeeded pod for each of 5 succeeded indexes
    failed: 10            # 2 failed pods (1 retry) for each of 5 failed indexes
    conditions:
    - message: Job has failed indexes
      reason: FailedIndexes
      status: "True"
      type: Failed

Додатково ви можете використовувати ліміт затримки для кожного індексу разом з політикою збоїв Podʼа. Коли використовується ліміт затримки для кожного індексу, доступний новій дії FailIndex, який дозволяє вам уникати непотрібних повторів всередині індексу.

Політика збою Podʼа

СТАН ФУНКЦІОНАЛУ: Kubernetes v1.26 [beta]

Політика збою Podʼа, визначена за допомогою поля .spec.podFailurePolicy, дозволяє вашому кластеру обробляти відмови Podʼа на основі кодів виходу контейнера та умов Podʼа.

У деяких ситуаціях вам може знадобитися кращий контроль обробки відмов Podʼа, ніж контроль, який надає політика збою Podʼа за допомогою затримки збою Podʼа, яка базується на .spec.backoffLimit завдання. Ось деякі приклади використання:

  • Для оптимізації витрат на виконання завдань, уникнення непотрібних перезапусків Podʼа, ви можете завершити завдання, як тільки один із його Podʼів відмовить із кодом виходу, що вказує на помилку програмного забезпечення.
  • Щоб гарантувати, що ваше завдання завершиться, навіть якщо є розлади, ви можете ігнорувати відмови Podʼа, спричинені розладами (такими як випередження, виселення, ініційоване API або виселення на підставі маркування), щоб вони не враховувалися при досягненні .spec.backoffLimit ліміту спроб.

Ви можете налаштувати політику збою Podʼа в полі .spec.podFailurePolicy, щоб відповідати вищенаведеним використанням. Ця політика може обробляти відмови Podʼа на основі кодів виходу контейнера та умов Podʼа.

Ось маніфест для завдання, яке визначає podFailurePolicy:

apiVersion: batch/v1
kind: Job
metadata:
  name: job-pod-failure-policy-example
spec:
  completions: 12
  parallelism: 3
  template:
    spec:
      restartPolicy: Never
      containers:
      - name: main
        image: docker.io/library/bash:5
        command: ["bash"]        # приклад команди, що емулює помилку, яка запускає дію FailJob
        args:
        - -c
        - echo "Hello world!" && sleep 5 && exit 42
  backoffLimit: 6
  podFailurePolicy:
    rules:
    - action: FailJob
      onExitCodes:
        containerName: main      # опціонально
        operator: In             # одне з: In, NotIn
        values: [42]
    - action: Ignore             # одне з: Ignore, FailJob, Count
      onPodConditions:
      - type: DisruptionTarget   # вказує на порушення роботи Podʼу

У вищенаведеному прикладі перше правило політики збою Podʼа вказує, що завдання слід позначити як невдале, якщо контейнер main завершиться кодом виходу 42. Наступні правила стосуються саме контейнера main:

  • код виходу 0 означає, що контейнер виконався успішно
  • код виходу 42 означає, що все завдання виконалося невдало
  • будь-який інший код виходу вказує, що контейнер виконався невдало, і, отже, весь Pod буде створений заново, якщо загальна кількість перезапусків менше backoffLimit. Якщо backoffLimit досягнуте, все завдання виконалося невдало.

Друге правило політики збою Podʼа, що вказує дію Ignore для невдалих Podʼів з умовою DisruptionTarget, виключає Podʼи, які взяли участь в розладах, з хунок ліміту спроб .spec.backoffLimit.

Ось деякі вимоги та семантика API:

  • якщо ви хочете використовувати поле .spec.podFailurePolicy для завдання Job, вам також слід визначити шаблон Podʼа цього завдання з .spec.restartPolicy, встановленим на Never.
  • правила політики збою Podʼа, які ви визначаєте у spec.podFailurePolicy.rules, оцінюються послідовно. Якщо одне з правил відповідає збою Podʼа, інші правила ігноруються. Якщо жодне правило не відповідає збою Podʼа, застосовується типова обробка.
  • ви можете бажати обмежити правило певним контейнером, вказавши його ім'я у spec.podFailurePolicy.rules[*].onExitCodes.containerName. Якщо не вказано, правило застосовується до всіх контейнерів. Якщо вказано, воно повинно відповідати імені одного з контейнерів або initContainer у шаблоні Podʼа.
  • ви можете вказати дію, вживану при відповідності політиці збою Podʼа, у spec.podFailurePolicy.rules[*].action. Можливі значення:
    • FailJob: використовуйте це, щоб вказати, що завдання Podʼа має бути позначено як Failed та всі запущені Podʼи повинні бути завершені.
    • Ignore: використовуйте це, щоб вказати, що лічильник до .spec.backoffLimit не повинен збільшуватися, і повинен бути створений замінюючий Pod.
    • Count: використовуйте це, щоб вказати, що Pod повинен бути оброблений типовим способом. Лічильник до .spec.backoffLimit повинен збільшитися.
    • FailIndex: використовуйте цю дію разом із лімітом затримки на кожен індекс для уникнення непотрібних повторних спроб в межах індексу невдалого Podʼа.

Політика успіху

СТАН ФУНКЦІОНАЛУ: Kubernetes v1.30 [alpha]

При створенні Індексованого Завдання ви можете визначити, коли завдання може бути визнане успішним за допомогою .spec.successPolicy, на основі успішних Podʼів.

Типово завдання вважається успішним, коли кількість успішних Podʼів дорівнює .spec.completions. Існують ситуації, коли вам може знадобитися додатковий контроль для визначення завдання успішним:

  • Під час виконання симуляцій з різними параметрами, вам можуть не знадобитися всі симуляції для успішного завершення загального завдання.
  • При використанні шаблону лідер-робітник, успіх лідера визначає успіх чи невдачу завдання. Прикладами цього є фреймворки, такі як MPI та PyTorch тощо.

Ви можете налаштувати політику успіху у полі .spec.successPolicy, щоб задовольнити вищезазначені випадки використання. Ця політика може керувати успіхом завдання на основі успішних Podʼів. Після того, як завдання відповідає політиці успіху, контролер завдань завершує залишкові Podʼи. Політика успіху визначається правилами. Кожне правило може мати одну з наступних форм:

  • Коли ви вказуєте лише succeededIndexes, одразу після успішного завершення всіх індексів, вказаних у succeededIndexes, контролер завдань позначає завдання як успішне. succeededIndexes повинен бути списком інтервалів між 0 і .spec.completions-1.
  • Коли ви вказуєте лише succeededCount, як тільки кількість успішних індексів досягне succeededCount, контролер завдань позначає завдання як успішне.
  • Коли ви вказуєте як succeededIndexes, так і succeededCount, як тільки кількість успішних індексів з підмножини індексів, вказаних у succeededIndexes, досягне succeededCount, контролер завдань позначає завдання як успішне.

Зверніть увагу, що коли ви вказуєте кілька правил у .spec.successPolicy.rules, контролер завдань оцінює правила послідовно. Після того, як завдання задовольняє правило, контролер завдань ігнорує решту правил.

Ось приклад маніфеста для завдання із successPolicy:

apiVersion: batch/v1
kind: Job
metadata:
  name: job-success
spec:
  parallelism: 10
  completions: 10
  completionMode: Indexed # Обовʼязково для іспішної політики
  successPolicy:
    rules:
      - succeededIndexes: 0,2-3
        succeededCount: 1
  template:
    spec:
      containers:
      - name: main
        image: python
        command:          # З урахуванням того, що принаймні один із Podʼів з індексами 0, 2 та 3 успішно завершився,
                          # загальна задача вважатиметься успішною.
          - python3
          - -c
          - |
            import os, sys
            if os.environ.get("JOB_COMPLETION_INDEX") == "2":
              sys.exit(0)
            else:
              sys.exit(1)            
      restartPolicy: Never

У вищенаведеному прикладі були вказані як succeededIndexes, так і succeededCount. Тому контролер завдань позначить завдання як успішне і завершить залишкові Podʼи після успіху будь-яких зазначених індексів, 0, 2 або 3. Завдання, яке відповідає політиці успіху, отримує умову SuccessCriteriaMet. Після видалення залишкових Podʼів, завдання отримує стан Complete.

Зверніть увагу, що succeededIndexes представлено як інтервали, розділені дефісом. Номери перераховані у вигляді першого та останнього елементів серії, розділених дефісом.

Завершення завдання та очищення

Коли завдання завершується, Podʼи більше не створюються, але Podʼи зазвичай також не видаляються. Тримання їх допомагає переглядати логи завершених Podʼів для перевірки наявності помилок, попереджень чи іншого діагностичного виводу. Обʼєкт завдання також залишається після завершення, щоб ви могли переглядати його статус. Користувачу слід видаляти старі завдання після перегляду їх статусу. Видаліть завдання за допомогою kubectl (наприклад, kubectl delete jobs/pi або kubectl delete -f ./job.yaml). Коли ви видаляєте завдання за допомогою kubectl, всі його створені Podʼи також видаляються.

Стандартно завдання буде виконуватися без перерви, поки Pod не вийде з ладу (restartPolicy=Never) або контейнер не вийде з ладу з помилкою (restartPolicy=OnFailure), після чого завдання дотримується .spec.backoffLimit, описаного вище. Як тільки досягнуто .spec.backoffLimit, завдання буде позначене як невдале, і будь-які запущені Podʼи будуть завершені.

Іншим способом завершити завдання є встановлення граничного терміну виконання. Це робиться шляхом встановлення поля .spec.activeDeadlineSeconds завдання кількості секунд. activeDeadlineSeconds застосовується до тривалості завдання, незалежно від кількості створених Podʼів. Як тільки Job досягає activeDeadlineSeconds, всі його запущені Podʼи завершуються, і статус завдання стане type: Failed з reason: DeadlineExceeded.

Зауважте, що .spec.activeDeadlineSeconds Job має пріоритет перед його .spec.backoffLimit. Отже, завдання, яке повторює один або кілька невдал х Podʼів, не розпочне розгортання додаткових Podʼів, якщо activeDeadlineSeconds вже досягнуто, навіть якщо backoffLimit не досягнуто. Приклад:

apiVersion: batch/v1 kind: Job
metadata:
  name: pi-with-timeout
Стандартноimit: 5
  activeDeadlineSeconds: 100
  template:
    spec:
      containers:
      - name: pi
        image: perl:5.34.0
        command: ["perl", "-Mbignum=bpi", "-wle", "print bpi(2000)"]
      restartPolicy: Never

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

Памʼятайте, що restartPolicy застосовується до Podʼа, а не до самого завдання: автоматичного перезапуску завдання не відбудеться, якщо статус завдання type: Failed. Іншими словами, механізми завершення завдання, активовані за допомогою .spec.activeDeadlineSeconds і .spec.backoffLimit, призводять до постійної невдачі завдання, що вимагає ручного втручання для вирішення.

Автоматичне очищення завершених завдань

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

Механізм TTL для завершених завдань

СТАН ФУНКЦІОНАЛУ: Kubernetes v1.23 [stable]

Ще один спосіб автоматично очищати завершені завдання (якщо вони Complete або Failed) — це використовувати механізм TTL, наданий контролером TTL для завершених ресурсів, вказуючи поле .spec.ttlSecondsAfterFinished у Job.

Коли контролер TTL очищує Job, він каскадно видаляє Job, тобто видаляє його залежні обʼєкти, такі як Podʼи, разом з Job. Зверніть увагу що при видаленні Job будуть дотримані гарантії його життєвого циклу, такі як завершувачі.

Наприклад:

apiVersion: batch/v1
kind: Job
metadata:
  name: pi-with-ttl
spec:
  ttlSecondsAfterFinished: 100
  template:
    spec:
      containers:
      - name: pi
        image: perl:5.34.0
        command: ["perl", "-Mbignum=bpi", "-wle", "print bpi(2000)"]
      restartPolicy: Never

Job pi-with-ttl буде призначено для автоматичного видалення через 100 секунд після завершення.

Якщо поле встановлено в 0, Job буде призначено для автоматичного видалення негайно після завершення. Якщо поле не вказане, це Job не буде очищено контролером TTL після завершення.

Патерни використання завдань (Job)

Обʼєкт завдання (Job) може використовуватися для обробки набору незалежних, але повʼязаних робочих одиниць. Це можуть бути електронні листи для надсилання, кадри для відтворення, файли для транскодування, діапазони ключів у базі даних NoSQL для сканування і так далі.

У складній системі може бути кілька різних наборів робочих одиниць. Тут ми розглядаємо лише один набір робочих одиниць, якими користувач хоче управляти разом — пакетне завдання.

Існує кілька різних патернів для паралельних обчислень, кожен з власними перевагами та недоліками. Компроміси:

  • Один обʼєкт Job для кожної робочої одиниці або один обʼєкт Job для всіх робочих одиниць. Один Job на кожну робочу одиницю створює деяку накладну роботу для користувача та системи при управлінні великою кількістю об'єктів Job. Один Job для всіх робочих одиниць підходить краще для великої кількості одиниць.
  • Кількість створених Podʼів дорівнює кількості робочих одиниць або кожен Pod може обробляти кілька робочих одиниць. Коли кількість Podʼів дорівнює кількості робочих одиниць, Podʼи зазвичай вимагають менше змін у наявному коді та контейнерах. Кожен Pod, що обробляє кілька робочих одиниць, підходить краще для великої кількості одиниць.
  • Декілька підходів використовують робочу чергу. Це вимагає запуску служби черги та модифікацій існуючої програми чи контейнера, щоб зробити його сумісним із чергою роботи. Інші підходи легше адаптувати до наявного контейнеризованого застосунку.
  • Коли Job повʼязаний із headless Service, ви можете дозволити Podʼам у межах Job спілкуватися один з одним для спільної обчислень.

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

ПатернОдин обʼєкт JobКількість Pods менше за робочі одиниці?Використовувати застосунок без модифікацій?
Черга з Pod на робочу одиницюіноді
Черга змінної кількості Pod
Індексоване Завдання із статичним призначенням роботи
Завдання із спілкуванням від Pod до Podінодііноді
Розширення шаблону Job

Коли ви вказуєте завершення з .spec.completions, кожний Pod, створений контролером Job, має ідентичний spec. Це означає, що всі Podʼи для завдання матимуть однакову командну строку та один і той же шаблон, та (майже) ті ж самі змінні середовища. Ці патерни — це різні способи організації Podʼів для роботи над різними завданнями.

Ця таблиця показує обовʼязкові налаштування для .spec.parallelism та .spec.completions для кожного з патернів. Тут W — це кількість робочих одиниць.

Патерн.spec.completions.spec.parallelism
Черга з Pod на робочу одиницюWбудь-яка
Черга змінної кількості Podnullбудь-яка
Індексоване Завдання із статичним призначенням роботиWбудь-яка
Завдання із спілкуванням від Pod до PodWW
Розширення шаблону Job1повинно бути 1

Розширене використання завдань (Job)

Відкладення завдання

СТАН ФУНКЦІОНАЛУ: Kubernetes v1.24 [stable]

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

Щоб призупинити Job, ви можете оновити поле .spec.suspend Job на значення true; пізніше, коли ви захочете відновити його, оновіть його на значення false. Створення Job з .spec.suspend встановленим в true створить його в призупиненому стані.

При відновленні Job з призупинення йому буде встановлено час початку .status.startTime в поточний час. Це означає, що таймер .spec.activeDeadlineSeconds буде зупинений і перезапущений, коли Job буде призупинено та відновлено.

Коли ви призупиняєте Job, будь-які запущені Podʼи, які не мають статусу Completed, будуть завершені з сигналом SIGTERM. Буде дотримано період відповідного завершення ваших Podʼів, і вашим Podʼам слід обробити цей сигнал протягом цього періоду. Це може включати в себе збереження прогресу для майбутнього або скасування змін. Podʼи, завершені цим чином, не враховуватимуться при підрахунку completions Job.

Приклад визначення Job в призупиненому стані може виглядати так:

kubectl get job myjob -o yaml
apiVersion: batch/v1
kind: Job
metadata:
  name: myjob
spec:
  suspend: true
  parallelism: 1
  completions: 5
  template:
    spec:
      ...

Ви також можете перемикати призупинення Job, використовуючи командний рядок.

Призупиніть активний Job:

kubectl patch job/myjob --type=strategic --patch '{"spec":{"suspend":true}}'

Відновіть призупинений Job:

kubectl patch job/myjob --type=strategic --patch '{"spec":{"suspend":false}}'

Статус Job може бути використаний для визначення того, чи Job призупинено чи його було зупинено раніше:

kubectl get jobs/myjob -o yaml
apiVersion: batch/v1
kind: Job
# .metadata and .spec пропущено
status:
  conditions:
  - lastProbeTime: "2021-02-05T13:14:33Z"
    lastTransitionTime: "2021-02-05T13:14:33Z"
    status: "True"
    type: Suspended
  startTime: "2021-02-05T13:13:48Z"

Умова Job типу "Suspended" зі статусом "True" означає, що Job призупинено; поле lastTransitionTime може бути використане для визначення того, як довго Job було призупинено. Якщо статус цієї умови є "False", то Job було раніше призупинено і зараз працює. Якщо такої умови не існує в статусі Job, то Job ніколи не був зупинений.

Також створюються події, коли Job призупинено та відновлено:

kubectl describe jobs/myjob
Name:           myjob
...
Events:
  Type    Reason            Age   From            Message
  ----    ------            ----  ----            -------
  Normal  SuccessfulCreate  12m   job-controller  Created pod: myjob-hlrpl
  Normal  SuccessfulDelete  11m   job-controller  Deleted pod: myjob-hlrpl
  Normal  Suspended         11m   job-controller  Job suspended
  Normal  SuccessfulCreate  3s    job-controller  Created pod: myjob-jvb44
  Normal  Resumed           3s    job-controller  Job resumed

Останні чотири події, зокрема події "Suspended" та "Resumed", є прямим наслідком перемикання поля .spec.suspend. Протягом цього часу ми бачимо, що жоден Pods не був створений, але створення Pod розпочалося знову, як тільки Job було відновлено.

Змінні директиви планування

СТАН ФУНКЦІОНАЛУ: Kubernetes v1.27 [stable]

У більшості випадків, паралелльні завдання вимагатимуть щоб їхні Podʼи запускались з певними обмеженнями, типу "всі в одній зоні" або "всі на GPU моделі x або y", але не комбінація обох.

Поле призупинення є першим кроком для досягнення цих семантик. Призупинення дозволяє власному контролеру черги вирішити, коли повинне початися завдання; Однак після того, як завдання престає бути призупиненим, власний контролер черги не впливає на те, де насправді буде розташований Pod завдання.

Ця функція дозволяє оновлювати директиви планування Job до запуску, що дає власному контролеру черги змогу впливати на розташування Podʼів, а в той самий час здійснювати власне призначення Podʼів вузлам в kube-scheduler. Це дозволяється тільки для призупинених Завдань, які ніколи не переставали бути призупиненими раніше.

Поля в шаблоні Podʼа Job, які можна оновити, це приналежність до вузла, селектор вузла, толерантності, мітки, анотації та вікно планування.

Вказання власного селектора Podʼа

Зазвичай, коли ви створюєте обʼєкт Job, ви не вказуєте .spec.selector. Логіка системного визначення типових значень додає це поле під час створення Завдання. Вона обирає значення селектора, яке не буде перекриватися з будь-яким іншим завданням.

Однак у деяких випадках вам може знадобитися перевизначити цей автоматично встановлений селектор. Для цього ви можете вказати .spec.selector Job.

Будьте дуже уважні при цьому. Якщо ви вказуєте селектор міток, який не є унікальним для Podʼів цього Завдання, і який відповідає неповʼязаним Podʼам, то Podʼи з неповʼязаним Завданням можуть бути видалені, або це Завдання може вважати інші Podʼи завершеними, або одне чи обидва Завдання можуть відмовитися від створення Podʼів або виконуватись до завершення роботи. Якщо вибираєте неунікальний селектор, то інші контролери (наприклад, ReplicationController) та їхні Podʼи можуть проявляти непередбачувану поведінку також. Kubernetes не зупинить вас від того, що ви можете зробити помилку при зазначені .spec.selector.

Ось приклад ситуації, коли вам може знадобитися використовувати цю функцію.

Скажімо, Завдання old вже запущене. Ви хочете, щоб наявні Podʼи продовжували працювати, але ви хочете, щоб решта Podʼів, які воно створює, використовували інший шаблон Pod і щоб у Завдання було нове імʼя. Ви не можете оновити Завдання, оскільки ці поля не можна оновлювати. Отже, ви видаляєте Завдання old, але залишаєте її Podʼи запущеними, використовуючи kubectl delete jobs/old --cascade=orphan. Перед видаленням ви робите помітку, який селектор використовувати:

kubectl get job old -o yaml

Виввід схожий на цей:

kind: Job
metadata:
  name: old
  ...
spec:
  selector:
    matchLabels:
      batch.kubernetes.io/controller-uid: a8f3d00d-c6d2-11e5-9f87-42010af00002
  ...

Потім ви створюєте нове Завдання з імʼям new і явно вказуєте той самий селектор. Оскільки існуючі Podʼи мають мітку batch.kubernetes.io/controller-uid=a8f3d00d-c6d2-11e5-9f87-42010af00002, вони також контролюються Завданням new.

Вам потрібно вказати manualSelector: true в новому Завданні, оскільки ви не використовуєте селектор, який система зазвичай генерує автоматично.

kind: Job
metadata:
  name: new
  ...
spec:
  manualSelector: true
  selector:
    matchLabels:
      batch.kubernetes.io/controller-uid: a8f3d00d-c6d2-11e5-9f87-42010af00002
  ...

Саме нове Завдання матиме інший uid від a8f3d00d-c6d2-11e5-9f87-42010af00002. Встановлення manualSelector: true говорить системі, що ви розумієте, що робите, і дозволяє це неспівпадіння.

Відстеження Завдання за допомогою завершувачів

СТАН ФУНКЦІОНАЛУ: Kubernetes v1.26 [stable]

Панель управління стежить за Podʼами, які належать будь-якого Завдання, і виявляє, чи Pod був видалений з сервера API. Для цього контролер Job створює Podʼи з завершувачем batch.kubernetes.io/job-tracking. Контролер видаляє завершувач тільки після того, як Pod був врахований в стані Завдання, що дозволяє видалити Pod іншими контролерами або користувачами.

Еластичні Індексовані Завдання

СТАН ФУНКЦІОНАЛУ: Kubernetes v1.27 [beta]

Ви можете масштабувати Індексовані Завдання вгору чи вниз, змінюючи як .spec.parallelism, так і .spec.completions разом, з умовою, що .spec.parallelism == .spec.completions. Коли увімкнено feature gate ElasticIndexedJob на сервері API, .spec.completions є незмінним.

Сценарії використання для еластичних Індексованих Завдань включають пакетні робочі навантаження, які вимагають масштабування Індексованого Завдання, такі як MPI, Horovord, Ray, та PyTorch.

Відкладене створення замінних Podʼів

СТАН ФУНКЦІОНАЛУ: Kubernetes v1.29 [beta]

Типово контролер Job створює Podʼи якнайшвидше, якщо вони або зазнають невдачі, або знаходяться в стані завершення (мають відмітку видалення). Це означає, що в певний момент часу, коли деякі з Podʼів знаходяться в стані завершення, кількість робочих Podʼів для Завдання може бути більшою, ніж parallelism або більше, ніж один Pod на індекс (якщо ви використовуєте Індексоване Завданя).

Ви можете вибрати створення замінних Podʼів лише тоді, коли Podʼи, які знаходиться в стані завершення, повністю завершені (мають status.phase: Failed). Для цього встановіть .spec.podReplacementPolicy: Failed. Типова політика заміщення залежить від того, чи Завдання має встановлену політику відмови Podʼів. Якщо для Завдання не визначено політику відмови Podʼів, відсутність поля podReplacementPolicy вибирає політику заміщення TerminatingOrFailed: панель управління створює Podʼи заміни негайно після видалення Podʼів (як тільки панель управління бачить, що Pod для цього Завдання має встановлене значення deletionTimestamp). Для Завдань із встановленою політикою відмови Podʼів стандартне значення podReplacementPolicy — це Failed, інших значень не передбачено. Докладніше про політики відмови Podʼів для Завдань можна дізнатися у розділі Політика відмови Podʼіви.

kind: Job
metadata:
  name: new
  ...
spec:
  podReplacementPolicy: Failed
  ...

При увімкненому feature gate у вашому кластері ви можете перевірити поле .status.terminating Завдання. Значення цього поля — це кількість Podʼів, якими володіє Завдання, які зараз перебувають у стані завершення.

kubectl get jobs/myjob -o yaml
apiVersion: batch/v1
kind: Job
# .metadata and .spec omitted
status:
  terminating: 3 # три Podʼи, які перебувають у стані завершення і ще не досягли стану Failed

Делегування управління обʼєктом Job зовнішньому контролеру

СТАН ФУНКЦІОНАЛУ: Kubernetes v1.30 [alpha]

Ця функція дозволяє вам вимкнути вбудований контролер Job для конкретного Завдання і делегувати узгодження цього Завдання зовнішньому контролеру.

Ви вказуєте контролер, який узгоджує Завдання, встановлюючи власне значення для поля spec.managedBy — будь-яке значення окрім kubernetes.io/job-controller. Значення поля є незмінним.

Альтернативи

Тільки Podʼи

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

Контролер реплікації

Завдання є компліментарними до контролера реплікації. Контролер реплікації керує Podʼами, які не повинні завершуватися (наприклад, вебсервери), а Завдання керує Podʼами, які очікують завершення (наприклад, пакетні задачі).

Якщо врахувати життєвий цикл Podʼа, Job лише придатний для Podʼів з RestartPolicy, рівним OnFailure або Never. (Примітка: Якщо RestartPolicy не встановлено, стандартне значення — Always.)

Один Job запускає контролер Podʼів

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

Один приклад такого підходу — це Завдання, яке створює Podʼи, яка виконує сценарій, який з свого боку запускає контролер Spark master (див. приклад Spark), запускає драйвер Spark, а потім робить очищення.

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

Що далі

Змінено June 20, 2024 at 12:44 PM PST: Sync changest from andygol/k8s-website (36d05bc8a1)