# 정적 작업 할당을 통한 병렬 처리를 위한 색인된 잡

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

---

<div class="feature-state-notice feature-stable">
      <span class="feature-state-name">기능 상태:</span>
      <code>Kubernetes v1.24 [stable]</code>
    </div>
  



<!-- overview -->


이 예제에서는, 여러 병렬 워커(worker) 프로세스를 활용해 쿠버네티스 잡(Job)을
실행한다.
각 워커는 각 파드에서 실행되는 서로 다른 컨테이너다. 파드에는
컨트롤 플레인이 자동으로 설정한 _인덱스 번호_ 가 부여되며, 이를 통해 각 파드는
전체 태스크 중 어느 부분을 수행해야 할 지 식별할 수 있다.

파드 인덱스는 10진수 값을 문자열로 표현한 <a class='glossary-tooltip' title='임의의 식별되지 않는 메타데이터를 오브젝트에 첨부할 때 이용하는 키-밸류 쌍.' data-bs-toggle='tooltip' data-bs-placement='top' href='/ko/docs/concepts/overview/working-with-objects/annotations' target='_blank' aria-label='어노테이션'>어노테이션</a>
`batch.kubernetes.io/job-completion-index` 를 통해
사용할 수 있다. 컨테이너화된 태스크 프로세스가 이 인덱스 정보를 가져갈 수 있도록,
[다운워드(downward) API](/ko/docs/concepts/workloads/pods/downward-api/)
메커니즘을 사용하여 어노테이션의 값을 발행할 수 있다.
편의상, 컨트롤 플레인은 downward API를 자동 설정하여
`JOB_COMPLETION_INDEX` 환경변수에 인덱스를 노출할 수 있도록 한다.

이 예제에서의 단계에 대한 개요는 다음과 같다.

1. **색인된 완료(indexed completion)를 사용하는 잡 매니페스트를 정의한다**.
   downward API를 통해 파드 인덱스 어노테이션을
   환경변수 또는 파일의 형태로 컨테이너에 전달할 수 있다.
2. **해당 매니페스트를 바탕으로 색인된(`Indexed`) 잡을 시작한다**.

## 시작하기 전에

기본적이고, 병렬 작업이 아닌,
[잡](/ko/docs/concepts/workloads/controllers/job/)의 사용법에 대해 잘 알고 있어야 한다.

<p>쿠버네티스 클러스터가 필요하고, kubectl 커맨드-라인 툴이 클러스터와
통신할 수 있도록 설정되어 있어야 한다. 이 튜토리얼은 컨트롤 플레인 호스트가 아닌 노드가 적어도 2개 포함된 클러스터에서 실행하는 것을 추천한다. 만약, 아직 클러스터를 가지고
있지 않다면,
<a href="/ko/docs/tasks/tools/#minikube">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>
 
 
 쿠버네티스 서버의 버전은 다음과 같거나 더 높아야 함. 버전: v1.21.
  <p>버전 확인을 위해서, 다음 커맨드를 실행  <code>kubectl version</code>.</p>


<!-- steps -->

## 접근 방법 선택하기

워커 프로그램에서 작업 항목에 접근하기 위한 몇 가지 선택지가 있다.

1. `JOB_COMPLETION_INDEX` 환경변수를 읽는다. 잡
   <a class='glossary-tooltip' title='API 서버를 통해 클러스터의 공유된 상태를 감시하고, 현재 상태를 원하는 상태로 이행시키는 컨트롤 루프.' data-bs-toggle='tooltip' data-bs-placement='top' href='/ko/docs/concepts/architecture/controller/' target='_blank' aria-label='컨트롤러'>컨트롤러</a> 는
   이 환경변수를 완료 인덱스 정보를 갖고 있는 어노테이션에 자동
   연결한다.
2. 완료 인덱스 정보를 갖고 있는 파일을 읽는다.
3. 프로그램을 수정할 수 없다면,
   위 방법을 통해 인덱스를 읽고 프로그램의
   입력값으로 사용 가능한 형태로 변환하는 스크립트로 감싸준다.

예를 들어, 3번째 방법을 선택했으며 
[rev](https://man7.org/linux/man-pages/man1/rev.1.html) 유틸리티를 실행한다고 가정하면, 이
프로그램은 파일을 인자로 받아 그 내용을 거꾸로 출력한다.

```shell
rev data.txt
```

`rev` 툴은
[`busybox`](https://hub.docker.com/_/busybox) 컨테이너 이미지 내에서 실행할 것이다.

예제에 불과하므로, 각 파드는 아주 작은 작업(짧은 문자열 거꾸로 뒤집기)만을
수행한다. 실제 워크로드에서는 예를
들어
장면 정보를 바탕으로 60초 길이의 영상을 제작하는 태스크에 해당하는 잡을 생성할 수도 있다.
영상 렌더링 잡의 각 작업 항목은 영상 클립의 특정
프레임을 렌더링하는 것이다. 색인된 완료는 잡에 포함된 각 파드가
클립의 시작 지점부터 프레임 수를 세어 어느 프레임을 렌더링하고 발행해야 할 지
알고 있음을 의미한다.

## 색인된 잡 정의하기

다음은 색인된(`Indexed`) 완료 모드를 사용하는 잡 매니페스트의 예이다.

















<div class="highlight code-sample">
    <div class="copy-code-icon">
    <a href="https://raw.githubusercontent.com/kubernetes/website/main/content/ko/examples/application/job/indexed-job.yaml" download="application/job/indexed-job.yaml"><code>application/job/indexed-job.yaml</code>
    </a><img src="/images/copycode.svg" class="icon-copycode" onclick="copyCode('application-job-indexed-job-yaml')" title="Copy application/job/indexed-job.yaml to clipboard"></img></div>
    <div class="includecode" id="application-job-indexed-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="s1">&#39;indexed-job&#39;</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">completions</span><span class="p">:</span><span class="w"> </span><span class="m">5</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">3</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">completionMode</span><span class="p">:</span><span class="w"> </span><span class="l">Indexed</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">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">restartPolicy</span><span class="p">:</span><span class="w"> </span><span class="l">Never</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">initContainers</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="s1">&#39;input&#39;</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="s1">&#39;docker.io/library/bash&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">command</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="s2">&#34;bash&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="s2">&#34;-c&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="p">|</span><span class="sd">
</span></span></span><span class="line"><span class="cl"><span class="sd">          items=(foo bar baz qux xyz)
</span></span></span><span class="line"><span class="cl"><span class="sd">          echo ${items[$JOB_COMPLETION_INDEX]} &gt; /input/data.txt</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">volumeMounts</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="nt">mountPath</span><span class="p">:</span><span class="w"> </span><span class="l">/input</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">input</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="s1">&#39;worker&#39;</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="s1">&#39;docker.io/library/busybox&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">command</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="s2">&#34;rev&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="s2">&#34;/input/data.txt&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">volumeMounts</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="nt">mountPath</span><span class="p">:</span><span class="w"> </span><span class="l">/input</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">input</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">volumes</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">input</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">emptyDir</span><span class="p">:</span><span class="w"> </span>{}<span class="w">
</span></span></span></code></pre></div></div>
</div>

위 예제에서, 잡 컨트롤러가 모든 컨테이너에 설정한 `JOB_COMPLETION_INDEX`
내장 환경 변수를 사용한다. [초기화 컨테이너](/ko/docs/concepts/workloads/pods/init-containers/)는
인덱스를 정적 값으로 매핑하고 [emptyDir 볼륨](/ko/docs/concepts/storage/volumes/#emptydir)을 통해
워커를 실행중인 컨테이너와 공유하는 파일에 쓴다.
선택적으로 컨테이너에 인덱스를 발행하기 위해 [downward API를 통해
직접 환경 변수를 정의](/ko/docs/tasks/inject-data-application/environment-variable-expose-pod-information/)할
수 있다. 또한 [환경변수 또는 파일로 된 컨피그맵(ConfigMap)](/docs/tasks/configure-pod-container/configure-pod-configmap/)으로부터
값 목록을 불러올 수도 있다.

혹은 다음 예제와 같이 직접 [downward API를 사용하여 어노테이션의 값을 볼륨 파일의 형태로 전달](
/ko/docs/tasks/inject-data-application/downward-api-volume-expose-pod-information/#파드-필드-저장)할
수도 있다.

















<div class="highlight code-sample">
    <div class="copy-code-icon">
    <a href="https://raw.githubusercontent.com/kubernetes/website/main/content/ko/examples/application/job/indexed-job-vol.yaml" download="application/job/indexed-job-vol.yaml"><code>application/job/indexed-job-vol.yaml</code>
    </a><img src="/images/copycode.svg" class="icon-copycode" onclick="copyCode('application-job-indexed-job-vol-yaml')" title="Copy application/job/indexed-job-vol.yaml to clipboard"></img></div>
    <div class="includecode" id="application-job-indexed-job-vol-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="s1">&#39;indexed-job&#39;</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">completions</span><span class="p">:</span><span class="w"> </span><span class="m">5</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">3</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">completionMode</span><span class="p">:</span><span class="w"> </span><span class="l">Indexed</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">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">restartPolicy</span><span class="p">:</span><span class="w"> </span><span class="l">Never</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="s1">&#39;worker&#39;</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="s1">&#39;docker.io/library/busybox&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">command</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="s2">&#34;rev&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="s2">&#34;/input/data.txt&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">volumeMounts</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="nt">mountPath</span><span class="p">:</span><span class="w"> </span><span class="l">/input</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">input</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">volumes</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">input</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">downwardAPI</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">items</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span>- <span class="nt">path</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;data.txt&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">fieldRef</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">fieldPath</span><span class="p">:</span><span class="w"> </span><span class="l">metadata.annotations[&#39;batch.kubernetes.io/job-completion-index&#39;]</span></span></span></code></pre></div></div>
</div>

## 잡 실행하기

이제 잡을 실행하자.

```shell
# 첫 번째 접근 방법을 사용한다. ($JOB_COMPLETION_INDEX 에 의존)
kubectl apply -f https://kubernetes.io/examples/application/job/indexed-job.yaml
```

이 잡을 생성할 때, 컨트롤 플레인은 명시한 각 인덱스당 하나씩 일련의 파드를 생성한다. `.spec.parallelism`의 값은 한 번에 실행 가능한 수를 결정하는 반면 `.spec.completions`는 잡에서 총 생성되는 파드의 수를 결정한다.

`.spec.parallelism`가 `.spec.completions`보다 작기 때문에, 컨트롤 플레인은 추가로 파드를 시작하기 전 최초 생성된 파드 중 일부가 완료되기를 기다린다.

아래와 같이 시간 제한(timeout)을 설정하고, 잡이 성공할 때까지 기다린다.
```shell
# 조건명은 대소문자를 구분하지 않는다.
kubectl wait --for=condition=complete --timeout=300s job/indexed-job
```

잡의 상세 설명을 출력하여 성공적으로 수행되었는지 확인한다.


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

이 예제에서는 각 인덱스에 직접 설정한 값을 갖는 잡을 실행한다. 파드
중 하나의 출력을 검사할 수도 있다.

```shell
kubectl logs indexed-job-fdhq5 # 잡에 속한 파드의 이름에 맞춰 변경한다.
```


출력 결과는 다음과 비슷하다.

```
xuq
```
