# Тонка паралельна обробка за допомогою черги роботи

LLMS index: [llms.txt](/llms.txt)

---

<!-- overview -->

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

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

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

1. **Запустіть службу зберігання, щоб зберігати чергу завдань.** У цьому прикладі ви використаєте Redis для зберігання робочих елементів. У [попередньому прикладі](/docs/tasks/job/coarse-parallel-processing-work-queue), ви використали RabbitMQ. У цьому прикладі ви будете використовувати Redis та власну бібліотеку клієнтів черг завдань; це тому, що AMQP не надає зручний спосіб клієнтам виявити, коли скінчиться черга робочих елементів з обмеженою довжиною. На практиці ви налаштуєте сховище, таке як Redis, один раз і повторно використовуватимете його для черг робочих завдань багатьох завдань та іншого.
2. **Створіть чергу та заповніть її повідомленнями.** Кожне повідомлення представляє одне завдання, яке потрібно виконати. У цьому прикладі повідомленням є ціле число, над яким ми виконаємо тривалі обчислення.
3. **Запустіть завдання, яке працює над завданнями з черги**. Завдання запускає декілька Podʼів.  Кожний Pod бере одне завдання з черги повідомлень, обробляє його та повторює цей процес до досягнення кінця черги.

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

<p>Вам треба мати кластер Kubernetes, а також інструмент командного рядка kubectl має бути налаштований для роботи з вашим кластером. Рекомендується виконувати ці настанови у кластері, що має щонайменше два вузли, які не виконують роль вузлів управління. Якщо у вас немає кластера, ви можете створити його, за допомогою <a href="https://minikube.sigs.k8s.io/docs/tutorials/multi_node/">minikube</a> або використовувати одну з цих пісочниць:</p>
<ul>
<li><a href="https://labs.iximiuz.com/playgrounds?category=kubernetes&filter=all">iximiuz Labs</a></li>
<li><a href="https://killercoda.com/playgrounds/scenario/kubernetes">Killercoda</a></li>
<li><a href="https://kodekloud.com/public-playgrounds">KodeKloud</a></li>
</ul>


Вам знадобиться реєстр контейнерних образів, де ви можете завантажувати образи для запуску у вашому кластері. У прикладі використовується [Docker Hub](https://hub.docker.com/), але ви можете адаптувати його до іншого реєстру контейнерних образів.

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

<!-- steps -->

Ви маєте бути знайомі з базовим, не-паралельним використанням [Job](/docs/concepts/workloads/controllers/job/).

<!-- steps -->

## Запуск Redis {#starting-redis}

У цьому прикладі, для спрощення, ви запустите один екземпляр Redis. Дивіться [Приклад Redis](https://github.com/kubernetes/examples/tree/master/web/guestbook/) для прикладу розгортання Redis масштабовано та надійно.

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

- [`redis-pod.yaml`](/examples/application/job/redis/redis-pod.yaml)
- [`redis-service.yaml`](/examples/application/job/redis/redis-service.yaml)
- [`Dockerfile`](/examples/application/job/redis/Dockerfile)
- [`job.yaml`](/examples/application/job/redis/job.yaml)
- [`rediswq.py`](/examples/application/job/redis/rediswq.py)
- [`worker.py`](/examples/application/job/redis/worker.py)

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

```shell
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
```

## Заповнення черги завданнями {#filling-the-queue-with-tasks}

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

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

```shell
kubectl run -i --tty temp --image redis --command "/bin/sh"
```

```none
Waiting for pod default/redis2-c7h78 to be running, status is Pending, pod ready: false
Hit enter for command prompt
```

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

```shell
redis-cli -h redis
```

```console
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`.

## Створення образу контейнера {#create-an-image}

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

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

Надається проста бібліотека клієнтів черги роботи Redis, яка називається `rediswq.py` ([Завантажити](/examples/application/job/redis/rediswq.py)).

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


















<div class="highlight code-sample">
    <div class="copy-code-icon">
    <a href="https://raw.githubusercontent.com/kubernetes/website/main/content/uk/examples/application/job/redis/worker.py" download="application/job/redis/worker.py"><code>application/job/redis/worker.py</code>
    </a><img src="/images/copycode.svg" class="icon-copycode" onclick="copyCode('application-job-redis-worker-py')" title="Копіювати application/job/redis/worker.py до буферу обміну"></img></div>
    <div class="includecode" id="application-job-redis-worker-py"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="ch">#!/usr/bin/env python</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">time</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">rediswq</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">host</span><span class="o">=</span><span class="s2">&#34;redis&#34;</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Якщо у вас немає працюючого Kube-DNS, розкоментуйте наступні два рядки.</span>
</span></span><span class="line"><span class="cl"><span class="c1"># import os</span>
</span></span><span class="line"><span class="cl"><span class="c1"># host = os.getenv(&#34;REDIS_SERVICE_HOST&#34;)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">q</span> <span class="o">=</span> <span class="n">rediswq</span><span class="o">.</span><span class="n">RedisWQ</span><span class="p">(</span><span class="n">name</span><span class="o">=</span><span class="s2">&#34;job2&#34;</span><span class="p">,</span> <span class="n">host</span><span class="o">=</span><span class="n">host</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="s2">&#34;Worker with sessionID: &#34;</span> <span class="o">+</span>  <span class="n">q</span><span class="o">.</span><span class="n">sessionID</span><span class="p">())</span>
</span></span><span class="line"><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="s2">&#34;Initial queue state: empty=&#34;</span> <span class="o">+</span> <span class="nb">str</span><span class="p">(</span><span class="n">q</span><span class="o">.</span><span class="n">empty</span><span class="p">()))</span>
</span></span><span class="line"><span class="cl"><span class="k">while</span> <span class="ow">not</span> <span class="n">q</span><span class="o">.</span><span class="n">empty</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">  <span class="n">item</span> <span class="o">=</span> <span class="n">q</span><span class="o">.</span><span class="n">lease</span><span class="p">(</span><span class="n">lease_secs</span><span class="o">=</span><span class="mi">10</span><span class="p">,</span> <span class="n">block</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span> <span class="n">timeout</span><span class="o">=</span><span class="mi">2</span><span class="p">)</span> 
</span></span><span class="line"><span class="cl">  <span class="k">if</span> <span class="n">item</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="n">itemstr</span> <span class="o">=</span> <span class="n">item</span><span class="o">.</span><span class="n">decode</span><span class="p">(</span><span class="s2">&#34;utf-8&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;Working on &#34;</span> <span class="o">+</span> <span class="n">itemstr</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">time</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="mi">10</span><span class="p">)</span> <span class="c1"># Put your actual work here instead of sleep.</span>
</span></span><span class="line"><span class="cl">    <span class="n">q</span><span class="o">.</span><span class="n">complete</span><span class="p">(</span><span class="n">item</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;Waiting for work&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="s2">&#34;Queue empty, exiting&#34;</span><span class="p">)</span>
</span></span></code></pre></div></div>
</div>

Ви також можете завантажити файли [`worker.py`](/examples/application/job/redis/worker.py), [`rediswq.py`](/examples/application/job/redis/rediswq.py) та [`Dockerfile`](/examples/application/job/redis/Dockerfile), а потім побудувати контейнерний образ. Ось приклад використання Docker для побудови образу:

```shell
docker build -t job-wq-2 .
```

### Збереження образу в реєстрі {#push-the-image}

Для [Docker Hub](https://hub.docker.com/), позначте свій образ програми імʼям користувача та завантажте його до Hub за допомогою наступних команд. Замість `<username>` вкажіть своє імʼя користувача Hub.

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

Вам потрібно завантажити в публічний репозиторій або [налаштувати кластер для доступу до вашого приватного репозиторію](/docs/concepts/containers/images/).

## Визначення завдання {#defining-a-job}

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


















<div class="highlight code-sample">
    <div class="copy-code-icon">
    <a href="https://raw.githubusercontent.com/kubernetes/website/main/content/uk/examples/application/job/redis/job.yaml" download="application/job/redis/job.yaml"><code>application/job/redis/job.yaml</code>
    </a><img src="/images/copycode.svg" class="icon-copycode" onclick="copyCode('application-job-redis-job-yaml')" title="Копіювати application/job/redis/job.yaml до буферу обміну"></img></div>
    <div class="includecode" id="application-job-redis-job-yaml"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">batch/v1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">Job</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">job-wq-2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">spec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">parallelism</span><span class="p">:</span><span class="w"> </span><span class="m">2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">template</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">job-wq-2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">spec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">containers</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">c</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">gcr.io/myproject/job-wq-2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">restartPolicy</span><span class="p">:</span><span class="w"> </span><span class="l">OnFailure</span><span class="w">
</span></span></span></code></pre></div></div>
</div>


<div class="alert alert-info" role="note"><h4 class="alert-heading">Примітка:</h4>Не забудьте відредагувати маніфест, змінивши <code>gcr.io/myproject</code> на свій власний шлях.</div>


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

## Запуск завдання {#running-the-job}

Отже, зараз запустіть завдання:

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

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

```shell
kubectl describe jobs/job-wq-2
```

```none
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
```

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

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

```shell
kubectl logs pods/job-wq-2-7r7b2
```

```none
Worker with sessionID: bbd72d0a-9e5c-4dd6-abf6-416cc267991f
Initial queue state: empty=False
Working on banana
Working on date
Working on lemon
```

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

<!-- discussion -->

## Альтернативи {#alternatives}

Якщо запуск служби черги або модифікація ваших контейнерів для використання робочої черги є незручними, ви можете розглянути один з інших [шаблонів завдань](/docs/concepts/workloads/controllers/job/#job-patterns).

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