본문 바로가기

Infrastructure/Kubernetes

[워크로드 리소스] 잡(Job)

잡(Job)

쿠버네티스 공식문서를 확인하며 잡(Job)에 대해서 기억해야 하는 부분을 기록한다.

  • 잡에서 하나 이상의 파드를 생성하고 지정된 수의 파드가 성공적으로 종료될 때까지 계속해서 파드의 실행을 재시도한다.
  • 파드가 성공적으로 완료되면 성공적으로 완료된 잡을 추적하고, 지정된 성공 완료 수치에 도달하면, 작업이 완료된다.
  • 잡을 삭제하면 잡이 생성한 파드가 정리되고 작업을 일시 중지하면 작업이 다시 재개될 때까지 활성 파드가 삭제된다.
  • 잡의 예시 중하나로 잡 오브젝트를 하나 생성해서 파드 하나를 안정적으로 실행시키고 완료시킬 수 있다.
  • 첫 번째 파드가 어떠한 이유로 실패 또는 삭제되는 경우, 잡 오브젝트는 새로운 파드를 기동시킨다.
  • 잡을 사용하면 여러 파드를 병렬로 실행할 수도 있다.
  • 잡을 스케줄에 따라 구동하고 싶은 경우에는 크론잡을 사용하면 된다.

[정리]

사용자가 지정하는 특정 작업을 수행하기 위해서 잡(Job)이 사용되고, 잡을 완료하기 위해 파드가 생성된다. 잡을 삭제하는 경우, 잡을 완료시키기 위해 생성된 파드가 제거된다. 잡은 병렬로 실행될 수 있으며 여러 파드에서 병렬로 실행될 수 있다.


예시

  • 파이(π)의 2000자리까지 출력하고 이를 완료하는데 10초가 걸리는 예시를 살펴본다.
apiVersion: batch/v1
kind: Job
metadata:
  name: pi
spec:
  template:
    spec:
      containers:
      - name: pi
        image: perl:5.34.0
        command: ["perl",  "-Mbignum=bpi", "-wle", "print bpi(2000)"]
      restartPolicy: Never
  backoffLimit: 4
  • 아래의 커맨드를 실행하여 잡을 실행할 수 있다.
# 실행
kubectl apply -f https://kubernetes.io/examples/controllers/job.yaml
# 결과
job.batch/pi created
  • kubectl describe job pi, kubectl get job pi -o yaml 커맨드를 실행하여 잡의 상태를 확인할 수 있다.
Name:           pi
Namespace:      default
Selector:       controller-uid=c9948307-e56d-4b5d-8302-ae2d7b7da67c
Labels:         controller-uid=c9948307-e56d-4b5d-8302-ae2d7b7da67c
                job-name=pi
Annotations:    kubectl.kubernetes.io/last-applied-configuration:
                  {"apiVersion":"batch/v1","kind":"Job","metadata":{"annotations":{},"name":"pi","namespace":"default"},"spec":{"backoffLimit":4,"template":...
Parallelism:    1
Completions:    1
Start Time:     Mon, 02 Dec 2019 15:20:11 +0200
Completed At:   Mon, 02 Dec 2019 15:21:16 +0200
Duration:       65s
Pods Statuses:  0 Running / 1 Succeeded / 0 Failed
Pod Template:
  Labels:  controller-uid=c9948307-e56d-4b5d-8302-ae2d7b7da67c
           job-name=pi
  Containers:
   pi:
    Image:      perl:5.34.0
    Port:       <none>
    Host Port:  <none>
    Command:
      perl
      -Mbignum=bpi
      -wle
      print bpi(2000)
    Environment:  <none>
    Mounts:       <none>
  Volumes:        <none>
Events:
  Type    Reason            Age   From            Message
  ----    ------            ----  ----            -------
  Normal  SuccessfulCreate  14m   job-controller  Created pod: pi-5rwd7

잡 사양 작성하기

  • 잡의 사양을 작성할 때 다른 설정과 동일하게 apiVersion, kind, metadata 필드는 필수값으로 입력해야 하고 .spec 섹션도 필수로 입력해야 한다.

파드 템플릿

  • .spec.template.spec의 필수 필드이며 파드 템플릿을 의미하며 apiVersion, kind가 없다는 것을 제외한다면 파드와 정확하게 같은 스키마를 가지고 있다.
  • 파드의 필수 필드 외에도 잡의 파드 템플릿은 적절한 레이블과 적절한 재시작 정책을 명시해야 한다.
  • RestartPolicyNever 또는 OnFailure만 지원한다.

파드 셀렉터

  • spec.selector 필드는 선택 사항이며 대부분의 경우에 지정해서는 안된다. 자신의 파드 셀렉터를 지정하기 위해 섹션을 참고한다.

잡에 대한 병렬 실행

  • 잡으로 실행하기에 적합한 작업 유형은 크게 세 가지가 있다.
    1. 비-병렬(Non-parallel)잡:
      • 일반적으로, 파드가 실패하지 않는 한, 하나의 파드만 시작된다.
      • 파드가 성공적으로 종료하자마자 즉시 잡이 완료된다.
    2. 고정적(fixed)인 완료 횟수를 가진 병렬 잡:
      • spec.completions에 0이 아닌 양수 값을 지정한다.
      • 잡은 전체 작업을 나타내며, .spec.completions 만큼의 성공한 파드가 있을 때 완료된다.
      • .spec.completionMode="Indexed"를 사용할 때, 각 파드는 0에서 .spec.completions-1 범위 내의 서로 다른 인덱스를 가져온다.
    3. 작업 큐(queue)가 있는 병렬 잡:
      • .spec.completions를 지정하지 않고, .spec.parallelism를 기본으로 한다.
      • 파드는 각자 또는 외부 서비스 간에 조정을 통해 각각의 작업을 결정해야 한다. 예를 들어, 파드는 작업 큐에서 최대 N개의 항목을 일괄로 가져올(fetch) 수 있다.
      • 각 파드는 모든 피어들의 작업이 완료되었는지 여부를 독립적으로 판단할 수 있으며, 결과적으로 전체 잡이 완료되게 한다.
      • 잡의 모든 파드가 성공적으로 종료되면, 새로운 파드는 생성되지 않는다.
      • 하나 이상의 파드가 성공적으로 종료되고, 모든 파드가 종료되면 잡은 성공적으로 완료된다.
      • 성공적으로 종료된 파드가 하나라도 생긴 경우, 다른 파드들은 해당 작업을 지속하지 않아야 하며 어떠한 문자도 출력되어서는 안된다. 또한 모든 파드는 종료되는 과정에 있어야 한다.
  • 비-정렬잡은 .spec.completionsspec.parallelism 모두를 설정하지 않을 수 있으며, 기본값은 1이다.
  • 고정적인 완료 횟수 잡은 .spec.completions을 필요한 완료 횟수로 설정해야 한다.
  • 작업 큐잡은 .spec.completions를 설정하지 않은 상태로 두고, .spec.parallelism을 음수가 아닌 정수로 설정해야 한다.

병렬 처리 제어하기

  • 요청된 병렬 처리(.spec.parallelism)는 음수가 아닌 값으로 설정할 수 있다. 만약 지정되지 않은 경우에는 1이 기본값이 되고, 0으로 설정되는 경우 병렬 처리가 증가할 때까지 사실상 일시 중지된다.
  • 실제 병렬 처리는 아래와 같은 이유로 요청된 병렬 처리보다 많거나 적을 수 있다.
    • 고정적인 완료 횟수(fixed completion count) 잡의 경우, 병렬로 실행 중인 파드의 수는 남은 완료 수를 초과하지 않는다. 사실상 .spec.parallelism의 더 큰 값은 사실상 무시된다.
    • 작업 큐 잡은 파드가 성공한 이후에 새로운 파드가 시작되지 않고 나머지 파드는 완료될 수 있다.
    • 잡 컨트롤러가 반응할 시간이 없는 경우 병렬 처리되는 수는 요청보다 많거나 적을 수 있다.
    • 리소스 부족과 같은 이유로 잡 컨트롤러가 파드 생성에 실패한 경우, 요청한 것보다 적은 수의 파드가 있을 수 있다.
    • 잡 컨트롤러는 동일한 잡에서 과도하게 실패한 이전 파드들로 인해 새로운 파드의 생성을 조절할 수 있다.
    • 파드가 정상적으로(gracefully) 종료되면, 중지하는데 시간이 소요된다.

완료 모드

  • 완료 횟수가 null이 아니고 .spec.completions와 같이 고정적인 완료 횟수를 가진 경우, .spec.completionMode에 지정된 완료 모드를 가질 수 있다.

    • NonIndexed(기본값): .spec.completions가 성공적으로 완료된 파드가 있는 경우 작업이 완료된 것으로 간주된다. 즉, 각 파드 완료는 서로 상동(homologous)하다. .spec.completionsnull인 잡의 경우 암시적으로 NonIndexed이다.

    • Indexed: 잡의 파드는 연결된 완료 인덱스를 0에서 .spec.completions-1까지 가져온다. 인덱스는 아래의 세 가지 메커니즘으로 얻을 수 있다.

      • 파드 애너테이션 batch.kubernetes.io/job-completion-index.

      • 파드 호스트네임 중 일부($(job-name)-$(index) 형태). 인덱스된 잡과 서비스를 결합하여 사용하고 있다면, 잡에 속한 파드는 DNS를 이용하여 서로를 디스커버 하기 위해 사전에 결정된 호스트 네임을 사용할 수 있다.

      • 컨테이너화된 태스크의 경우, JOB_COMPLETION_INDEX 환경 변수.

        각 인덱스에 대해 성공적으로 완료된 파드가 하나 있으면 작업이 완료된 것으로 간주된다. 간혹, 동일한 인덱스에 대해 둘 이상의 파드를 시작할 수 있지만, 그 중 하나만 완료 횟수에 포함된다.

[정리]

잡의 사양에서 .spec는 파드 템플릿을 의미하며 파드와 거의 동일한 스키마를 가지고 있다. 파드의 필수 필드 외에도 파드 템플릿은 적절한 레이블과 적절한 재시작 정책을 명시해야 한다. 셀렉터의 경우 선택 사항이지만 대부분의 경우 사용되지 않는다.

잡의 경우 비-병렬, 고정적인 완료 횟수, 작업큐가 있는 병렬과 같이 사용되기에 적합한 유형이 있으며 잡을 사용할 계획이라면 자신의 작업이 잡을 사용하기 적합한 작업인지 확인해야 한다.

잡의 병렬처리를 위한 .spec.parallelism의 값은 0 또는 양의 정수로 설정할 수 있다. 1이 기본값이며 여러가지 이유로 설정한 값과 요청과 다른 수의 잡이 병렬로 실행될 수 있다.

완료 횟수를 .spec.completions를 통해서 지정할 수 있으며 .spec.completionMode필드를 통해 지정된 완료 모드를 가질 수 있다. 완료모드의 기본값은 인덱싱을 하지 않는 NonIndexed이며 필요에 따라 Indexed 모드를 사용할 수 있다.


파드와 컨테이너 장애 처리하기

  • 파드내 컨테이너의 프로세스가 0이 아닌 종료 코드로 종료되었거나 컨테이너 메모리 제한을 초과해서 죽는 여러가지 이유로 실패할 수 있다.
  • 만약 이런 일이 발생하고 .spec.template.spec.restartPlicy = "OnFailure"라면 파드는 노드에 그대로 유지되지만, 컨테이너는 다시 실행된다.
  • 프로그램은 로컬에서 재시작될 때의 케이스를 다루거나 .spec.template.spec.restartPolicy = "Never"로 지정해야 한다.
  • 파드가 노드에서 내보내지는 경우(노드 업그레이드, 재부팅, 삭제 등) 또는 파드의 컨테이너가 실패되고 .spec.template.spec.restartPolicy = "Never"로 설정됨과 같은 여러 이유로 전체 파드가 실패할 수 있다.
  • 파드가 실패하면 잡 컨트롤러는 새 파드를 시작하기 때문에 우리는 애플리케이션이 새 파드에서 재시작될 때 이러한 케이스를 처리해야 한다.
  • .spec.parallelism = 1, .spec.completions = 1 그리고 .spec.template.spec.restartPolicy = "Never"를 지정하더라도 같은 프로그램을 두 번 시작하는 경우가 있다는 점을 참고한다.
  • .spec.parallelism 그리고 .spec.completions를 모두 1보다 크게 지정한다면 한번에 여러 개의 파드가 실행될 수 있기 때문에 동시성에 대해서도 관대(tolerant)해야 한다.

파드 백오프(backoff) 실패 정책

  • 파드 구성에 논리적인 오류가 포함되어 있는 경우 파드는 재실행하게 될 것이고 재실행에 대한 횟수를 제한해야하는 경우가 있을 수 있다. 이러한 경우에는 .spec.backoffLimit 값을 설정해 주어야 한다.
  • 백오프 제한은 기본적으로 6으로 설정되어 있고, 이는 잡에 연계도니 실패 상태 파드가 6분 내에서 지수적으로 증가하는 백-오프 지연(10초, 20초 …)을 적용하여, 잡 컨트롤러에 의해 재생성되는 것을 의미한다.
  • 파드 실행 재시도 횟수는 아래의 두 가지 방법으로 계산된다.
    • .status.phase = "Failed"인 파드의 수.
    • restartPolicy = "OnFailure"를 사용하는 경우, .status.phasePending이거나 Running인 파드들이 가지고 있는 모든 컨테이너의 수.
  • 계산 중 하나가 .spec.backoffLimit에 도달하면, 잡이 실패한 것으로 간주한다.
  • JobTrackingWithFinalizers 기능이 비활성화되어 있다면, 실패한 파드의 수는 API에 표시되고 있는 파드로만 계산된다.
  • 잡에 restartPolicy = "OnFailure"가 있는 경우 잡 백오프 한계에 도달하면 잡을 실행 중인 파드가 종료되고 이로 인해 잡 실행 파일의 디버깅이 더 어려워질 수 있다.
  • 디버깅하거나 로깅 시스템을 사용해서 실패한 작업의 결과를 실수로 손실되지 않도록 하려면 restartPolicy = "Never"로 설정하는 것이 권장된다.

[정리]

파드는 여러가지 이유로 실패할 수 있고 재실행 정책이 “OnFailure”라면 파드는 노드에 유지되고, 컨테이너는 재실행된다. 만약 애플리케이션 재실행에 대해 처리를 하지 않았다면 재실행 정책은 “Never”로 설정해야 한다. 사용자가 실행 횟수를 1로 의도하고 설정을 하더라도 동일한 프로그램이 두 번 실행될 수 있으며, 한번에 여러 개의 파드가 실행되는 경우에 대한 동시성도 염두해 두어야 한다.

.spec.backoffLimit값을 설정하여 재실행의 수를 제한하여 무제한으로 재실행되는 것을 방지할 수 있다. 재실행 정책이 “OnFailure”인 경우 잡의 재실행으로 인해 디버깅이 어려워질 수 있기 때문에 디버깅 또는 작업의 손실을 방지하기 위해서 “Never”로 설정하는 것이 권장된다.


잡의 종료와 정리

  • 잡이 완료되면 파드가 더 이상 생성되지 않지만, “일반적”으로는 삭제되지 않는다.

  • 잡의 완료 상태를 유지하며 파드의 로그를 계속 보고 에러, 경고 또는 다른 기타 진단 출력을 확인할 수 있다.

  • 잡 오브젝트는 완료된 후에도 상태를 볼 수 있도록 남아있고, 상태를 확인한 후 이전 잡을 삭제하는 것은 사용자의 책임이다.

  • kubectl커맨드로 잡을 삭제할 수 있으며 이때 생성된 모든 파드도 함께 삭제된다.

  • 기본적으로 파드의 실패(restartPolicy=Never) 또는 컨테이너 오류(restartPolicy=OnFailure)로 종료되지 않는 한, 잡은 중단되지 않고 실행되고 이때 위에서 살펴본 .spec.backoffLimit까지 연기된다.

  • “유효 데드라인”을 설정하여 잡을 종료할 수 있으며, .spec.activeDeadlineSeconds 필드를 초단위로 설정하면 된다.

  • activeDeadlineSeconds는 생성된 파드의 수에 관계 없이 잡의 기간에 적용되고, 잡이 activeDeadlineSeconds에 도달하면, 실행 중인 모든 파드가 종료되고 잡의 상태는 reason:DeadlineExceeded와 함께 type: Failed가 된다.

  • 잡의 .spec.activeDeadlineSeconds.spec.backoffLimit보다 우선한다는 점을 참고해야 한다.

  • 하나 이상 실패한 파드를 재시도하는 잡은 backoffLimit에 도달하지 않은 경우에도 activeDeadlineSeconds에 지정된 시간 제한에 도달하면 추가 파드를 배포하지 않는다.

  • 아래는 activeDeadlineSecondsbackoffLimit 값을 설정하는 예시이다.

      apiVersion: batch/v1
      kind: Job
      metadata:
        name: pi-with-timeout
      spec:
        backoffLimit: 5
        activeDeadlineSeconds: 100
        template:
          spec:
            containers:
            - name: pi
              image: perl:5.34.0
              command: ["perl",  "-Mbignum=bpi", "-wle", "print bpi(2000)"]
            restartPolicy: Never
  • 잡의 사양과 잡의 파드 템플릿 사양에는 모두 activeDeadlineSeconds 필드가 있으며 적절한 값으로 설정해야 한다.

  • restartPolicy는 잡 자체에 적용되는 것이 아니라 파드에 적용되어 있다는 것을 기억해야 한다.

  • 잡의 상태가 type: Failed이 되면, 잡의 자동 재시작은 없다. 즉, .spec.activeDeadlineSeconds.spec.backoffLimit로 활성화된 잡의 종료 메커니즘은 영구적인 잡의 실패를 유발하며 이를 해결하기 위해 사용자의 개입이 필요하다.

[정리]

잡이 종료되는 경우 파드는 더 이상 생성되지 않지만 일반적으로 삭제되지는 않는다. 완료 상태의 파드를 통해 로그와 결과를 확인할 수 있고 필요 없어진 파드는 사용자가 수동으로 삭제해 주어야 한다.

activeDeadlineSeconds 를 초단위로 설정하여 설정한 시간이 초과하는 경우 잡을 실패 상태로 만들 수 있다.


완료된 잡을 자동으로 정리

  • 완료된 잡은 일반적으로 시스템에서 더 이상 필요로 하지 않고, 이를 시스템 내에 유지한다면 API 서버에 부담이 된다.
  • 만약 크론잡과 같은 상위 레벨 컨트롤러가 잡을 직접 관리하는 경우, 지정된 용량 기반 정리 정책에 따라 크론잡이 잡을 정리할 수 있다.

완료된 잡을 위한 TTL 메커니즘

  • 완료된 잡 (Complete 또는 Failed)을 자동으로 정리하는 또 다른 방법은 잡의 .spec.ttlSecondsAfterFinished 필드를 지정해서 완료된 리소스에 대해 TTL 컨트롤러에서 제공하는 TTL 메커니즘을 사용하는 것이다.

  • TTL 컨트롤러는 잡을 정리하면 잡을 계단식으로 삭제한다. 즉, 잡과 함께 파드와 같은 종속 오브젝트를 삭제한다.

  • 잡을 삭제하면 finalizer와 같은 라이프사이클 보증이 보장되는 것을 참고해야 한다.

  • 아래는 ttlSecondsAfterFinished 값을 설정하는 예시이다.

      apiVersion: batch/v1
      kind: Job
      metadata:
        name: pi-with-ttl
      spec:
        ttlSecondsAfterFinished: 100
        template:
          spec:
            containers:
            - name: pi
              image: perl:5.34.0
              command: ["perl",  "-Mbignum=bpi", "-wle", "print bpi(2000)"]
            restartPolicy: Never
    • pi-with-ttl 잡은 완료 후 100초 이후에 자동으로 삭제될 수 있다.
    • 만약 필드를 0으로 설정하면, 잡이 완료된 직후에 자동으로 삭제되도록 할 수 있다. 하지만 필드를 설정하지 않으면 완료된 후에 TTL 컨트롤러에 의해 정리되지 않는다.
  • ttlSecondsAfterFinished 필드를 설정하는 것을 권장하는데, 이는 관리되지 않는 잡(직접 생성한, 크론잡등, 다른 워크로드 API를 통해 간접적으로 생성하지 않은 잡)의 기본 삭제 정책이 orphanDependents(관리되지 않는 잡이 완전히 삭제되어도 해당 잡에 의해 생성된 파드를 남겨둠)이기 때문이다.

  • 삭제된 잡의 파드가 실패하거나 완료된 뒤 컨트롤러 플레인이 언젠가 가비지 콜렉션을 한다고 해도, 남아 있는 파드는 클러스터의 성능을 저하시키거나 최악의 경우에는 이 성능 저하로 인해 클러스터가 중단될 수 있다.

  • “리밋 레인지(Limit Range)”와 “리소스 쿼터”를 사용하여 특정 네임스페이스가 사용할 수 있는 자원량을 제한할 수 있다.

[정리]

완료된 잡은 기본적으로 삭제되지 않기 때문에 사용자가 직접 수동으로 삭제해 주어야 한다. 만약 사용자가 수동으로 삭제하지 않는다면 남아있는 파드로 인해 클러스터의 성능이 저하될 수 있다. 이 때 ttlSecondsAfterFinished 필드의 값을 설정하여 작업이 완료되고 일정 시간 이후에 파드가 자동으로 삭제되도록 지정할 수 있다.


잡 패턴

  • 잡 오브젝트를 사용해서 신뢰할 수 있는 파드의 병렬 실행을 지원할 수 있지만, 잡 오브젝트는 컴퓨팅에서 일반적으로 사용되는 밀접하게 통신하는 병렬 프로세스를 지원하도록 설계되지 않았다.
  • 잡 오브젝트는 독립적이지만 관련된 작업 항목 집합의 병렬 처리를 지원한다. 여기에는 전송해야 하는 이메일, 렌터링할 프레임, 코드 변환이 필요한 파일, NoSQL 데이터베이스에서의 키 범위 스캔 등이 있다.
  • 복잡한 시스템에는 여러 개의 다른 작업 항목 집합이 있을 수 있다. 여기서는 사용자와 함께 관리하려난 하나의 작업 항목 집합 - 배치 잡을 고려하고 있다.
  • 병렬 계산에는 몇몇 다른 패턴이 있으며 각각의 장단점이 있으며 트레이드오프는 아래와 같다.
    • 각 작업 항목에 대한 하나의 잡 오브젝트(A) vs 모든 작업 항목에 대한 단일 잡 오브젝트(B): A는 사용자와 시스템이 많은 수의 잡 오브젝트를 관리해야 하는 약간의 오버헤드를 만든다. B의 경우 작업 항목 수가 많은 경우에 더 적합하다.
    • 작업 항목과 동일한 개수의 파드 생성(A) vs 각 파드에서 다수의 작업 항목을 처리(B): A는 일반적으로 기존 코드와 컨테이너를 거의 수정할 필요가 없다. B는 이전 글 머리표(-)와 비슷한 이유로 많은 수의 작업 항목에 적합하다.
    • 여러 접근 방식이 작업 큐를 사용한다. 이를 위해서는 큐 서비스를 실행하고, 작업 큐를 사용하도록 기존 프로그램이나 컨테이너를 수정해야 한다. 다른 접근 방식들은 기존에 컨테이너화된 애플리케이션에 보다 쉽게 적용할 수 있다.
패턴 단일 잡 오브젝트 작업 항목보다 파드가 적은가? 수정되지 않은 앱을 사용하는가?
작업 항목 당 파드가 있는 큐 O 때때로
가변 파드 수를 가진 큐 O O
정적 작업 할당을 사용한 인덱싱된 잡 O O
잡 템플릿 확장 O
- .spec.completions로 완료를 지정할 때, 잡 컨트롤러에 의해 생성된 각 파드는 동일한 사양을 갖는다. 이것은 작업의 모든 파드는 동일한 명렬 줄과 동일한 이미지, 동일한 볼륨, 거의 동일한 환경 변수를 가진다는 것을 의미한다.
- 이러한 패턴은 파드가 다른 작업을 수행하도록 배열하는 다른 방법이다.
- 아래의 표는 각 패턴에 필요한 .spec.parallelism 그리고 .spec.completions 설정을 보여준다. (W는 작업 항목의 수를 의미한다.)
패턴 .spec.completions .spec.parallelism
작업 항목 당 파드가 있는 큐 W any
가변 파드 수를 가진 큐 null any
정적 작업 할당을 사용한 인덱싱된 잡 W any
잡 템플릿 확장 1 1이어야 함

[정리]

잡 오브젝트는 파드의 병렬 실행을 지원하지만 컴퓨팅에서 사용되는 병렬 프로세스를 지원하도록 설계되지는 않았다. 잡 오브젝트는 독립적이지만 관련된 “작업 항목” 집합의 병렬 처리를 지원한다. 병렬 처리를 계산할 때 고려해야 하는 트레이드오프가 있기 때문에 적용 전에 잡의 성향에 맞는 방식을 선택해야 한다.


고급 사용법

잡 일시 중지

  • 잡이 생성되면, 잡 컨트롤러는 잡의 요구 사항을 충족하기 위해 즉시 파드 생성을 시작하고 잡이 완료될 때까지 계속한다.

  • 그러나, 잡의 실행을 일시적으로 중단하고 나중에 재개하거나, 잡을 중단 상태로 생성하고 언제 시작할지를 커스텀 컨트롤러가 나중에 결정하도록 하고 싶을 수 있다.

  • 잡을 일시 중지하려면, 잡의 .spec.suspend 필드를 true로 업데이트할 수 있다. 이후에, 다시 재개하려면, false로 업데이트한다. .spec.suspend로 설정된 잡을 생성하면 일시 중지된 상태로 생성된다.

  • 잡이 일시 중지에서 재개되면, 해당 .status.startTime 필드가 현재 시간으로 재설정된다. 즉, 잡이 일시 중지 및 재개되면 .spec.activeDeadlineSeconds 타이머가 중지되고 재설정된다.

  • 잡을 일시 중지하면, Completed 상태가 아닌 모든 실행중인 파드가 SIGTERM 시그널로 종료된다.

  • 파드의 정상 종료 기간이 적용되면 사용자의 파드는 이 기간 동안에 SIGTERM 시그널을 처리해야 하며, 여기에는 진행 상황을 저장하거나 변경 사항을 취소하는 작업이 포함될 수 있다.

  • 이렇게 종료된 파드는 잡의 completions 수에 포함되지 않는다.

  • 일시 중지된 상태의 잡 정의는 아래와 같다.

      apiVersion: batch/v1
      kind: Job
      metadata:
        name: myjob
      spec:
        suspend: true
        parallelism: 1
        completions: 5
        template:
          spec:
            ...
  • 아래의 커맨드를 실행하여 잡을 중지/재개할 수 있으며 이전에 중지되었던 이력을 확인할 수 있다.

      # 활성화된 잡 일시 중지
      kubectl patch job/myjob --type=strategic --patch '{"spec":{"suspend":true}}'
      # 일시 중지된 잡 재개
      kubectl patch job/myjob --type=strategic --patch '{"spec":{"suspend":false}}'
      # 잡의 일시 중지 이력 유무 확인
      kubectl get jobs/myjob -o yaml
      apiVersion: batch/v1
      kind: Job
      # .metadata and .spec omitted
      status:
        conditions:
        - lastProbeTime: "2021-02-05T13:14:33Z"
          lastTransitionTime: "2021-02-05T13:14:33Z"
          status: "True"
          type: Suspended
        startTime: "2021-02-05T13:13:48Z"
    • “True” 상태인 “Suspended” 유형의 잡의 컨디션은 잡이 일시 중지되었음을 의미한다.
    • lastTransitionTime 필드는 잡이 일시 중지된 기간을 결정하는 데 사용할 수 있다.
    • 해당 컨디션의 상태가 “False”이면, 잡이 이전에 일시 중지되었다가 현재 실행 중인 것을 의미한다. 이러한 컨디션이 잡의 상태에 없다면, 잡이 중지되지 않은 것이다.
  • 잡이 일시 중지 및 재개될 때에도 이벤트가 생성되는데 kubectl describe jobs/myjob 커맨드를 실행하여 이벤트를 확인할 수 있다.

      Name:           myjob
      ...
      Events:
        Type    Reason            Age   From            Message
        ----    ------            ----  ----            -------
        Normal  SuccessfulCreate  12m   job-controller  Created pod: myjob-hlrpl
        Normal  SuccessfulDelete  11m   job-controller  Deleted pod: myjob-hlrpl
        Normal  Suspended         11m   job-controller  Job suspended
        Normal  SuccessfulCreate  3s    job-controller  Created pod: myjob-jvb44
        Normal  Resumed           3s    job-controller  Job resumed
  • 마지막 4개의 이벤트, “Suspended” 및 “Resumed” 이벤트는 .spec.suspend 필드를 전환한 결과이며, 이 두 개의 이벤트 사이에 파드가 생성되지 않았지만, 잡이 재개되자마자 파드 생성이 다시 시작되었음을 알 수 있다.

가변적 스케줄링 지시

  • 이 기능을 사용하려면, API 서버에 JobMutableNodeSchedulingDirectives 기능 게이트를 활성화해야 하며 기본적으로 활성화되어 있다.
  • 병렬 잡에서 대부분의 경우 파드를 특정 계약 조건 하에서 실행하고 싶은 경우가 있으며, suspend 필드는 목적을 달성하기 위한 첫 번재 단계이다.
  • 이 필드를 사용하면 커스텀 큐(queue) 컨트롤러가 잡이 언제 시작될지를 결정할 수 있다. 하지만 잡이 재개된 이후에는 커스텀 큐 컨트롤러는 잡의 파드가 실제로 어디에 할당되는지에 대해서는 영향을 주지 않는다.
  • 이러한 기능을 사용하여 잡이 실행되기 전에 잡의 스케줄링 지시를 업데이트할 수 있으며, 이를 통해 커스텀 큐 컨트롤러가 파드 배치에 영향을 줌과 동시에 노드로의 파드 실제 할당 작업을 kube-scheduler로 부터 경감시켜 줄 수 있도록 하며, 이전에 재개된 적이 없는 중지된 잡에 대해서만 허용된다.
  • 잡의 파드 템플릿 필드 중, 노드 어피니티(node affinity), 노드 셀렉터(node selector), 톨러레이션(toleration), 레이블(label), 애너테이션(annotation)은 업데이트가 가능하다.

자신의 파드 셀렉터를 지정하기

  • 일반적으로 잡 오브젝트를 생성할 때, .spec.selector를 지정하지 않고 시스템의 기본적인 로직은 잡이 생성될 때 이 필드를 추가한다. 이 값은 다른 잡과 겹쳐지지 셀렉터 값을 선택해야 한다.

  • 일부 케이스에서는 이 자동화된 셀렉터를 재정의해야 할 수도 있으며 잡의 .spec.selector를 설정할 수 있다.

  • 셀렉터를 직접 설정할 때 담당자는 매우 주의해야 하며, 해당 잡의 파드에 고유하지 않고 연관이 없는 파드와 일치하는 레이블 셀렉터를 지정하면, 연관이 없는 잡의 파드가 삭제되거나 해당 잡이 다른 파드가 완료한 것으로 수를 세거나, 하나 또는 양쪽 잡 모두 파드 생성이나 실행 완료를 거부할 수도 있다.

  • 고유하지 않은 셀렉터가 선택된 경우, 다른 컨트롤러와 해당 파드는 예측할 수 없는 방식으로 작동할 수 있다.

  • 쿠버네티스는 우리가 .spec.selector를 지정할 때 발생하는 실수를 막을 수 없을 것이다.

  • 셀렉터를 직접 지정하는 상황의 예시를 살펴본다. 이미 실행 중인 old 잡이 있다. 기존 파드는 계속 실행되기를 원하지만, 잡이 생성한 나머지 파드는 새로운 이름을 부여하기를 원한다. 하지만 관련된 필드들은 업데이트가 불가능하기 때문에 잡을 업데이트할 수 없다. 우리는 kubectl delete jobs/old --cascade=orphan 커맨드를 사용해서 old 잡을 삭제하지만, 파드를 실행 상태로 둔다. 삭제 하기 전에 kubectl get job old -o yaml 커맨드를 사용하여 어떠한 셀렉터를 사용하는지 기록해 둔다.

      kind: Job
      metadata:
        name: old
        ...
      spec:
        selector:
          matchLabels:
            controller-uid: a8f3d00d-c6d2-11e5-9f87-42010af00002
        ...
    • 이름이 new인 새로운 잡을 생성하고, 동일한 셀렉터를 명시적으로 지정한다. 기존 파드에는 controller-uid=a8f3d00d-c6d2-11e5-9f87-42010af00002 레이블이 있기 때문에 잡 new에 의해서도 제어된다.

    • 시스템이 일반적으로 자동 생성하는 셀렉터를 사용하지 않다록 하기 위해 새 잡에서 manualSelector: true를 지정해야 한다.

      kind: Job
      metadata:
      name: new
      ...
      spec:
      manualSelector: true
      selector:
        matchLabels:
          controller-uid: a8f3d00d-c6d2-11e5-9f87-42010af00002
      ...
    • 새로운 잡 자체는 a8f3d00d-c6d2-11e5-9f87-42010af00002와 다른 uid를 가지게 될 것이다.

    • manualSelector: true를 설정하면 시스템에게 사용자가 무엇을 하는지 알고 있으며 불일치를 허용한다고 알릴 수도 있다.

종료자(finalizers)를 이용한 잡 추적

  • 이 기능을 사용하기 위해서는 API 서버와 컨트롤러 매니저에 대해 JobTrackingWithFinalizers 기능 게이트를 활성화해야 한다.
  • 이 기능이 활성화되면, 컨트롤 플레인은 아래에서 다룰 동작을 이용하여 새로운 잡이 생성되는지 추적한다. 기능이 활성화되기 이전에 생성된 잡은 영항을 받지 않는다.
  • 사용자가 느낄 수 있는 유일한 차이점은 컨트롤 플레인이 잡 종료를 좀 더 정확하게 추적할 수 있다는 것이다.
  • 이 기능이 활성화되지 않으면, 잡 컨트롤러는 succeededfailed 파드의 수를 세어 잡 상태를 추적하지만 아래와 같은 이유로 제거될 수 있다.
    • 노드가 다운되었을 때 가비지 콜렉터가 버려진(orphan) 파드를 제거
    • 가비지 콜렉터가(Succeeded 또는 Failed 단계에 있는) 완료된 파드를 일정 임계값 이후에 제거
    • 잡에 속한 파드를 사용자가 임의로 제거
    • 쿠버네티스에 속하지 않는 외부 컨트롤러가 파드를 제거하거나 교체
  • 클러스터에서 JobTrackingWithFinalizers 기능을 활성화하면, 컨트롤 플레인은 잡에 속하는 파드의 상태를 추적하고 API 서버에서 제거되면 이를 감지한다.
  • 잡 컨트롤러는 batch.kubernetes.io/job-tracking 종료자를 갖는 파드를 생성한다.
  • 컨트롤러는 파드의 상태 변화가 잡 상태에 반영된 후에만 종료자를 제거하므로, 이후 다른 컨트롤러나 사용자가 파드를 제거할 수 있다.
  • 잡 컨트롤러는 새로운 잡에 대해서만 새로운 알고리즘을 적용하기 때문에 기능이 활성화되기 전에 생성된 잡은 영향을 받지 않는다.
  • 잡에 batch.kubernetes.io/job-tracking 애너테이션이 있는지 확인하여, 잡 컨트롤러가 파드 종료자를 이용하여 잡을 추적하고 있는지 여부를 확인할 수 있다. 하지만 애너테이션을 수동으로 추가하거나 제거해서는 안 된다.

[정리]

잡의 중단 및 재개와 같은 작업을 사용자가 수동으로 결정하고 싶은 경우에 spec.suspend의 값을 설정하면 된다.

JobMutableNodeSchedulingDirectives 필드를 통해서 특정 조건에만 원하는 시점에 잡이 실행되도록 할 수 있다. 잡이 실행되기 전에 스케줄링을 지시할 수 있다. 하지만 이전에 재개된 적이 없는 중지된 잡에 대해서만 허용된다.

잡을 생성할 때 기본적으로 셀렉터를 지정하지 않고 시스템에 의해 자동으로 셀렉터 필드가 추가된다. manualSelector 필드를 true로 변경하여 셀렉터를 수동으로 지정할 수도 있지만 많은 사이드 이펙트를 유발할 수 있기 때문에 담당자는 주의해서 작업해야 한다.

JobTrackingWithFinalizers 기능을 활성화하여 잡의 종료를 더 정확하게 추적할 수 있도록 할 수 있다. 잡의 종료를 감시하는 job-tracking 를 갖는 파드를 추가로 생성한다.


대안

베어(Bare) 파드

  • 파드가 실행 중인 노드가 재부팅되거나 실패하면 파드가 종료되고 다시 시작되지는 않지만 잡은 종료된 항목을 대체하기 위해 새 파드를 생성한다.
  • 애플리케이션에 단일 파드만 필요한 경우에도 베어 파드 대신 잡을 사용하는 것을 권장한다.

레플리케이션 컨트롤러

  • 잡은 레플리케이션 컨트롤러를 보완한다. “레플리케이션 컨트롤러”는 종료하지 않을 파드를 관리하고, “잡”은 종료될 것으로 예상되는 파드를 관리한다.
  • 파드 라이프사이클에서 설명한 것 처럼, “잡”은 오직 OnFailure 또는 Never와 같은 RestartPolicy를 사용하는 파드에만 적절하다.

단일 잡으로 컨트롤러 파드 시작

  • 다른 패턴은 단일 잡이 파드를 생성한 후 다른 파드들을 생성해서 해당 파드들에 일종의 사용자 정의 컨트롤러 역할을 하는 것이다.
  • 이것을 통해 최대한 유연성을 얻을 수 있지만, 시작하기에는 다소 복잡할 수 있으며 쿠버네티스와의 통합성이 낮아진다.
  • 이 패턴의 한 예시는 파드를 시작하는 잡이다. 파드는 스크립트를 실행해서 스파크(Spark) 마스터 컨트롤러를 시작하고, 스파크 드라이버를 실행한 다음, 정리한다.
  • 이러한 접근 방식의 장점은 전체 프로세스가 잡 오브젝트의 완료를 보장하면서도, 파드 생성과 직업 할당 방법을 완전히 제어하고 유지한다는 것이다.

완료된 잡 자동 정리

완료-이후-TTL 컨트롤러(이하 TTL 컨트롤러)

  • 완료-이후-TTL(TTL-after-finished) 컨트롤러는 실행이 완료된 리소스 오브젝트의 수명을 제한하는 TTL (time to live) 메커니즘을 제공하고 유일하고 잡(Job)만을 제어한다.
  • 클러스터 운영자는 .spec.ttlSecondsAfterFinished 필드를 명시하여 완료된 잡(완료 또는 실패)을 자동으로 정리하기 위해 이 기능을 사용할 수 있다.
  • 잡의 작업이 완료된 TTL 초(sec) 후, TTL 컨트롤러는 해당 잡이 정리될 수 있다고 가정하고, 잡을 정리할 때 잡을 연속적으로 삭제하고 동시에 의존하는 오브젝트도 해당 잡과 함께 삭제된다.
  • 잡이 삭제되면 완료자(finalizers)와 같은 라이프사이클 보증이 적용 된다.
  • TTL 초(sec)는 언제든지 설정이 가능하다. 아래는 .spec.ttlSecondsAfterFinished를 설정하는 몇가지 예시이다.
    • 작업이 완료된 다음, 일정 시간 후에 자동으로 잡이 정리될 수 있도록 잡 메니페스트에 이 필드를 지정한다.
    • 이미 완료된 기존 잡에 새 기능을 적용하기 위해서 이 필드를 설정한다.
    • 어드미션 웹후크 변형을 사용해서 잡 생성시 이 필드를 동적으로 설정한다. 클러스터 관리자는 이것을 사용해서 완료된 잡에 대해 TTL 정책을 적용할 수 있다.
    • 잡이 완료된 이후에 어드미션 웹 후크 변형을 사용해서 이 필드를 동적으로 설정하고, 잡의 상태나 레이블 등에 따라 다른 TTL 값을 선택한다.

TTL 초(sec) 업데이트

  • TTL 기간은, 잡의 .spec.ttlSecondsAfterFinished 필드는 잡을 생성하거나 완료한 후에 수정할 수 있다.
  • 하지만 잡을 삭제할 수 있게 되면(TTL이 만료된 경우) 시스템은 TTL을 연장하기 위한 업데이트가 성공적인 API 응답을 리턴하더라도 작업이 유지되도록 보장하지 않는다.

시간 차이(Skew)

  • TTL 컨트롤러는 쿠버네티스 잡에 저장된 타임스탬프를 사용해서 TTL의 만료 여부를 결정하기 때문에, 기능은 클러스터 간의 시간 차이에 민감하며, 시간 차이에 의해서 TTL 컨트롤러가 잘못된 시간에 잡 오브젝트를 정리하게 될 수 있다.
  • 시계가 항상 정확한 것은 아니지만, 그 차이는 가능한 작아야 한다.

[정리]

완료-이후-TTL 컨트롤러를 사용하여 잡의 수명을 제한할 수 있다. .spec.ttlSecondsAfterFinished 필드를 통해서 시간을 지정할 수 있으며 잡은 지정된 시간이 지나면 자동으로 삭제되게 된다. 해당 설정은 잡을 생성하거나 완료한 후에 설정은 할 수 있지만 이미 잡을 삭제할 수 있게 되었다면 작업이 유지되는 것을 보장하지 않는다. 또한 시간을 기준으로 작업이 이루어지기 때문에 시차 관리에 유의해야 한다.


참고 자료