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분)
restartPolicy는 Never(실패 시 새 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 설정이 안정적 운영의 핵심이다.