1 - Виконання автоматизованих завдань за допомогою CronJob

На цій сторінці показано, як виконувати автоматизовані завдання за допомогою обʼєкта Kubernetes CronJob.

Перш ніж ви розпочнете

  • Вам треба мати кластер Kubernetes, а також інструмент командного рядка kubectl має бути налаштований для роботи з вашим кластером. Рекомендується виконувати ці настанови у кластері, що має щонайменше два вузли, які не виконують роль вузлів управління. Якщо у вас немає кластера, ви можете створити його, за допомогою minikube або використовувати одну з цих пісочниць:

Створення CronJob

CronJobs потребують файлу конфігурації. Ось маніфест CronJob, який щохвилини запускає просте демонстраційне завдання:

apiVersion: batch/v1
kind: CronJob
metadata:
  name: hello
spec:
  schedule: "* * * * *"
  jobTemplate:
    spec:
      template:
        spec:
          containers:
          - name: hello
            image: busybox:1.28
            imagePullPolicy: IfNotPresent
            command:
            - /bin/sh
            - -c
            - date; echo Hello from the Kubernetes cluster
          restartPolicy: OnFailure

Запустіть приклад CronJob за допомогою цієї команди:

kubectl create -f https://k8s.io/examples/application/job/cronjob.yaml

Вивід подібний до такого:

cronjob.batch/hello created

Після створення CronJob отримайте його статус за допомогою цієї команди:

kubectl get cronjob hello

Вивід подібний до такого:

NAME    SCHEDULE      SUSPEND   ACTIVE   LAST SCHEDULE   AGE
hello   */1 * * * *   False     0        <none>          10s

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

kubectl get jobs --watch

Вивід подібний до такого:

NAME               COMPLETIONS   DURATION   AGE
hello-4111706356   0/1                      0s
hello-4111706356   0/1           0s         0s
hello-4111706356   1/1           5s         5s

Тепер ви побачили один запущений Job, запланований CronJob "hello". Ви можете припинити відстеження Job і знову переглянути CronJob, щоб побачити, що він запланував Job:

kubectl get cronjob hello

Вивід подібний до такого:

NAME    SCHEDULE      SUSPEND   ACTIVE   LAST SCHEDULE   AGE
hello   */1 * * * *   False     0        50s             75s

Ви маєте побачити, що CronJob hello успішно запланував Job у час, вказаний у LAST SCHEDULE. Наразі активних Jobs 0, тобто Job завершився успішно або з помилкою.

Тепер знайдіть Podʼи, які створив останній запланований Job, і перегляньте стандартний вивід одного з Podʼів.

Примітка:

Назва Job відрізняється від назви Podʼа.
# Замініть "hello-4111706356" на назву Job у вашій системі
pods=$(kubectl get pods --selector=job-name=hello-4111706356 --output=jsonpath={.items[*].metadata.name})

Покажіть лог Podʼа:

kubectl logs $pods

Вивід подібний до такого:

Fri Feb 22 11:02:09 UTC 2019
Hello from the Kubernetes cluster

Видалення CronJob

Коли CronJob більше не потрібен, видаліть його за допомогою kubectl delete cronjob <cronjob name>:

kubectl delete cronjob hello

Видалення CronJob вилучає всі створені ним Jobs і Podʼи та зупиняє створення нових Jobs. Докладніше про вилучення Jobs читайте в розділі збір сміття.

2 - Груба паралельна обробка за допомогою робочої черги

У цьому прикладі ви запустите Job Kubernetes з кількома паралельними робочими процесами.

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

Ось огляд кроків у цьому прикладі:

  1. Запустіть сервіс черги повідомлень. У цьому прикладі використовується RabbitMQ, але можна використати інший. На практиці ви налаштували б сервіс черги повідомлень один раз і повторно використовували б його для багатьох Jobs.
  2. Створіть чергу й заповніть її повідомленнями. Кожне повідомлення представляє одне завдання, яке потрібно виконати. У цьому прикладі повідомлення — це ціле число, над яким ми виконаємо тривалі обчислення.
  3. Запустіть Job, який працює над завданнями з черги. Job запускає кілька Podʼів. Кожен Pod бере одне завдання з черги повідомлень, обробляє його й завершує роботу.

Перш ніж ви розпочнете

Ви вже маєте знати базове, непаралельне використання Job.

Вам треба мати кластер Kubernetes, а також інструмент командного рядка kubectl має бути налаштований для роботи з вашим кластером. Рекомендується виконувати ці настанови у кластері, що має щонайменше два вузли, які не виконують роль вузлів управління. Якщо у вас немає кластера, ви можете створити його, за допомогою minikube або використовувати одну з цих пісочниць:

Вам знадобиться реєстр образів контейнерів, куди можна завантажувати образи для запуску у вашому кластері.

Цей приклад завдання також передбачає, що Docker встановлено локально.

Запуск сервісу черги повідомлень

У цьому прикладі використовується RabbitMQ, проте ви можете адаптувати його для іншого сервісу повідомлень типу AMQP.

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

Запустіть RabbitMQ таким чином:

# Створіть Service для використання StatefulSet
kubectl create -f https://kubernetes.io/examples/application/job/rabbitmq/rabbitmq-service.yaml
service "rabbitmq-service" created
kubectl create -f https://kubernetes.io/examples/application/job/rabbitmq/rabbitmq-statefulset.yaml
statefulset "rabbitmq" created

Тестування сервісу черги повідомлень

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

Спочатку створіть тимчасовий інтерактивний Pod.

# Створіть тимчасовий інтерактивний контейнер
kubectl run -i --tty temp --image ubuntu:22.04
Waiting for pod default/temp-loe07 to be running, status is Pending, pod ready: false
... [ previous line repeats several times .. hit return when it stops ] ...

Зверніть увагу, що назва вашого Podʼа й запрошення командного рядка будуть іншими.

Далі встановіть amqp-tools, щоб працювати з чергами повідомлень. Наведені нижче команди показують, що потрібно виконати в інтерактивній оболонці цього Podʼа:

apt-get update && apt-get install -y curl ca-certificates amqp-tools python3 dnsutils

Пізніше ви створите образ контейнера, що містить ці пакети.

Далі перевірте, чи можете виявити Service для RabbitMQ:

# Виконайте ці команди всередині Podʼа
# Зверніть увагу, що rabbitmq-service має DNS-імʼя, надане Kubernetes:
nslookup rabbitmq-service
Server:        10.0.0.10
Address:    10.0.0.10#53

Name:    rabbitmq-service.default.svc.cluster.local
Address: 10.0.147.152

(IP-адреси будуть іншими)

Якщо надбудову DNS кластера налаштовано неправильно, попередній крок може не спрацювати. Ви також можете знайти IP-адресу цього Service у змінній середовища:

# виконайте цю перевірку всередині Podʼа
env | grep RABBITMQ_SERVICE | grep HOST
RABBITMQ_SERVICE_SERVICE_HOST=10.0.147.152

(IP-адреса буде іншою)

Далі перевірте, чи можете створити чергу, а також публікувати й отримувати повідомлення.

# Виконайте ці команди всередині Podʼа
# У наступному рядку rabbitmq-service — це імʼя хоста, за яким доступний rabbitmq-service.
# 5672 — стандартний порт rabbitmq.
export BROKER_URL=amqp://guest:guest@rabbitmq-service:5672
# Якщо на попередньому кроці не вдалося розвʼязати "rabbitmq-service",
# використайте натомість цю команду:
BROKER_URL=amqp://guest:guest@$RABBITMQ_SERVICE_SERVICE_HOST:5672

# Тепер створіть чергу:

/usr/bin/amqp-declare-queue --url=$BROKER_URL -q foo -d
foo

Опублікуйте одне повідомлення в чергу:

/usr/bin/amqp-publish --url=$BROKER_URL -r foo -p -b Hello

# І отримайте його назад.

/usr/bin/amqp-consume --url=$BROKER_URL -q foo -c 1 cat && echo 1>&2
Hello

В останній команді інструмент amqp-consume взяв одне повідомлення (-c 1) із черги й передав його на стандартний ввід довільної команди. У цьому випадку програма cat виводить символи, прочитані зі стандартного вводу, а echo додає повернення каретки, щоб приклад було зручно читати.

Заповнення черги завданнями

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

На практиці вміст повідомлень може бути таким:

  • назви файлів, які потрібно обробити
  • додаткові прапорці для програми
  • діапазони ключів у таблиці бази даних
  • параметри конфігурації для симуляції
  • номери кадрів сцени для рендерингу

Якщо всім Podʼам Job потрібні великі дані лише для читання, їх зазвичай розміщують у спільній файловій системі на кшталт NFS і монтують лише для читання в усі Podʼи або пишуть програму в Podʼі так, щоб вона могла напряму читати дані з кластерної файлової системи (наприклад, HDFS).

У цьому прикладі ви створите чергу й заповните її за допомогою інструментів командного рядка AMQP. На практиці можна написати програму для заповнення черги за допомогою клієнтської бібліотеки AMQP.

# Виконайте це на вашому компʼютері, а не в Podʼі
/usr/bin/amqp-declare-queue --url=$BROKER_URL -q job1  -d
job1

Додайте елементи до черги:

for f in apple banana cherry date fig grape lemon melon
do
  /usr/bin/amqp-publish --url=$BROKER_URL -r job1 -p -b $f
done

Ви додали 8 повідомлень до черги.

Створення образу контейнера

Тепер ви готові створити образ, який ви будете запускати як Job.

Job використовуватиме утиліту amqp-consume, щоб читати повідомлення з черги й виконувати фактичну роботу. Ось дуже простий приклад програми:

#!/usr/bin/env python

# Just prints standard out and sleeps for 10 seconds.
import sys
import time
print("Processing " + sys.stdin.readlines()[0])
time.sleep(10)

Надайте скрипту дозвіл на виконання:

chmod +x worker.py

Тепер зберіть образ. Створіть тимчасову теку, перейдіть до неї, завантажте Dockerfile і worker.py. У будь-якому разі зберіть образ за допомогою цієї команди:

docker build -t job-wq-1 .

Для Docker Hub додайте до образу застосунку теґ зі своїм іменем користувача й надішліть його в Hub наведеними нижче командами. Замініть <username> своїм іменем користувача Hub.

docker tag job-wq-1 <username>/job-wq-1
docker push <username>/job-wq-1

Якщо ви використовуєте інший реєстр образів контейнерів, додайте теґ до образу й надішліть його туди.

Визначення Job

Ось маніфест Job. Потрібно зробити копію маніфесту Job (назвіть її ./job.yaml) і змінити назву образу контейнера на ту, яку ви використали.

apiVersion: batch/v1
kind: Job
metadata:
  name: job-wq-1
spec:
  completions: 8
  parallelism: 2
  template:
    metadata:
      name: job-wq-1
    spec:
      containers:
      - name: c
        image: gcr.io/<project>/job-wq-1
        env:
        - name: BROKER_URL
          value: amqp://guest:guest@rabbitmq-service:5672
        - name: QUEUE
          value: job1
      restartPolicy: OnFailure

У цьому прикладі кожен Pod працює над одним елементом із черги, а потім завершує роботу. Отже, кількість завершень Job відповідає кількості виконаних робочих елементів. Тому в маніфесті прикладу .spec.completions дорівнює 8.

Запуск Job

Тепер запустіть Job:

# це передбачає, що ви вже завантажили та відредагували маніфест
kubectl apply -f ./job.yaml

Ви можете зачекати на успішне завершення Job із тайм-аутом:

# Перевірка назви умови не залежить від регістру
kubectl wait --for=condition=complete --timeout=300s job/job-wq-1

Далі перевірте Job:

kubectl describe jobs/job-wq-1
Name:             job-wq-1
Namespace:        default
Selector:         controller-uid=41d75705-92df-11e7-b85e-fa163ee3c11f
Labels:           controller-uid=41d75705-92df-11e7-b85e-fa163ee3c11f
                  job-name=job-wq-1
Annotations:      <none>
Parallelism:      2
Completions:      8
Start Time:       Wed, 06 Sep 2022 16:42:02 +0000
Pods Statuses:    0 Running / 8 Succeeded / 0 Failed
Pod Template:
  Labels:       controller-uid=41d75705-92df-11e7-b85e-fa163ee3c11f
                job-name=job-wq-1
  Containers:
   c:
    Image:      container-registry.example/causal-jigsaw-637/job-wq-1
    Port:
    Environment:
      BROKER_URL:       amqp://guest:guest@rabbitmq-service:5672
      QUEUE:            job1
    Mounts:             <none>
  Volumes:              <none>
Events:
  FirstSeen  LastSeen   Count    From    SubobjectPath    Type      Reason              Message
  ─────────  ────────   ─────    ────    ─────────────    ──────    ──────              ───────
  27s        27s        1        {job }                   Normal    SuccessfulCreate    Created pod: job-wq-1-hcobb
  27s        27s        1        {job }                   Normal    SuccessfulCreate    Created pod: job-wq-1-weytj
  27s        27s        1        {job }                   Normal    SuccessfulCreate    Created pod: job-wq-1-qaam5
  27s        27s        1        {job }                   Normal    SuccessfulCreate    Created pod: job-wq-1-b67sr
  26s        26s        1        {job }                   Normal    SuccessfulCreate    Created pod: job-wq-1-xe5hj
  15s        15s        1        {job }                   Normal    SuccessfulCreate    Created pod: job-wq-1-w2zqe
  14s        14s        1        {job }                   Normal    SuccessfulCreate    Created pod: job-wq-1-d6ppa
  14s        14s        1        {job }                   Normal    SuccessfulCreate    Created pod: job-wq-1-p17e0

Усі Podʼи цього Job завершилися успішно! Готово.

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

Перевага цього підходу в тому, що вам не потрібно змінювати свою програму-«робітника», щоб вона знала про робочу чергу. Ви можете додати незмінену робочу програму до образу контейнера.

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

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

У цьому прикладі ви використали утиліту amqp-consume, щоб читати повідомлення з черги й запускати фактичну програму. Перевага в тому, що програму не потрібно змінювати, щоб вона знала про чергу. У прикладі тонкої паралельної робочої черги показано, як взаємодіяти з робочою чергою за допомогою клієнтської бібліотеки.

Застереження

Якщо кількість завершень менша за кількість елементів у черзі, не всі елементи буде оброблено.

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

У цього шаблону є малоймовірний стан гонитви. Якщо контейнер буде завершено між моментом, коли команда amqp-consume підтвердила повідомлення, і моментом успішного завершення контейнера, або якщо вузол вийде з ладу, перш ніж kubelet зможе надіслати API-серверу повідомлення про успіх Podʼа, Job не вважатиметься завершеним, навіть якщо всі елементи в черзі оброблено.

3 - Indexed Job для паралельної обробки зі статичним призначенням роботи

СТАН ФУНКЦІОНАЛУ: GA since Kubernetes v1.24

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

Індекс Podʼа доступний в анотації batch.kubernetes.io/job-completion-index як рядок із його десятковим значенням. Щоб контейнеризований процес завдання отримав цей індекс, можна опублікувати значення анотації за допомогою механізму downward API. Для зручності панель управління автоматично налаштовує downward API так, щоб індекс був доступний у змінній середовища JOB_COMPLETION_INDEX.

Нижче наведено огляд кроків у цьому прикладі:

  1. Визначте маніфест Job з індексованим завершенням. Downward API дозволяє передати анотацію індексу Podʼа контейнеру як змінну середовища або файл.
  2. Запустіть Indexed Job на основі цього маніфесту.

Перш ніж ви розпочнете

Ви вже маєте знати базове, непаралельне використання Job.

Вам треба мати кластер Kubernetes, а також інструмент командного рядка kubectl має бути налаштований для роботи з вашим кластером. Рекомендується виконувати ці настанови у кластері, що має щонайменше два вузли, які не виконують роль вузлів управління. Якщо у вас немає кластера, ви можете створити його, за допомогою minikube або використовувати одну з цих пісочниць:

Версія вашого Kubernetes сервера має бути не старішою ніж v1.21.

Для перевірки версії введіть kubectl version.

Оберіть підхід

Щоб отримати доступ до робочого елемента з програми робочого процесу, є кілька варіантів:

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

У цьому прикладі уявіть, що ви обрали варіант 3 і хочете запустити утиліту rev. Ця програма приймає файл як аргумент і виводить його вміст у зворотному порядку.

rev data.txt

Ви використаєте інструмент rev з образу контейнера busybox.

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

Визначте Indexed Job

Ось приклад маніфесту Job, який використовує режим завершення Indexed:

apiVersion: batch/v1
kind: Job
metadata:
  name: 'indexed-job'
spec:
  completions: 5
  parallelism: 3
  completionMode: Indexed
  template:
    spec:
      restartPolicy: Never
      initContainers:
      - name: 'input'
        image: 'docker.io/library/bash'
        command:
        - "bash"
        - "-c"
        - |
          items=(foo bar baz qux xyz)
          echo ${items[$JOB_COMPLETION_INDEX]} > /input/data.txt
        volumeMounts:
        - mountPath: /input
          name: input
      containers:
      - name: 'worker'
        image: 'docker.io/library/busybox'
        command:
        - "rev"
        - "/input/data.txt"
        volumeMounts:
        - mountPath: /input
          name: input
      volumes:
      - name: input
        emptyDir: {}

У наведеному вище прикладі ви використовуєте вбудовану змінну середовища JOB_COMPLETION_INDEX, яку контролер Job встановлює для всіх контейнерів. Init-контейнер зіставляє індекс зі статичним значенням і записує його у файл, спільний із контейнером робочого процесу через том emptyDir. За бажання можна визначити власну змінну середовища через downward API, щоб публікувати індекс у контейнери. Також можна завантажити список значень із ConfigMap як змінну середовища або файл.

Або ви можете безпосередньо використати downward API для передачі значення анотації як файлу тому, як показано в наведеному нижче прикладі:

apiVersion: batch/v1
kind: Job
metadata:
  name: 'indexed-job'
spec:
  completions: 5
  parallelism: 3
  completionMode: Indexed
  template:
    spec:
      restartPolicy: Never
      containers:
      - name: 'worker'
        image: 'docker.io/library/busybox'
        command:
        - "rev"
        - "/input/data.txt"
        volumeMounts:
        - mountPath: /input
          name: input
      volumes:
      - name: input
        downwardAPI:
          items:
          - path: "data.txt"
            fieldRef:
              fieldPath: metadata.annotations['batch.kubernetes.io/job-completion-index']

Запуск Job

Тепер запустіть Job:

# Це використовує перший підхід (покладаючись на $JOB_COMPLETION_INDEX)
kubectl apply -f https://kubernetes.io/examples/application/job/indexed-job.yaml

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

Оскільки .spec.parallelism менше за .spec.completions, панель управління чекає завершення деяких перших Podʼів, перш ніж запускати нові.

Ви можете зачекати на успішне завершення Job із тайм-аутом:

# Перевірка назви умови не залежить від регістру
kubectl wait --for=condition=complete --timeout=300s job/indexed-job

Тепер перегляньте опис Job і переконайтеся, що він успішний.

kubectl describe jobs/indexed-job

Вивід подібний до такого:

Name:              indexed-job
Namespace:         default
Selector:          controller-uid=bf865e04-0b67-483b-9a90-74cfc4c3e756
Labels:            controller-uid=bf865e04-0b67-483b-9a90-74cfc4c3e756
                   job-name=indexed-job
Annotations:       <none>
Parallelism:       3
Completions:       5
Start Time:        Thu, 11 Mar 2021 15:47:34 +0000
Pods Statuses:     2 Running / 3 Succeeded / 0 Failed
Completed Indexes: 0-2
Pod Template:
  Labels:  controller-uid=bf865e04-0b67-483b-9a90-74cfc4c3e756
           job-name=indexed-job
  Init Containers:
   input:
    Image:      docker.io/library/bash
    Port:       <none>
    Host Port:  <none>
    Command:
      bash
      -c
      items=(foo bar baz qux xyz)
      echo ${items[$JOB_COMPLETION_INDEX]} > /input/data.txt

    Environment:  <none>
    Mounts:
      /input from input (rw)
  Containers:
   worker:
    Image:      docker.io/library/busybox
    Port:       <none>
    Host Port:  <none>
    Command:
      rev
      /input/data.txt
    Environment:  <none>
    Mounts:
      /input from input (rw)
  Volumes:
   input:
    Type:       EmptyDir (a temporary directory that shares a pod's lifetime)
    Medium:
    SizeLimit:  <unset>
Events:
  Type    Reason            Age   From            Message
  ----    ------            ----  ----            -------
  Normal  SuccessfulCreate  4s    job-controller  Created pod: indexed-job-njkjj
  Normal  SuccessfulCreate  4s    job-controller  Created pod: indexed-job-9kd4h
  Normal  SuccessfulCreate  4s    job-controller  Created pod: indexed-job-qjwsz
  Normal  SuccessfulCreate  1s    job-controller  Created pod: indexed-job-fdhq5
  Normal  SuccessfulCreate  1s    job-controller  Created pod: indexed-job-ncslj

У цьому прикладі ви запускаєте Job із власними значеннями для кожного індексу. Ви можете переглянути вивід одного з Podʼів:

kubectl logs indexed-job-fdhq5 # Змініть це на назву Podʼа з цього Job

Вивід подібний до такого:

xuq

4 - Job із взаємодією між Podʼами

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

Podʼам у межах Job може знадобитися взаємодіяти між собою. Робоче навантаження користувача в кожному Podʼі могло б запитувати API-сервер Kubernetes, щоб дізнатися IP-адреси інших Podʼів, але набагато простіше покладатися на вбудоване в Kubernetes розвʼязання DNS.

Jobs у режимі завершення Indexed автоматично задають імена хостів Podʼів у форматі ${jobName}-${completionIndex}. Цей формат дозволяє детерміновано будувати імена хостів Podʼів і забезпечувати взаємодію між Podʼами без потреби створювати клієнтське зʼєднання з панеллю управління Kubernetes для отримання імен хостів або IP-адрес Podʼів через запити API.

Ця конфігурація корисна для сценаріїв, де потрібна мережева взаємодія Podʼів, але ви не хочете залежати від мережевого зʼєднання з API-сервером Kubernetes.

Перш ніж ви розпочнете

Ви вже маєте знати основи використання Job.

Вам треба мати кластер Kubernetes, а також інструмент командного рядка kubectl має бути налаштований для роботи з вашим кластером. Рекомендується виконувати ці настанови у кластері, що має щонайменше два вузли, які не виконують роль вузлів управління. Якщо у вас немає кластера, ви можете створити його, за допомогою minikube або використовувати одну з цих пісочниць:

Версія вашого Kubernetes сервера має бути не старішою ніж v1.21.

Для перевірки версії введіть kubectl version.

Примітка:

Якщо ви використовуєте minikube або подібний інструмент, може знадобитися виконати додаткові кроки, щоб забезпечити наявність DNS.

Запуск Job із взаємодією між Podʼами

Щоб увімкнути взаємодію між Podʼами за іменами хостів Podʼів у Job, потрібно зробити таке:

  1. Налаштуйте headless Service із дійсним селектором міток для Podʼів, створених вашим Job. Headless Service має бути в тому самому просторі імен, що й Job. Простий спосіб зробити це — використати селектор job-name: <your-job-name>, оскільки Kubernetes автоматично додає мітку job-name. Ця конфігурація спонукає систему DNS створити записи імен хостів Podʼів, що виконують ваш Job.

  2. Налаштуйте headless Service як сервіс піддомену для Podʼів Job, додавши таке значення до специфікації шаблону Job:

    subdomain: <headless-svc-name>
    

Приклад

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

Примітка:

У скрипті Bash, який виконується в кожному Podʼі у наведеному нижче прикладі, імена хостів Podʼів можуть також мати префікс простору імен, якщо Pod має бути доступний ззовні простору імен.
apiVersion: v1
kind: Service
metadata:
  name: headless-svc
spec:
  clusterIP: None # clusterIP має бути None для створення headless Service
  selector:
    job-name: example-job # має відповідати назві Job
---
apiVersion: batch/v1
kind: Job
metadata:
  name: example-job
spec:
  completions: 3
  parallelism: 3
  completionMode: Indexed
  template:
    spec:
      subdomain: headless-svc # має відповідати назві Service
      restartPolicy: Never
      containers:
      - name: example-workload
        image: bash:latest
        command:
        - bash
        - -c
        - |
          for i in 0 1 2
          do
            gotStatus="-1"
            wantStatus="0"             
            while [ $gotStatus -ne $wantStatus ]
            do                                       
              ping -c 1 example-job-${i}.headless-svc > /dev/null 2>&1
              gotStatus=$?                
              if [ $gotStatus -ne $wantStatus ]; then
                echo "Failed to ping pod example-job-${i}.headless-svc, retrying in 1 second..."
                sleep 1
              fi
            done                                                         
            echo "Successfully pinged pod: example-job-${i}.headless-svc"
          done

Після застосування наведеного вище прикладу Podʼи зможуть звертатися один до одного через мережу за допомогою <pod-hostname>.<headless-service-name>. Ви маєте побачити вивід, подібний до такого:

kubectl logs example-job-0-qws42
Failed to ping pod example-job-0.headless-svc, retrying in 1 second...
Successfully pinged pod: example-job-0.headless-svc
Successfully pinged pod: example-job-1.headless-svc
Successfully pinged pod: example-job-2.headless-svc

Примітка:

Майте на увазі, що формат назви <pod-hostname>.<headless-service-name>, використаний у цьому прикладі, не працюватиме з політикою DNS None або Default. Дивіться Політику DNS Podʼа.

5 - Тонка паралельна обробка за допомогою робочої черги

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

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

Ось загальний огляд кроків у цьому прикладі:

  1. Запустіть сервіс зберігання для робочої черги. У цьому прикладі ви використаєте Redis для зберігання робочих елементів. У попередньому прикладі ви використовували RabbitMQ. У цьому прикладі ви використаєте Redis і власну клієнтську бібліотеку робочої черги, тому що AMQP не дає клієнтам зручного способу виявити, що робоча черга скінченної довжини порожня. На практиці ви один раз налаштуєте сховище на кшталт Redis і повторно використовуватимете його для робочих черг багатьох Jobs та для інших потреб.
  2. Створіть чергу й заповніть її повідомленнями. Кожне повідомлення представляє одне завдання, яке потрібно виконати. У цьому прикладі повідомлення — це ціле число, над яким ми виконаємо тривалі обчислення.
  3. Запустіть Job, який працює над завданнями з черги. Job запускає кілька Podʼів. Кожен Pod бере одне завдання з черги повідомлень, обробляє його й повторює це, доки не буде досягнуто кінця черги.

Перш ніж ви розпочнете

Вам треба мати кластер Kubernetes, а також інструмент командного рядка kubectl має бути налаштований для роботи з вашим кластером. Рекомендується виконувати ці настанови у кластері, що має щонайменше два вузли, які не виконують роль вузлів управління. Якщо у вас немає кластера, ви можете створити його, за допомогою minikube або використовувати одну з цих пісочниць:

Вам знадобиться реєстр образів контейнерів, куди можна завантажувати образи для запуску у вашому кластері. У прикладі використовується Docker Hub, але ви можете адаптувати його до іншого реєстру образів контейнерів.

Цей приклад завдання також передбачає, що Docker встановлено локально. Docker використовується для збирання образів контейнерів.

Ви маєте знати базове, непаралельне використання Job.

Запуск Redis

Для простоти в цьому прикладі ви запустите один екземпляр Redis. Приклад масштабованого й відмовостійкого розгортання Redis дивіться в прикладі Redis.

Ви також можете безпосередньо завантажити такі файли:

Щоб запустити один екземпляр Redis, потрібно створити Pod redis і Service redis:

kubectl apply -f https://k8s.io/examples/application/job/redis/redis-pod.yaml
kubectl apply -f https://k8s.io/examples/application/job/redis/redis-service.yaml

Заповнення черги завданнями

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

Запустіть тимчасовий інтерактивний Pod для запуску Redis CLI.

kubectl run -i --tty temp --image redis --command "/bin/sh"
Waiting for pod default/redis2-c7h78 to be running, status is Pending, pod ready: false
Hit enter for command prompt

Тепер натисніть Enter, запустіть Redis CLI і створіть список із кількома робочими елементами.

redis-cli -h redis
redis:6379> rpush job2 "apple"
(integer) 1
redis:6379> rpush job2 "banana"
(integer) 2
redis:6379> rpush job2 "cherry"
(integer) 3
redis:6379> rpush job2 "date"
(integer) 4
redis:6379> rpush job2 "fig"
(integer) 5
redis:6379> rpush job2 "grape"
(integer) 6
redis:6379> rpush job2 "lemon"
(integer) 7
redis:6379> rpush job2 "melon"
(integer) 8
redis:6379> rpush job2 "orange"
(integer) 9
redis:6379> lrange job2 0 -1
1) "apple"
2) "banana"
3) "cherry"
4) "date"
5) "fig"
6) "grape"
7) "lemon"
8) "melon"
9) "orange"

Отже, список із ключем job2 буде робочою чергою.

Примітка: якщо Kube DNS налаштовано неправильно, може знадобитися змінити перший крок у наведеному вище блоці на redis-cli -h $REDIS_SERVICE_HOST.

Створення образу контейнера

Тепер ви готові створити образ, який оброблятиме роботу в цій черзі.

Ви використаєте робочу програму на Python із клієнтом Redis для читання повідомлень із черги повідомлень.

Надається проста клієнтська бібліотека робочої черги Redis rediswq.py (Завантажити).

Програма-«робітник» у кожному Podʼі Job використовує клієнтську бібліотеку робочої черги, щоб отримувати роботу. Ось вона:

#!/usr/bin/env python

import time
import rediswq

host="redis"
# Uncomment next two lines if you do not have CoreDNS working.
# import os
# host = os.getenv("REDIS_SERVICE_HOST")

q = rediswq.RedisWQ(name="job2", host=host)
print("Worker with sessionID: " +  q.sessionID())
print("Initial queue state: empty=" + str(q.empty()))
while not q.empty():
  item = q.lease(lease_secs=10, block=True, timeout=2) 
  if item is not None:
    itemstr = item.decode("utf-8")
    print("Working on " + itemstr)
    time.sleep(10) # Put your actual work here instead of sleep.
    q.complete(item)
  else:
    print("Waiting for work")
print("Queue empty, exiting")

Ви також можете завантажити файли worker.py, rediswq.py і Dockerfile, а потім зібрати образ контейнера. Ось приклад збирання образу за допомогою Docker:

docker build -t job-wq-2 .

Надішліть образ у реєстр

Для Docker Hub додайте до образу застосунку теґ зі своїм іменем користувача й надішліть його в Hub наведеними нижче командами. Замініть <username> своїм іменем користувача Hub.

docker tag job-wq-2 <username>/job-wq-2
docker push <username>/job-wq-2

Потрібно надіслати образ у публічний репозиторій або налаштувати кластер для доступу до вашого приватного репозиторію.

Визначення Job

Ось маніфест для створення Job:

apiVersion: batch/v1
kind: Job
metadata:
  name: job-wq-2
spec:
  parallelism: 2
  template:
    metadata:
      name: job-wq-2
    spec:
      containers:
      - name: c
        image: gcr.io/myproject/job-wq-2
      restartPolicy: OnFailure

Примітка:

Не забудьте відредагувати маніфест, змінивши gcr.io/myproject на свій власний шлях.

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

Запуск Job

Отже, тепер запустіть Job:

# передбачається, що ви вже завантажили та відредагували маніфест
kubectl apply -f ./job.yaml

Тепер трохи зачекайте, а потім перевірте стан Job:

kubectl describe jobs/job-wq-2
Name:             job-wq-2
Namespace:        default
Selector:         controller-uid=b1c7e4e3-92e1-11e7-b85e-fa163ee3c11f
Labels:           controller-uid=b1c7e4e3-92e1-11e7-b85e-fa163ee3c11f
                  job-name=job-wq-2
Annotations:      <none>
Parallelism:      2
Completions:      <unset>
Start Time:       Mon, 11 Jan 2022 17:07:59 +0000
Pods Statuses:    1 Running / 0 Succeeded / 0 Failed
Pod Template:
  Labels:       controller-uid=b1c7e4e3-92e1-11e7-b85e-fa163ee3c11f
                job-name=job-wq-2
  Containers:
   c:
    Image:              container-registry.example/exampleproject/job-wq-2
    Port:
    Environment:        <none>
    Mounts:             <none>
  Volumes:              <none>
Events:
  FirstSeen    LastSeen    Count    From            SubobjectPath    Type        Reason            Message
  ---------    --------    -----    ----            -------------    --------    ------            -------
  33s          33s         1        {job-controller }                Normal      SuccessfulCreate  Created pod: job-wq-2-lglf8

Ви можете зачекати на успішне завершення Job із тайм-аутом:

# Перевірка назви умови не залежить від регістру
kubectl wait --for=condition=complete --timeout=300s job/job-wq-2
kubectl logs pods/job-wq-2-7r7b2
Worker with sessionID: bbd72d0a-9e5c-4dd6-abf6-416cc267991f
Initial queue state: empty=False
Working on banana
Working on date
Working on lemon

Як бачите, один із Podʼів цього Job працював над кількома одиницями роботи.

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

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

Якщо у вас є постійний потік фонової роботи, натомість розгляньте запуск фонових робочих процесів за допомогою ReplicaSet і використання бібліотеки фонової обробки, наприклад https://github.com/resque/resque.

6 - Паралельна обробка з розгортанням шаблону

Це завдання демонструє запуск кількох Jobs на основі спільного шаблону. Цей підхід можна використовувати для паралельної обробки пакетів роботи.

У цьому прикладі є лише три елементи: apple, banana та cherry. Приклади Jobs обробляють кожен елемент, виводячи рядок, а потім роблячи паузу.

Дивіться використання Jobs у реальних робочих навантаженнях, щоб дізнатися, як цей шаблон підходить для реалістичніших сценаріїв.

Перш ніж ви розпочнете

Ви маєте знати базове, непаралельне використання Job.

Вам треба мати кластер Kubernetes, а також інструмент командного рядка kubectl має бути налаштований для роботи з вашим кластером. Рекомендується виконувати ці настанови у кластері, що має щонайменше два вузли, які не виконують роль вузлів управління. Якщо у вас немає кластера, ви можете створити його, за допомогою minikube або використовувати одну з цих пісочниць:

Для базового шаблонування вам потрібна утиліта командного рядка sed.

Для виконання прикладу з розширеним шаблонуванням потрібен робочий Python і бібліотека шаблонів Jinja2 для Python.

Після налаштування Python ви можете встановити Jinja2, виконавши:

pip install --user jinja2

Створення Job на основі шаблону

Спочатку завантажте наведений нижче шаблон Job у файл job-tmpl.yaml. Ось що ви завантажите:

apiVersion: batch/v1
kind: Job
metadata:
  name: process-item-$ITEM
  labels:
    jobgroup: jobexample
spec:
  template:
    metadata:
      name: jobexample
      labels:
        jobgroup: jobexample
    spec:
      containers:
      - name: c
        image: busybox:1.28
        command: ["sh", "-c", "echo Processing item $ITEM && sleep 5"]
      restartPolicy: Never
# Використайте curl для завантаження job-tmpl.yaml
curl -L -s -O https://k8s.io/examples/application/job/job-tmpl.yaml

Завантажений файл ще не є чинним маніфестом Kubernetes. Натомість цей шаблон є поданням обʼєкта Job у YAML із заповнювачами, які потрібно заповнити перед використанням. Синтаксис $ITEM нічого не означає для Kubernetes.

Створення маніфестів з шаблону

Наведений нижче фрагмент оболонки використовує sed, щоб замінити рядок $ITEM змінною циклу, записуючи результат у тимчасову теку jobs. Виконайте його зараз:

# Розгорніть шаблон у кілька файлів, по одному для кожного елемента, який потрібно обробити.
mkdir ./jobs
for i in apple banana cherry
do
  cat job-tmpl.yaml | sed "s/\$ITEM/$i/" > ./jobs/job-$i.yaml
done

Перевірте, чи це спрацювало:

ls jobs/

Вивід подібний до такого:

job-apple.yaml
job-banana.yaml
job-cherry.yaml

Ви можете використовувати будь-яку мову шаблонів (наприклад, Jinja2 або ERB) або написати програму для генерації маніфестів Job.

Створення Jobs з маніфестів

Далі створіть усі Jobs однією командою kubectl:

kubectl create -f ./jobs

Вивід подібний до такого:

job.batch/process-item-apple created
job.batch/process-item-banana created
job.batch/process-item-cherry created

Тепер перевірте Jobs:

kubectl get jobs -l jobgroup=jobexample

Вивід подібний до такого:

NAME                  COMPLETIONS   DURATION   AGE
process-item-apple    1/1           14s        22s
process-item-banana   1/1           12s        21s
process-item-cherry   1/1           12s        20s

Параметр -l для kubectl вибирає лише Jobs, що належать до цієї групи Jobs (у системі можуть бути інші, не повʼязані Jobs).

Ви також можете перевірити стан Podʼів, використовуючи той самий селектор міток:

kubectl get pods -l jobgroup=jobexample

Вивід подібний до такого:

NAME                        READY     STATUS      RESTARTS   AGE
process-item-apple-kixwv    0/1       Completed   0          4m
process-item-banana-wrsf7   0/1       Completed   0          4m
process-item-cherry-dnfu9   0/1       Completed   0          4m

Цією однією командою можна переглянути вивід усіх Jobs одразу:

kubectl logs -f -l jobgroup=jobexample

Вивід має бути таким:

Processing item apple
Processing item banana
Processing item cherry

Очищення

# Видаліть створені вами Jobs
# Ваш кластер автоматично очищає їхні Podʼи
kubectl delete job -l jobgroup=jobexample

Використання розширених параметрів шаблону

У першому прикладі кожен екземпляр шаблону мав один параметр, і цей параметр також використовувався в назві Job. Однак назви можуть містити лише певні символи.

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

У цій частині завдання ви використаєте однорядковий скрипт Python, щоб перетворити шаблон на набір маніфестів.

Спочатку скопіюйте й вставте наведений нижче шаблон обʼєкта Job у файл job.yaml.jinja2:

{% set params = [{ "name": "apple", "url": "http://dbpedia.org/resource/Apple", },
                  { "name": "banana", "url": "http://dbpedia.org/resource/Banana", },
                  { "name": "cherry", "url": "http://dbpedia.org/resource/Cherry" }]
%}
{% for p in params %}
{% set name = p["name"] %}
{% set url = p["url"] %}
---
apiVersion: batch/v1
kind: Job
metadata:
  name: jobexample-{{ name }}
  labels:
    jobgroup: jobexample
spec:
  template:
    metadata:
      name: jobexample
      labels:
        jobgroup: jobexample
    spec:
      containers:
      - name: c
        image: busybox:1.28
        command: ["sh", "-c", "echo Processing URL {{ url }} && sleep 5"]
      restartPolicy: Never
{% endfor %}

Наведений вище шаблон визначає два параметри для кожного обʼєкта Job за допомогою списку словників Python (рядки 1-4). Цикл for генерує один маніфест Job для кожного набору параметрів (решта рядків).

Цей приклад спирається на можливість YAML. Один файл YAML може містити кілька документів (у цьому випадку маніфестів Kubernetes), розділених рядком ---. Ви можете передати вивід безпосередньо до kubectl, щоб створити Jobs.

Далі використайте цю однорядкову програму Python, щоб розгорнути шаблон:

alias render_template='python -c "from jinja2 import Template; import sys; print(Template(sys.stdin.read()).render());"'

Використайте render_template, щоб перетворити параметри й шаблон на один файл YAML із маніфестами Kubernetes:

# Це потребує визначеного раніше псевдоніма
cat job.yaml.jinja2 | render_template > jobs.yaml

Ви можете переглянути jobs.yaml, щоб переконатися, що скрипт render_template спрацював правильно.

Коли ви переконаєтеся, що render_template працює так, як задумано, можна передати його вивід до kubectl:

cat job.yaml.jinja2 | render_template | kubectl apply -f -

Kubernetes приймає та запускає створені вами Jobs.

Очищення

# Видаліть створені вами Jobs
# Ваш кластер автоматично очищає їхні Podʼи
kubectl delete job -l jobgroup=jobexample

Використання Jobs у реальних робочих навантаженнях

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

У цьому завданні ви виконали команду, щоб зібрати вивід Podʼів, отримавши їхні логи. У реальному сценарії кожен Pod Job записує свій вивід у надійне сховище перед завершенням. Можна використовувати PersistentVolume для кожного Job або зовнішній сервіс зберігання. Наприклад, якщо ви рендерите кадри фільму, надсилайте дані готового кадру за допомогою HTTP PUT на URL, використовуючи окрему URL-адресу для кожного кадру.

Мітки на Jobs і Podʼах

Після створення Job Kubernetes автоматично додає мітки, які відрізняють Podʼи одного Job від Podʼів іншого Job.

У цьому прикладі кожен Job та його шаблон Pod мають мітку: jobgroup=jobexample.

Сам Kubernetes не звертає уваги на мітки з назвою jobgroup. Мітка на всіх Jobs, створених із шаблону, дає змогу зручно працювати з усіма цими Jobs одразу. У першому прикладі ви використали шаблон для створення кількох Jobs. Шаблон гарантує, що кожен Pod також отримує ту саму мітку, тож ви можете перевірити всі Podʼи цих Jobs однією командою.

Примітка:

Ключ мітки jobgroup не є особливим чи зарезервованим. Ви можете обрати власну схему міток. За бажання можна використовувати рекомендовані мітки.

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

Якщо ви плануєте створити велику кількість обʼєктів Job, ви можете виявити, що:

  • Навіть із мітками керувати такою кількістю Jobs незручно.
  • Якщо ви створюєте багато Jobs одним пакетом, це може створити високе навантаження на панель управління Kubernetes. Крім того, API-сервер Kubernetes може обмежити швидкість ваших запитів, тимчасово відхиляючи їх зі статусом 429.
  • Ви обмежені квотою ресурсів на Jobs: API-сервер остаточно відхиляє деякі ваші запити, коли ви створюєте багато роботи одним пакетом.

Існують інші шаблони Job, які можна використовувати для обробки великих обсягів роботи без створення дуже великої кількості обʼєктів Job.

Ви також можете розглянути можливість написання власного контролера, щоб автоматично керувати обʼєктами Job.

7 - Обробка повторюваних і неповторюваних збоїв Podʼів за допомогою політики збоїв Podʼа

This is a stable feature in Kubernetes, and has been since the 1.31 release. You can no longer toggle this feature (the associated feature gate has been removed).

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

Визначення політики збоїв Podʼа може допомогти:

Перш ніж ви розпочнете

Ви вже маєте знати основи використання Job.

Вам треба мати кластер Kubernetes, а також інструмент командного рядка kubectl має бути налаштований для роботи з вашим кластером. Рекомендується виконувати ці настанови у кластері, що має щонайменше два вузли, які не виконують роль вузлів управління. Якщо у вас немає кластера, ви можете створити його, за допомогою minikube або використовувати одну з цих пісочниць:

Версія вашого Kubernetes сервера має бути не старішою ніж v1.25.

Для перевірки версії введіть kubectl version.

Сценарії використання

Розгляньмо такі сценарії використання для Jobs, які визначають політику збоїв Podʼа:

Використання політики збоїв Podʼа для уникнення непотрібних повторних спроб запуску Podʼів

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

  1. Перегляньте наведений нижче маніфест:

    apiVersion: batch/v1
    kind: Job
    metadata:
      name: job-pod-failure-policy-failjob
    spec:
      completions: 8
      parallelism: 2
      template:
        spec:
          restartPolicy: Never
          containers:
          - name: main
            image: docker.io/library/bash:5
            command: ["bash"]
            args:
            - -c
            - echo "Hello world! I'm going to exit with 42 to simulate a software bug." && sleep 30 && exit 42
      backoffLimit: 6
      podFailurePolicy:
        rules:
        - action: FailJob
          onExitCodes:
            containerName: main
            operator: In
            values: [42]
    
  2. Застосуйте маніфест:

    kubectl create -f https://k8s.io/examples/controllers/job-pod-failure-policy-failjob.yaml
    
  3. Приблизно через 30 секунд весь Job має завершитися. Перевірте статус Job, виконавши команду:

    kubectl get jobs -l job-name=job-pod-failure-policy-failjob -o yaml
    

    У статусі Job відображаються такі стани:

    • Стан FailureTarget: має поле reason зі значенням PodFailurePolicy і поле message з додатковою інформацією про завершення, наприклад Container main for pod default/job-pod-failure-policy-failjob-8ckj8 failed with exit code 42 matching FailJob rule at index 0. Контролер Job додає цей стан, щойно Job вважається невдалим. Докладніше дивіться Завершення Podʼів Job.
    • Стан Failed: ті самі reason і message, що й у стану FailureTarget. Контролер Job додає цей стан після завершення всіх Podʼів Job.

    Для порівняння: якби політику збоїв Podʼа було вимкнено, Job повторював би спроби до досягнення backoffLimit (6 збоїв). Оскільки повторні спроби використовують експоненційний backoff, а з parallelism: 2 збої відбуваються парами, затримка між спробами зростає з кожною спробою. У результаті в цьому прикладі мало б минути щонайменше 9 хвилин, перш ніж Job зазнає збою.

Очищення

Видаліть створений Job:

kubectl delete jobs/job-pod-failure-policy-failjob

Кластер автоматично очищає Podʼи.

Використання політики збоїв Podʼа для ігнорування порушень роботи Podʼів

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

Увага:

У цьому прикладі важливий час, тому варто прочитати кроки перед виконанням. Щоб спричинити порушення роботи Podʼа, важливо виконати drain вузла, поки Pod працює на ньому (протягом 90 секунд після планування Podʼа).
  1. Перегляньте наведений нижче маніфест:

    apiVersion: batch/v1
    kind: Job
    metadata:
      name: job-pod-failure-policy-ignore
    spec:
      completions: 4
      parallelism: 2
      template:
        spec:
          restartPolicy: Never
          containers:
          - name: main
            image: docker.io/library/bash:5
            command: ["bash"]
            args:
            - -c
            - echo "Hello world! I'm going to exit with 0 (success)." && sleep 90 && exit 0
      backoffLimit: 0
      podFailurePolicy:
        rules:
        - action: Ignore
          onPodConditions:
          - type: DisruptionTarget
    
  2. Застосуйте маніфест:

    kubectl create -f https://k8s.io/examples/controllers/job-pod-failure-policy-ignore.yaml
    
  3. Виконайте цю команду, щоб перевірити nodeName, на який заплановано Pod:

    nodeName=$(kubectl get pods -l job-name=job-pod-failure-policy-ignore -o jsonpath='{.items[0].spec.nodeName}')
    
  4. Виконайте drain вузла, щоб виселити Pod до його завершення (протягом 90 секунд):

    kubectl drain nodes/$nodeName --ignore-daemonsets --grace-period=0
    
  5. Перевірте .status.failed, щоб переконатися, що лічильник для Job не збільшено:

    kubectl get jobs -l job-name=job-pod-failure-policy-ignore -o yaml
    
  6. Виконайте uncordon вузла:

    kubectl uncordon nodes/$nodeName
    

Job відновлюється й завершується успішно.

Для порівняння: якби політику збоїв Podʼа було вимкнено, порушення роботи Podʼа призвело б до завершення всього Job (оскільки .spec.backoffLimit дорівнює 0).

Очищення

Видаліть створений Job:

kubectl delete jobs/job-pod-failure-policy-ignore

Кластер автоматично очищає Podʼи.

Використання політики збоїв Podʼа для уникнення непотрібних повторних спроб запуску Podʼів на основі власних станів Podʼа

У наведеному нижче прикладі ви дізнаєтеся, як використовувати політику збоїв Podʼа, щоб уникати непотрібних перезапусків Podʼів на основі власних станів Podʼа.

Примітка:

Наведений нижче приклад працює з версії 1.27, оскільки спирається на перехід видалених Podʼів у фазі Pending до термінальної фази (дивіться Фаза Podʼа).
  1. Перегляньте наведений нижче маніфест:

    apiVersion: batch/v1
    kind: Job
    metadata:
      name: job-pod-failure-policy-config-issue
    spec:
      completions: 8
      parallelism: 2
      template:
        spec:
          restartPolicy: Never
          containers:
          - name: main
            image: "non-existing-repo/non-existing-image:example"
      backoffLimit: 6
      podFailurePolicy:
        rules:
        - action: FailJob
          onPodConditions:
          - type: ConfigIssue
    
  2. Застосуйте маніфест:

    kubectl create -f https://k8s.io/examples/controllers/job-pod-failure-policy-config-issue.yaml
    

    Зверніть увагу, що образ налаштовано неправильно, оскільки його не існує.

  3. Перевірте статус Podʼів Job, виконавши команду:

    kubectl get pods -l job-name=job-pod-failure-policy-config-issue -o yaml
    

    Ви побачите вивід, подібний до такого:

    containerStatuses:
    - image: non-existing-repo/non-existing-image:example
       ...
       state:
       waiting:
          message: Back-off pulling image "non-existing-repo/non-existing-image:example"
          reason: ImagePullBackOff
          ...
    phase: Pending
    

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

  4. Додайте власний стан. Спочатку підготуйте патч, виконавши команду:

    cat <<EOF > patch.yaml
    status:
      conditions:
      - type: ConfigIssue
        status: "True"
        reason: "NonExistingImage"
        lastTransitionTime: "$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
    EOF
    

    По-друге, виберіть один із Podʼів, створених Job, виконавши команду:

    podName=$(kubectl get pods -l job-name=job-pod-failure-policy-config-issue -o jsonpath='{.items[0].metadata.name}')
    

    Потім застосуйте патч до одного з Podʼів, виконавши таку команду:

    kubectl patch pod $podName --subresource=status --patch-file=patch.yaml
    

    Якщо патч успішно застосовано, ви отримаєте повідомлення такого типу:

    pod/job-pod-failure-policy-config-issue-k6pvp patched
    
  5. Видаліть Pod, щоб перевести його у фазу Failed, виконавши команду:

    kubectl delete pods/$podName
    
  6. Перевірте статус Job, виконавши:

    kubectl get jobs -l job-name=job-pod-failure-policy-config-issue -o yaml
    

    У статусі Job ви побачите стан Failed з полем reason, що дорівнює PodFailurePolicy. Крім того, поле message містить докладнішу інформацію про завершення Job, наприклад Pod default/job-pod-failure-policy-config-issue-k6pvp has condition ConfigIssue matching FailJob rule at index 0.

Примітка:

У промисловому середовищі кроки 3 та 4 має автоматизувати контролер, наданий користувачем.

Очищення

Видаліть створений Job:

kubectl delete jobs/job-pod-failure-policy-config-issue

Кластер автоматично очищає Podʼи.

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

Щоб уникнути непотрібних перезапусків Podʼів для кожного індексу, можна використовувати можливості політика збоїв Podʼа й обмеження backoff на індекс. У цьому розділі сторінки показано, як використовувати ці можливості разом.

  1. Перегляньте наведений нижче маніфест:

    apiVersion: batch/v1
    kind: Job
    metadata:
      name: job-backoff-limit-per-index-failindex
    spec:
      completions: 4
      parallelism: 2
      completionMode: Indexed
      backoffLimitPerIndex: 1
      template:
        spec:
          restartPolicy: Never
          containers:
          - name: main
            image: docker.io/library/python:3
            command:
              # The script:
              # - fails the Pod with index 0 with exit code 1, which results in one retry;
              # - fails the Pod with index 1 with exit code 42 which results
              #   in failing the index without retry.
              # - succeeds Pods with any other index.
              - python3
              - -c
              - |
                import os, sys
                index = int(os.environ.get("JOB_COMPLETION_INDEX"))
                if index == 0:
                  sys.exit(1)
                elif index == 1:
                  sys.exit(42)
                else:
                  sys.exit(0)
      backoffLimit: 6
      podFailurePolicy:
        rules:
        - action: FailIndex
          onExitCodes:
            containerName: main
            operator: In
            values: [42]
    
  2. Застосуйте маніфест:

    kubectl create -f https://k8s.io/examples/controllers/job-backoff-limit-per-index-failindex.yaml
    
  3. Приблизно через 15 секунд перевірте стан Podʼів для Job. Ви можете зробити це, виконавши команду:

    kubectl get pods -l job-name=job-backoff-limit-per-index-failindex -o yaml
    

    Ви побачите вивід, подібний до такого:

    NAME                                            READY   STATUS      RESTARTS   AGE
    job-backoff-limit-per-index-failindex-0-4g4cm   0/1     Error       0          4s
    job-backoff-limit-per-index-failindex-0-fkdzq   0/1     Error       0          15s
    job-backoff-limit-per-index-failindex-1-2bgdj   0/1     Error       0          15s
    job-backoff-limit-per-index-failindex-2-vs6lt   0/1     Completed   0          11s
    job-backoff-limit-per-index-failindex-3-s7s47   0/1     Completed   0          6s
    

    Зверніть увагу, що вивід показує таке:

    • Два Podʼи мають індекс 0, оскільки обмеження backoff дозволило одну повторну спробу для індексу.
    • Лише один Pod має індекс 1, оскільки код виходу збійного Podʼа збігся з політикою збоїв Podʼа з дією FailIndex.
  4. Перевірте статус Job, виконавши:

    kubectl get jobs -l job-name=job-backoff-limit-per-index-failindex -o yaml
    

    У статусі Job поле failedIndexes показує "0,1", оскільки обидва індекси зазнали збою. Оскільки індекс 1 не повторювався, кількість збійних Podʼів, вказана в полі статусу "failed", дорівнює 3.

Очищення

Видаліть створений Job:

kubectl delete jobs/job-backoff-limit-per-index-failindex

Кластер автоматично очищає Podʼи.

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

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