확장(Expansions)을 사용한 병렬 처리

확장(Expansions)을 사용한 병렬 처리 (Parallel Processing using Expansions)

이 작업은 공통 템플릿을 기반으로 여러 Job을 실행하는 방법을 보여 줘요. 이 접근 방식을 사용해 작업 묶음(batches of work)을 병렬로 처리할 수 있어요.

이 예시에는 apple, banana, cherry 세 개의 항목만 있어요. 예시 Job은 문자열을 출력한 다음 일시 정지하는 방식으로 각 항목을 처리해요.

이 패턴이 더 현실적인 사용 사례에 어떻게 맞는지 배우려면 '실제 워크로드에서 Job 사용하기'를 참고하세요.

출처: 문서

본문

시작하기 전에

Job의 기본적이고 병렬이 아닌 사용에 익숙해야 해요.

쿠버네티스 클러스터가 필요하고, kubectl 명령줄 도구가 클러스터와 통신하도록 설정돼 있어야 해요. 이 튜토리얼은 제어 플레인 호스트 역할을 하지 않는 노드가 최소 두 개 있는 클러스터에서 실행하는 것을 권장해요. 아직 클러스터가 없다면 minikube를 사용하거나 다음 쿠버네티스 플레이그라운드 중 하나를 사용해 만들 수 있어요.

  • iximiuz Labs
  • Killercoda
  • KodeKloud

기본 템플릿 작업에는 명령줄 유틸리티 sed가 필요해요.

고급 템플릿 예시를 따라가려면 Python과 Python용 Jinja2 템플릿 라이브러리의 동작하는 설치가 필요해요.

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

다운로드한 파일은 아직 유효한 쿠버네티스 매니페스트가 아니에요. 대신 그 템플릿은 사용하기 전에 채워야 하는 몇 가지 자리 표시자(placeholders)가 있는 Job 객체의 YAML 표현이에요. $ITEM 구문은 쿠버네티스가 의미를 알지 못해요.

템플릿에서 매니페스트 만들기

다음 셸 스니펫은 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 매니페스트를 생성하는 프로그램을 작성할 수 있어요.

매니페스트에서 Job 만들기

다음으로 하나의 kubectl 명령으로 모든 Job을 만들어요.

kubectl create -f ./jobs

출력은 다음과 비슷해요.

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

이제 Job을 확인해요.

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

kubectl의 -l 옵션을 사용하면 이 Job 그룹의 일부인 Job만 선택해요(시스템에 다른 관련 없는 Job이 있을 수 있어요).

같은 라벨 셀렉터로 파드도 확인할 수 있어요.

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

이 단일 명령으로 모든 Job의 출력을 한 번에 확인할 수 있어요.

kubectl logs -f -l jobgroup=jobexample

출력은 다음과 같아야 해요.

Processing item apple
Processing item banana
Processing item cherry

정리하기

# 생성한 Job 제거
# 클러스터가 그 파드들을 자동으로 정리

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 %}

위 템플릿은 python dict 목록(1-4행)을 사용해 각 Job 객체에 대해 두 개의 파라미터를 정의해요. for 루프는 각 파라미터 집합에 대해 하나의 Job 매니페스트를 생성해요(나머지 행).

이 예시는 YAML의 한 기능에 의존해요. 하나의 YAML 파일은 ---(한 줄에)로 구분된 여러 문서(여기서는 쿠버네티스 매니페스트)를 포함할 수 있어요. 출력을 kubectl에 직접 파이프해 Job을 만들 수 있어요.

다음으로 이 한 줄 Python 프로그램으로 템플릿을 확장해요.

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

render_template을 사용해 파라미터와 템플릿을 쿠버네티스 매니페스트를 포함하는 단일 YAML 파일로 변환해요.

# 이전에 정의한 alias가 필요함
cat job.yaml.jinja2 | render_template > jobs.yaml

render_template 스크립트가 올바르게 동작했는지 jobs.yaml을 확인할 수 있어요.

render_template이 의도한 대로 동작한다고 확신하면, 그 출력을 kubectl에 파이프할 수 있어요.

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

쿠버네티스는 생성한 Job을 받아들이고 실행해요.

정리하기

# 생성한 Job 제거
# 클러스터가 그 파드들을 자동으로 정리

kubectl delete job -l jobgroup=jobexample

실제 워크로드에서 Job 사용하기

실제 사용 사례에서 각 Job은 영화의 한 프레임을 렌더링하거나 데이터베이스의 행 범위를 처리하는 것처럼 상당한 계산을 수행해요. 영화를 렌더링한다면 $ITEM을 프레임 번호로 설정할 거예요. 데이터베이스 테이블의 행을 처리한다면 $ITEM을 처리할 데이터베이스 행 범위를 나타내도록 설정할 거예요.

이 작업에서는 파드의 로그를 가져와 출력을 수집하는 명령을 실행했어요. 실제 사용 사례에서 Job의 각 파드는 완료 전에 지속적인 저장소에 출력을 기록해요. 각 Job에 PersistentVolume을 사용하거나 외부 저장소 서비스를 사용할 수 있어요. 예를 들어 영화 프레임을 렌더링한다면 각 프레임에 다른 URL을 사용해 렌더링된 프레임 데이터를 URL에 HTTP PUT하세요.

Job과 파드의 라벨 (Labels on Jobs and Pods)

Job을 만든 후에 쿠버네티스는 한 Job의 파드와 다른 Job의 파드를 구분하는 추가 라벨을 자동으로 추가해요.

이 예시에서 각 Job과 그 파드 템플릿에는 jobgroup=jobexample 라벨이 있어요.

쿠버네티스 자체는 jobgroup이라는 이름의 라벨에 주의를 기울이지 않아요. 템플릿에서 만든 모든 Job에 라벨을 설정하면 그 모든 Job에 한꺼번에 작업을 수행하기 편리해요. 첫 번째 예시에서는 템플릿으로 여러 Job을 만들었어요. 템플릿은 각 파드도 같은 라벨을 받도록 보장하므로, 단일 명령으로 이 템플릿 Job들의 모든 파드를 확인할 수 있어요.

참고:

대안 (Alternatives)

많은 수의 Job 객체를 만들 계획이라면 다음을 알게 될 수 있어요.

  • 라벨을 사용하더라도 그렇게 많은 Job을 관리하는 것은 번거로워요.
  • 한 번에 많은 Job을 만들면 쿠버네티스 제어 플레인에 높은 부하를 줄 수 있어요. 또는 쿠버네티스 API 서버가 요청을 제한(rate limit)해 429 상태로 요청을 일시적으로 거부할 수 있어요.
  • Job에 대한 리소스 할당량으로 제한받을 수 있어요. 한 배치에 많은 작업을 만들면 API 서버가 일부 요청을 영구적으로 거부할 수 있어요.

Job 객체를 아주 많이 만들지 않고도 많은 양의 작업을 처리할 수 있는 다른 Job 패턴이 있어요.

Job 객체를 자동으로 관리하는 자신만의 컨트롤러를 작성하는 것도 고려할 수 있어요.

더 알아보기 (Learn more)