K8s Job·CronJob 배치 처리

Job과 CronJob이란?

Kubernetes의 Deployment는 항상 실행 중인 서비스를 위한 것이다. 반면 Job은 한 번 실행하고 완료되는 작업(배치 처리, 데이터 마이그레이션, ML 학습), CronJob은 주기적으로 반복 실행되는 작업(리포트 생성, 정리 작업, 백업)을 위한 리소스다. Pod이 성공적으로 완료될 때까지 보장하는 것이 핵심이다.

Job 기본 구성

apiVersion: batch/v1
kind: Job
metadata:
  name: db-migration
spec:
  template:
    spec:
      containers:
      - name: migrate
        image: my-app:latest
        command: ["npx", "typeorm", "migration:run"]
        env:
        - name: DB_HOST
          valueFrom:
            secretKeyRef:
              name: db-secret
              key: host
      restartPolicy: Never  # Job에서는 Never 또는 OnFailure만 허용
  backoffLimit: 3           # 실패 시 최대 재시도 횟수
  activeDeadlineSeconds: 600 # 전체 Job 타임아웃 (10분)

restartPolicyNever(실패 시 새 Pod 생성)와 OnFailure(같은 Pod에서 컨테이너 재시작) 중 선택한다. Always는 Job에서 사용할 수 없다.

병렬 처리

대량 데이터를 분할 처리할 때 Job의 병렬 실행 옵션을 사용한다.

apiVersion: batch/v1
kind: Job
metadata:
  name: batch-processor
spec:
  completions: 10     # 총 10개 작업 완료 필요
  parallelism: 3      # 동시 3개 Pod 실행
  completionMode: Indexed  # 각 Pod에 JOB_COMPLETION_INDEX 환경변수 부여
  template:
    spec:
      containers:
      - name: processor
        image: batch-worker:latest
        command: ["python", "process.py"]
        env:
        - name: BATCH_INDEX
          valueFrom:
            fieldRef:
              fieldPath: metadata.annotations['batch.kubernetes.io/job-completion-index']
      restartPolicy: OnFailure
설정 동작
completions=1, parallelism=1 단일 실행 (기본값)
completions=N, parallelism=M N개 작업을 M개씩 병렬 처리
completions=미설정, parallelism=M 작업 큐 패턴 (Pod이 스스로 종료 판단)

Indexed 모드에서는 각 Pod에 0부터 시작하는 인덱스가 부여된다. 데이터를 인덱스별로 분할 처리하는 패턴에 유용하다.

CronJob 설정

apiVersion: batch/v1
kind: CronJob
metadata:
  name: daily-report
spec:
  schedule: "0 2 * * *"        # 매일 새벽 2시 (UTC)
  timeZone: "Asia/Seoul"       # K8s 1.27+ 타임존 지원
  concurrencyPolicy: Forbid    # 이전 Job이 실행 중이면 새 Job 스킵
  successfulJobsHistoryLimit: 3
  failedJobsHistoryLimit: 5
  startingDeadlineSeconds: 300  # 스케줄 시간 후 5분 내 시작 못 하면 스킵
  suspend: false                # true로 설정하면 일시 중지
  jobTemplate:
    spec:
      backoffLimit: 2
      activeDeadlineSeconds: 3600
      template:
        spec:
          containers:
          - name: reporter
            image: report-generator:latest
            resources:
              requests:
                cpu: 500m
                memory: 512Mi
              limits:
                cpu: "1"
                memory: 1Gi
          restartPolicy: OnFailure

Cron 스케줄 표현식

# 분 시 일 월 요일
"0 2 * * *"         # 매일 02:00
"*/15 * * * *"      # 15분마다
"0 9 * * 1-5"       # 평일 09:00
"0 0 1 * *"         # 매월 1일 자정
"0 */6 * * *"       # 6시간마다

concurrencyPolicy

이전 Job이 아직 실행 중일 때 새 Job을 어떻게 처리할지 결정한다.

정책 동작 추천 상황
Allow 동시 실행 허용 (기본값) 독립적인 작업
Forbid 새 Job 스킵 DB 작업, 중복 방지 필수
Replace 기존 Job 종료 후 새 Job 실행 항상 최신 실행이 중요

실무 권장: 대부분의 배치 작업은 Forbid가 안전하다. 동일 작업이 동시에 실행되면 데이터 정합성 문제가 발생할 수 있다.

실패 처리 전략

apiVersion: batch/v1
kind: Job
metadata:
  name: critical-batch
spec:
  backoffLimit: 5              # 최대 5번 재시도
  activeDeadlineSeconds: 1800  # 30분 전체 타임아웃
  ttlSecondsAfterFinished: 86400  # 완료 후 24시간 뒤 자동 삭제
  podFailurePolicy:            # K8s 1.26+ Pod 실패 정책
    rules:
    - action: FailJob           # OOM이면 재시도 없이 즉시 실패
      onExitCodes:
        containerName: worker
        operator: In
        values: [137]           # OOMKilled exit code
    - action: Ignore            # 특정 exit code는 무시
      onExitCodes:
        containerName: worker
        operator: In
        values: [42]
    - action: Count             # 나머지는 backoffLimit 카운트
      onExitCodes:
        containerName: worker
        operator: NotIn
        values: [137, 42]
  template:
    spec:
      containers:
      - name: worker
        image: batch-worker:latest
      restartPolicy: Never

podFailurePolicy(K8s 1.26+)를 사용하면 exit code별로 다른 실패 처리가 가능하다. OOM은 재시도해도 의미 없으므로 즉시 실패 처리하고, 일시적 네트워크 오류는 재시도하는 식이다.

Init Container 활용

Job 실행 전 의존성 확인이나 데이터 준비를 Init Container로 처리한다.

spec:
  template:
    spec:
      initContainers:
      # DB 연결 대기
      - name: wait-for-db
        image: busybox:1.36
        command: ['sh', '-c',
          'until nc -z mysql-service 3306; do echo waiting; sleep 2; done']
      # 설정 파일 다운로드
      - name: fetch-config
        image: curlimages/curl:latest
        command: ['curl', '-o', '/config/settings.json',
          'https://config-server/batch-settings']
        volumeMounts:
        - name: config
          mountPath: /config
      containers:
      - name: batch
        image: batch-worker:latest
        volumeMounts:
        - name: config
          mountPath: /config
      volumes:
      - name: config
        emptyDir: {}

모니터링과 알림

# Job 상태 확인
kubectl get jobs -w
kubectl describe job db-migration

# CronJob 이력
kubectl get cronjobs
kubectl get jobs --selector=job-name=daily-report

# 실패한 Job의 Pod 로그
kubectl logs job/db-migration

# 모든 실패 Job 조회
kubectl get jobs --field-selector status.successful=0

Prometheus로 Job 메트릭을 수집하면 실패율 알림을 자동화할 수 있다.

# Prometheus 알림 규칙 예시
- alert: CronJobFailed
  expr: kube_job_status_failed{job_name=~"daily-report.*"} > 0
  for: 5m
  labels:
    severity: critical
  annotations:
    summary: "CronJob {{ $labels.job_name }} failed"

실전 패턴

DB 마이그레이션 Job (배포 전 실행)

# Helm hook으로 배포 전 자동 실행
apiVersion: batch/v1
kind: Job
metadata:
  name: pre-deploy-migration
  annotations:
    "helm.sh/hook": pre-upgrade
    "helm.sh/hook-weight": "-1"
    "helm.sh/hook-delete-policy": hook-succeeded
spec:
  backoffLimit: 0  # 마이그레이션 실패 시 배포 중단
  template:
    spec:
      containers:
      - name: migrate
        image: {{ .Values.image.repository }}:{{ .Values.image.tag }}
        command: ["npx", "typeorm", "migration:run"]
      restartPolicy: Never

Helm Hook과 함께 사용하면 배포 파이프라인에 마이그레이션을 자연스럽게 통합할 수 있다.

대용량 데이터 정리 CronJob

apiVersion: batch/v1
kind: CronJob
metadata:
  name: cleanup-old-data
spec:
  schedule: "0 3 * * 0"   # 매주 일요일 03:00
  timeZone: "Asia/Seoul"
  concurrencyPolicy: Forbid
  jobTemplate:
    spec:
      backoffLimit: 1
      activeDeadlineSeconds: 7200
      template:
        spec:
          containers:
          - name: cleaner
            image: cleanup-worker:latest
            command: ["python", "cleanup.py", "--days=90"]
            resources:
              requests:
                cpu: "1"
                memory: 2Gi
          restartPolicy: OnFailure

정리

설정 핵심
backoffLimit 재시도 횟수 제한
activeDeadlineSeconds 전체 타임아웃
concurrencyPolicy 중복 실행 방지
ttlSecondsAfterFinished 완료 Job 자동 정리
podFailurePolicy exit code별 실패 처리

Job과 CronJob은 배치 처리의 완료 보장, 재시도, 병렬화, 스케줄링을 K8s 레벨에서 해결한다. concurrencyPolicy: Forbid와 적절한 backoffLimit 설정이 안정적 운영의 핵심이다.

위로 스크롤
WordPress Appliance - Powered by TurnKey Linux