Notice
Recent Posts
Recent Comments
Link
«   2025/12   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31
Archives
Today
Total
관리 메뉴

근묵자흑

Kubernetes Pattern: Batch Job 본문

k8s/kubernetes-pattern

Kubernetes Pattern: Batch Job

Luuuuu 2025. 9. 6. 17:30

매일 수백만 건의 로그를 처리하거나, 수천 개의 이미지를 변환하거나, 복잡한 ML 모델을 훈련시켜야 한다면?

쿠버네티스 Job은 이런 배치 작업을 위한 솔루션이 될 수 있습니다.

Kubernetes 1.31 버전에서 GA가 된 Pod Failure Policy와 1.30에서 도입된 Success Policy는 배치 작업의 기능 또한 관련 내용을추가했습니다.

Kubernetes Job이란?

쿠버네티스에서 파드(Pod)를 실행하는 방법은 여러 가지가 있습니다. ReplicaSet은 웹 서버처럼 계속 실행되어야 하는 애플리케이션에 적합하고, DaemonSet은 모든 노드에서 로그 수집기를 실행하는 데 사용됩니다.

그렇다면 한 번 실행하고 완료되는 작업은 어떻게 처리할까? 데이터베이스 마이그레이션, 배치 데이터 처리, 리포트 생성 같은 작업들 말입니다. 이것이 바로 Job 리소스가 필요한 이유입니다.

Job은 하나 이상의 파드를 생성하고, 지정된 수의 파드가 성공적으로 종료될 때까지 계속 재시도합니다. 파드가 실패하면 Job은 새로운 파드를 생성하여 작업을 완료하려고 시도합니다.

Job vs 일반 Pod

일반 Pod를 직접 생성하면 노드 장애 시 재시작되지 않습니다. 하지만 Job으로 생성된 Pod는 노드 장애가 발생해도 다른 노드에서 재시작됩니다. 또한 Job은 작업 완료 후 자동으로 정리될 수 있고, 병렬 처리를 지원하며, 진행 상황을 추적할 수 있습니다.

크론잡, 스크립트 대신 Kubernetes Job을 사용해야 하는 이유는 아래와 같습니다.

  • 자동 재시도와 복구: 실패한 작업을 자동으로 재시도
  • 병렬 처리: 수백 개의 작업을 동시에 실행
  • 리소스 관리: CPU와 메모리를 효율적으로 활용
  • 모니터링: 프로메테우스와 통합
  • 자동 정리: TTL로 완료된 Job 자동 삭제

1. 기본 배치 잡

Job의 가장 기본적인 형태를 살펴보겠습니다. 이 Job은 단일 작업을 한 번 실행하고 종료됩니다.

apiVersion: batch/v1
kind: Job
metadata:
  name: data-processor
spec:
  ttlSecondsAfterFinished: 3600  # 1시간 후 자동 삭제
  activeDeadlineSeconds: 1800     # 30분 타임아웃
  backoffLimit: 3                 # 3번 재시도
  template:
    spec:
      restartPolicy: Never
      containers:
      - name: processor
        image: myapp:latest
        command: ["python", "process_data.py"]
        resources:
          requests:
            memory: "1Gi"
            cpu: "500m"
          limits:
            memory: "2Gi"
            cpu: "1"

주요 설정 항목 설명

ttlSecondsAfterFinished: Job이 완료(성공 또는 실패)된 후 자동으로 삭제되기까지의 시간입니다. 이 설정이 없으면 완료된 Job이 계속 남아있어 리소스를 낭비하게 됩니다.

activeDeadlineSeconds: Job이 시작된 후 최대 실행 시간입니다. 이 시간을 초과하면 실행 중인 모든 Pod가 종료됩니다. 무한 루프나 데드락에 빠진 작업을 방지하는 안전장치입니다.

backoffLimit: Job이 실패로 간주되기 전까지 Pod를 재시도하는 횟수입니다. 기본값은 6입니다. 재시도 간격은 지수 백오프(10초, 20초, 40초...)로 증가합니다.

restartPolicy: Job의 Pod는 반드시 Never 또는 OnFailure로 설정해야 합니다. Always는 사용할 수 없습니다. Job은 종료되어야 하는 작업이기 때문입니다.

2. Indexed Job: 대규모 데이터 분산 처리

수십 개의 레코드를 처리해야 한다면? Indexed Job이 효율적입니다.

Indexed Job은 각 Pod에 0부터 시작하는 고유한 인덱스를 할당합니다. 이 인덱스를 통해 각 Pod가 처리할 데이터 범위를 결정할 수 있어, 외부 작업 큐 없이도 작업을 분산할 수 있습니다.

apiVersion: batch/v1
kind: Job
metadata:
  name: distributed-processor
spec:
  completions: 100        # 100개의 작업
  parallelism: 10         # 10개씩 동시 실행
  completionMode: Indexed # 각 Pod에 고유 인덱스 할당
  backoffLimitPerIndex: 2 # 인덱스별 재시도
  maxFailedIndexes: 5     # 5개까지 실패 허용
  template:
    spec:
      restartPolicy: Never
      containers:
      - name: worker
        image: data-processor:latest
        env:
        - name: JOB_INDEX
          valueFrom:
            fieldRef:
              fieldPath: metadata.annotations['batch.kubernetes.io/job-completion-index']

Indexed Job의 작동 원리

completions: 완료해야 할 총 작업 수입니다. Indexed 모드에서는 0부터 completions-1까지의 인덱스가 할당됩니다.

parallelism: 동시에 실행할 수 있는 최대 Pod 수입니다. 이 값을 조정하여 클러스터 리소스와 처리 속도의 균형을 맞출 수 있습니다.

completionMode: Indexed: 이 설정으로 각 Pod에 고유 인덱스가 할당됩니다. 인덱스는 환경 변수 JOB_COMPLETION_INDEX로 접근할 수 있습니다.

backoffLimitPerIndex: 각 인덱스별로 독립적인 재시도 횟수를 설정합니다. 특정 인덱스의 작업이 계속 실패해도 다른 인덱스는 영향받지 않습니다.

maxFailedIndexes: 허용할 수 있는 최대 실패 인덱스 수입니다. 이 수를 초과하면 전체 Job이 실패로 간주됩니다.

실제 사용 사례: 이미지 변환

10,000개의 이미지를 처리해야 한다고 가정해봅시다. 100개의 워커를 생성하고, 각 워커가 100개씩 처리하도록 설정할 수 있습니다.

spec:
  completions: 100
  parallelism: 20  # 동시에 20개 Pod 실행
  completionMode: Indexed

각 워커는 자신의 인덱스를 기반으로 처리할 이미지 범위를 계산합니다. 인덱스 0은 0-99번 이미지를, 인덱스 1은 100-199번 이미지를 처리하는 식입니다.

3. Pod Failure Policy: 에러 핸들링

여러 환경에 따라 다양한 실패가 발생할 수 있습니다. 네트워크 일시 장애로 인한 실패는 재시도하면 성공할 가능성이 있지만, 데이터 형식 오류는 몇 번을 재시도해도 실패할 것입니다.

Kubernetes 1.31의 Pod Failure Policy로 세밀한 제어 가능을 추가했습니다.

apiVersion: batch/v1
kind: Job
metadata:
  name: smart-job
spec:
  podFailurePolicy:
    rules:
    # 노드 장애는 무시 (다른 노드에서 재시도)
    - action: Ignore
      onPodConditions:
      - type: DisruptionTarget
        status: "True"

    # 데이터 오류는 즉시 실패 (재시도 무의미)
    - action: FailJob
      onExitCodes:
        containerName: processor
        operator: In
        values: [42]  # 데이터 오류 코드

    # 일시적 오류는 재시도
    - action: Count
      onExitCodes:
        operator: In
        values: [1, 2, 3]

Pod Failure Policy 액션 설명

Ignore: 해당 실패를 무시하고 backoffLimit에 포함하지 않습니다. 노드 유지보수나 스팟 인스턴스 회수 같은 인프라 문제에 사용합니다.

FailJob: 즉시 전체 Job을 실패로 처리합니다. 재시도해도 해결되지 않을 치명적 오류에 사용합니다.

Count: 일반적인 실패 처리입니다. backoffLimit에 포함되어 재시도됩니다.

FailIndex (Indexed Job 전용): 특정 인덱스만 실패로 처리하고 다른 인덱스는 계속 진행합니다.

실전 예제: 외부 API 호출 작업

외부 API를 호출하는 작업에서는 다양한 실패 상황이 발생할 수 있습니다.

  • 429 (Rate Limit): 잠시 대기 후 재시도
  • 500-599 (서버 오류): 재시도 가능
  • 404 (Not Found): 재시도 무의미, 즉시 실패
  • Connection Error: 네트워크 문제, 재시도

각 상황에 맞는 exit code를 반환하고, Pod Failure Policy에서 적절히 처리하도록 설정합니다.

4. Success Policy: 부분 성공 허용

모든 작업이 100% 성공해야만 하는 것은 아닙니다. 웹 크롤링이나 데이터 수집 작업에서는 일부 실패를 허용할 수 있습니다.

Kubernetes 1.30의 Success Policy로 유연한 성공 조건을 정의할 수 있습니다.

apiVersion: batch/v1
kind: Job
metadata:
  name: flexible-job
spec:
  completions: 1000
  parallelism: 50
  completionMode: Indexed
  successPolicy:
    rules:
    # 핵심 작업(0-9)은 반드시 성공
    - succeededIndexes: "0-9"
      succeededCount: 10

    # 나머지는 90% 성공률이면 OK
    - succeededIndexes: "10-999"
      succeededCount: 891  # 990 * 0.9

Success Policy 활용 시나리오

데이터 수집: 1000개 사이트에서 데이터를 수집할 때, 95% 이상 성공하면 충분한 경우

배치 이메일 발송: 10만 명에게 이메일을 발송할 때, 99% 성공률이면 허용 가능한 경우

이미지 처리: 썸네일 생성 작업에서 일부 실패는 나중에 재처리 가능한 경우

크롤링 작업

1000개 웹사이트를 크롤링할 때, 일부 사이트가 다운되어 있어도 전체 작업은 성공으로 처리:

successPolicy:
  rules:
  - succeededIndexes: "0-999"
    succeededCount: 950  # 95% 성공률이면 충분

이렇게 설정하면 50개 사이트가 실패해도 Job은 성공으로 완료됩니다. 실패한 사이트는 별도로 로그를 남기고 나중에 재시도할 수 있습니다.

비용 최적화

대규모 배치 작업은 스팟 인스턴스나 프리엠티블 노드에서 실행하여 비용을 절감할 수 있습니다. Pod Failure Policy의 DisruptionTarget 조건을 활용하면 노드 회수 시 자동으로 다른 노드에서 재시작됩니다.

Job 패턴 요약

Single Pod Job

  • 사용 시기: 단일 작업, 데이터베이스 마이그레이션
  • 설정: completions: 1, parallelism: 1

Fixed Completion Job

  • 사용 시기: 정확한 작업 수를 알 때
  • 설정: completions: N, parallelism: M

Work Queue Job

  • 사용 시기: 작업 큐 처리, 동적 작업량
  • 설정: parallelism: N (completions 생략)

Indexed Job

  • 사용 시기: 데이터 샤딩, 범위 기반 처리
  • 설정: completionMode: Indexed