Information in this document may be out of date

This document has an older update date than the original, so the information it contains may be out of date. If you're able to read English, see the English version for the most up-to-date information: Fine Parallel Processing Using a Work Queue

작업 대기열을 사용한 정밀 병렬 처리

이 예에서는, 지정된 파드에서 여러 병렬 워커 프로세스가 있는 쿠버네티스 잡(Job)을 실행한다.

이 예에서는, 각 파드가 생성될 때, 작업 대기열에서 하나의 작업 단위를 선택하여, 처리하고, 대기열이 비워질 때까지 반복한다.

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

  1. 작업 대기열을 보관할 스토리지 서비스를 시작한다. 이 예에서는, Redis를 사용하여 작업 항목을 저장한다. 이전 예에서는, RabbitMQ를 사용했다. 이 예에서는, AMQP가 길이가 정해져 있는 작업 대기열이 비어있을 때 클라이언트가 이를 감지할 수 있는 좋은 방법을 제공하지 않기 때문에 Redis 및 사용자 지정의 작업 대기열 클라이언트 라이브러리를 사용한다. 실제로는 Redis와 같은 저장소를 한 번 설정하고 여러 작업과 다른 것들의 작업 대기열로 재사용한다.
  2. 대기열을 만들고, 메시지로 채운다. 각 메시지는 수행할 하나의 작업을 나타낸다. 이 예에서, 메시지는 긴 계산을 수행할 정수다.
  3. 대기열에서 작업을 수행하는 잡을 시작한다. 잡은 여러 파드를 시작한다. 각 파드는 메시지 대기열에서 하나의 작업을 가져와서, 처리한 다음, 대기열이 비워질 때까지 반복한다.

시작하기 전에

쿠버네티스 클러스터가 필요하고, kubectl 커맨드-라인 툴이 클러스터와 통신할 수 있도록 설정되어 있어야 한다. 이 튜토리얼은 컨트롤 플레인 호스트가 아닌 노드가 적어도 2개 포함된 클러스터에서 실행하는 것을 추천한다. 만약, 아직 클러스터를 가지고 있지 않다면, minikube를 사용해서 생성하거나 다음 쿠버네티스 플레이그라운드 중 하나를 사용할 수 있다.

의 기본적이고, 병렬 작업이 아닌, 사용법에 대해 잘 알고 있어야 한다.

Redis 시작

이 문서의 예시에서는, 단순함을 위해, Redis의 단일 인스턴스를 시작한다. Redis를 확장 가능하고 중복적으로 배포하는 예에 대해서는 Redis 예시를 참고한다.

다음 파일을 직접 다운로드할 수도 있다.

작업으로 대기열 채우기

이제 몇 가지 "작업"으로 대기열을 채운다. 이 예제의 작업은 문자열을 출력하는 것이다.

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

이제 엔터 키를 누르고, 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 로 변경해야 할 수 있다.

이미지 생성

이제 실행할 이미지를 만들 준비가 되었다.

redis 클라이언트와 함께 python 워커 프로그램을 사용하여 메시지 큐에서 메시지를 읽는다.

rediswq.py(다운로드)라는 간단한 Redis 작업 대기열 클라이언트 라이브러리가 제공된다.

잡의 각 파드에 있는 "워커" 프로그램은 작업 대기열 클라이언트 라이브러리를 사용하여 작업을 가져온다. 다음은 워커 프로그램이다.

#!/usr/bin/env python

import time
import rediswq

host="redis"
# Uncomment next two lines if you do not have Kube-DNS 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.pyDockerfile 파일을 다운로드할 수 있고, 그런 다음 이미지를 만들 수도 있다.

docker build -t job-wq-2 .

이미지 푸시

도커 허브(Docker Hub)를 위해, 아래 명령으로 사용자의 username과 앱 이미지에 태그하고 허브에 푸시한다. <username> 을 사용자의 허브 username으로 바꾼다.

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

공용 저장소로 푸시하거나 개인 저장소에 접근할 수 있도록 클러스터를 구성해야 한다.

Google Container Registry를 사용하는 경우, 사용자의 프로젝트 ID로 앱 이미지에 태그를 지정하고 GCR로 푸시한다. <project> 를 사용자의 프로젝트 ID로 바꾼다.

docker tag job-wq-2 gcr.io/<project>/job-wq-2
gcloud docker -- push gcr.io/<project>/job-wq-2

잡 정의

다음은 잡 정의이다.

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 를 변경하려면 잡 템플릿을 편집해야 한다.

이 예에서, 각 파드는 대기열의 여러 항목에 대해 작업한 다음 더 이상 항목이 없을 때 종료된다. 워커는 작업 대기열이 비어있을 때를 감지하고 잡 컨트롤러는 작업 대기열에 대해 알지 못하기 때문에, 작업이 완료되면 워커에게 신호를 보낸다. 워커는 성공적으로 종료하여 대기열이 비어 있음을 알린다. 따라서, 워커가 성공적으로 종료하자마자, 컨트롤러는 작업이 완료되었음을 인식하고, 파드가 곧 종료된다. 따라서, 잡 완료 횟수를 1로 설정했다. 잡 컨트롤러는 다른 파드도 완료될 때까지 기다린다.

잡 실행

이제 잡을 실행한다.

kubectl apply -f ./job.yaml

이제 조금 기다린 다음, 잡을 확인한다.

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 2016 17:07:59 -0800
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:              gcr.io/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

아래와 같이 시간 제한(timeout)을 설정하고, 잡이 성공할 때까지 기다린다.

# 조건명은 대소문자를 구분하지 않는다.
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

보다시피, 파드 중 하나가 여러 작업 항목을 처리했다.

대안

대기열 서비스를 실행하거나 작업 대기열을 사용하도록 컨테이너를 수정하는 것이 불편한 경우, 다른 잡 패턴 중 하나를 고려할 수 있다.

만약 실행할 백그라운드 처리 작업의 연속 스트림이 있는 경우, ReplicaSet 이 있는 백그라운드 워커를 실행하는 것과, https://github.com/resque/resque와 같은 백그라운드 처리 라이브러리를 실행하는 것이 좋다.