Certified Kubernetes Application Developer (CKAD) #7 Workloads 3: Job, CronJob (백오프, 동시성)

#5에서 Deployment로 무중단 배포를 익히고 #6에서 DaemonSet과 StatefulSet으로 노드 단위,상태 보존 워크로드를 다뤘다면, 이번에는 성격이 전혀 다른 워크로드를 봅니다. Deployment가 계속 떠 있어야 하는 서비스를 위한 것이라면, Job은 한 번 실행하고 끝나는 작업을 위한 것입니다. 데이터 마이그레이션, 백업, 배치 연산처럼 완료되면 그만인 일이 여기에 해당합니다.

서비스용 Pod가 죽으면 다시 살려야 하지만, 배치 작업 Pod는 성공적으로 끝나면 더 이상 재시작하면 안 됩니다. 이 “끝남"을 다루는 것이 Job의 핵심입니다. 그리고 그 Job을 cron 표기로 주기 실행하는 것이 CronJob 입니다. 시험에서는 backoffLimit과 concurrencyPolicy가 특히 단골이므로, 이번 글에서 YAML과 kubectl 양쪽으로 손에 익히겠습니다.

Job: 한 번 실행하고 끝나는 작업 #

Job은 하나 이상의 Pod를 만들어 지정한 횟수만큼 성공적으로 완료될 때까지 보장하는 워크로드입니다. Deployment가 항상 N 개의 Pod를 살려 두는 것과 달리, Job은 정해진 작업이 끝나면 Pod를 더 만들지 않고 멈춥니다.

가장 단순한 Job을 generator로 뽑아 보겠습니다.

k create job pi --image=perl:5.34 \
  $do -- perl -Mbignum=bpi -wle 'print bpi(2000)' > job.yaml

$do#1에서 정의한 --dry-run=client -o yaml입니다. 위 명령이 만든 뼈대는 다음과 같습니다.

apiVersion: batch/v1
kind: Job
metadata:
  name: pi
spec:
  template:
    spec:
      containers:
        - name: pi
          image: perl:5.34
          command: ["perl", "-Mbignum=bpi", "-wle", "print bpi(2000)"]
      restartPolicy: Never

여기서 Job은 apiVersion: batch/v1이라는 점, 그리고 Pod 템플릿의 restartPolicyNever라는 점이 Deployment와 다릅니다.

restartPolicy: Job에서는 Always가 금지된다 #

Job의 Pod 템플릿에는 restartPolicyOnFailure 또는 Never만 쓸 수 있습니다. Deployment와 달리 Always는 허용되지 않습니다. 작업이 끝나면 다시 돌리지 않아야 하기 때문입니다.

동작
Never실패한 Pod를 재시작하지 않고, Job이 새 Pod 를 만들어 재시도
OnFailure같은 Pod 안에서 컨테이너를 재시작해 재시도

Never는 실패할 때마다 Pod가 새로 쌓여 디버깅 로그가 남는 장점이 있고, OnFailure는 Pod 수를 늘리지 않는 장점이 있습니다. 시험에서는 문제 요구를 그대로 따르면 됩니다.

completions와 parallelism #

Job의 동작은 두 필드로 결정됩니다.

필드의미기본값
completions성공으로 간주하려면 몇 번 완료되어야 하는가1
parallelism동시에 몇 개의 Pod를 실행할 것인가1

예를 들어 completions: 6, parallelism: 2이면, Job은 한 번에 두 개씩 Pod를 돌려 총 여섯 번의 성공을 채울 때까지 진행합니다.

backoffLimit: 재시도 한도 #

backoffLimit은 Job이 실패를 몇 번까지 재시도할지를 정하는 한도입니다. 이 횟수를 넘기면 Job은 Failed로 표시되고 더 이상 새 Pod를 만들지 않습니다. 기본값은 6 입니다.

spec:
  backoffLimit: 4

재시도 사이에는 점점 길어지는 지연(exponential backoff)이 적용됩니다. 무한히 실패하는 작업이 클러스터 자원을 갉아먹지 않도록 막는 안전장치입니다. 시험에서 “최대 N 번까지만 재시도하라"는 요구가 나오면 이 필드를 잡으면 됩니다.

activeDeadlineSeconds: 시간 제한 #

activeDeadlineSeconds는 Job이 시작된 뒤 이 초를 넘기면 강제로 종료시키는 시간 제한입니다. backoffLimit이 횟수 기준이라면 이쪽은 시간 기준입니다. 둘 중 먼저 도달하는 조건이 적용됩니다.

spec:
  activeDeadlineSeconds: 100

시간을 넘겨 종료되면 Job은 DeadlineExceeded 사유로 실패 처리됩니다.

ttlSecondsAfterFinished: 자동 정리 #

완료되거나 실패한 Job은 기본적으로 클러스터에 그대로 남습니다. ttlSecondsAfterFinished를 설정하면 종료된 뒤 지정한 초가 지났을 때 Job과 그 Pod가 자동으로 삭제됩니다.

spec:
  ttlSecondsAfterFinished: 60

배치 작업이 끝난 뒤 오래된 Job이 쌓이는 것을 막는 용도이며, 0으로 두면 완료 즉시 삭제됩니다.

병렬 실행 패턴 #

Job은 completions와 parallelism의 조합으로 세 가지 대표 패턴을 만듭니다.

패턴설정쓰임
단일 작업completions 미지정, parallelism 미지정한 번 돌고 끝나는 단발 작업
고정 완료 수completions: N (병렬 선택)N 개의 독립 항목을 처리
작업 큐parallelism: M, completions 미지정워커가 큐에서 항목을 꺼내 처리하고 큐가 비면 종료

작업 큐 패턴에서는 각 Pod가 외부 큐를 보고 스스로 일을 가져가므로 completions를 비워 두고 parallelism으로 워커 수만 정합니다.

고정 완료 수 + 병렬 Job 예제 #

여섯 번의 완료를 한 번에 세 개씩 처리하고, 재시도는 두 번까지만 허용하는 Job 입니다.

apiVersion: batch/v1
kind: Job
metadata:
  name: batch-import
spec:
  completions: 6
  parallelism: 3
  backoffLimit: 2
  activeDeadlineSeconds: 120
  ttlSecondsAfterFinished: 120
  template:
    spec:
      restartPolicy: Never
      containers:
        - name: importer
          image: busybox:1.36
          command: ["sh", "-c", "echo importing && sleep 5"]

이 Job은 동시에 세 개의 Pod를 돌려 총 여섯 번의 성공을 채우고, 두 번을 초과해 실패하면 멈추며, 120 초를 넘기면 강제 종료되고, 끝난 뒤 120 초가 지나면 자동으로 정리됩니다.

CronJob: Job을 주기적으로 돌린다 #

CronJob은 정해진 일정에 따라 Job을 만들어 내는 워크로드입니다. Job이 일회성이라면 CronJob은 그것을 반복합니다. 백업을 매일 새벽에 돌리거나, 리포트를 5 분마다 생성하는 작업이 여기에 해당합니다.

generator로 뼈대를 만들어 보겠습니다.

k create cronjob report \
  --image=busybox:1.36 \
  --schedule="*/5 * * * *" \
  $do -- /bin/sh -c 'date; echo report' > cronjob.yaml
apiVersion: batch/v1
kind: CronJob
metadata:
  name: report
spec:
  schedule: "*/5 * * * *"
  jobTemplate:
    spec:
      template:
        spec:
          restartPolicy: OnFailure
          containers:
            - name: report
              image: busybox:1.36
              command: ["/bin/sh", "-c", "date; echo report"]

CronJob은 spec.jobTemplate 아래에 Job의 spec을 그대로 품고, 그 안에 다시 Pod 템플릿이 들어가는 3 단 중첩 구조입니다. 시험에서 들여쓰기를 가장 틀리기 쉬운 리소스이므로 generator로 뽑는 편이 안전합니다.

schedule: cron 표기 #

schedule은 표준 cron 다섯 자리 표기를 씁니다.

┌── 분 (0〜59)
│ ┌── 시 (0〜23)
│ │ ┌── 일 (1〜31)
│ │ │ ┌── 월 (1〜12)
│ │ │ │ ┌── 요일 (0〜6, 0=일요일)
│ │ │ │ │
* * * * *
표기의미
*/5 * * * *5 분마다
0 * * * *매시 정각
0 2 * * *매일 새벽 2 시
0 0 * * 0매주 일요일 자정

concurrencyPolicy: 동시성 정책 #

이전 실행이 아직 끝나지 않았는데 다음 일정이 도래하면 어떻게 할지를 정하는 것이 concurrencyPolicy입니다. 시험 단골이므로 세 값의 차이를 정확히 외워 두겠습니다.

동작
Allow (기본)동시 실행을 허용. 이전 Job이 돌고 있어도 새 Job을 생성
Forbid이전 Job이 끝나지 않았으면 새 일정을 건너뜀
Replace이전 Job을 취소하고 새 Job으로 교체

겹치면 안 되는 백업 작업은 Forbid, 최신 실행만 의미가 있는 작업은 Replace가 적절합니다.

startingDeadlineSeconds: 시작 마감 시한 #

컨트롤러가 멈춰 있었거나 노드 문제로 예정된 시각에 Job을 시작하지 못했을 때, 이 초 안에라면 늦게라도 시작하고 그 시한을 넘기면 해당 실행을 건너뛰게 하는 필드입니다.

spec:
  startingDeadlineSeconds: 30

suspend: 일시 중지 #

suspend: true로 두면 CronJob이 새 Job을 만들지 않고 멈춥니다. 점검 중에 주기 작업을 잠깐 꺼 둘 때 씁니다. 이미 돌고 있는 Job에는 영향을 주지 않습니다.

# 일시 중지
k patch cronjob report -p '{"spec":{"suspend":true}}'

# 재개
k patch cronjob report -p '{"spec":{"suspend":false}}'

히스토리 한도 #

CronJob은 끝난 Job을 일정 개수만큼 보관합니다.

필드의미기본값
successfulJobsHistoryLimit성공한 Job을 몇 개까지 남길지3
failedJobsHistoryLimit실패한 Job을 몇 개까지 남길지1

오래된 Job이 무한히 쌓이지 않도록 막는 설정이며, 디버깅을 위해 더 많은 이력을 남기려면 값을 올립니다.

concurrencyPolicy를 적용한 CronJob 예제 #

겹치는 실행을 막고, 늦은 시작을 30 초까지만 허용하며, 이력을 줄여 둔 백업 CronJob 입니다.

apiVersion: batch/v1
kind: CronJob
metadata:
  name: db-backup
spec:
  schedule: "0 2 * * *"
  concurrencyPolicy: Forbid
  startingDeadlineSeconds: 30
  successfulJobsHistoryLimit: 5
  failedJobsHistoryLimit: 2
  jobTemplate:
    spec:
      backoffLimit: 3
      template:
        spec:
          restartPolicy: OnFailure
          containers:
            - name: backup
              image: busybox:1.36
              command: ["/bin/sh", "-c", "echo backing up && sleep 10"]

매일 새벽 2 시에 돌되 이전 백업이 끝나지 않았으면 이번 실행은 건너뛰고, jobTemplate 안의 backoffLimit으로 실패 시 세 번까지 재시도합니다.

실습: Job과 CronJob 다루기 #

Job만들고 로그 확인 #

# Job 생성
k apply -f job.yaml

# 상태 확인 (COMPLETIONS 열이 6/6이 되면 완료)
k get job batch-import

# Job이 만든 Pod 보기
k get pods -l job-name=batch-import

# 로그 확인 (Job 이름으로 한 Pod의 로그를 본다)
k logs job/batch-import

# 자세한 진행 상황과 이벤트
k describe job batch-import

k get jobCOMPLETIONS 열은 성공/목표 형식으로 진행도를 보여 줍니다. DURATIONAGE로 소요 시간도 확인할 수 있습니다.

CronJob 다루기와 수동 트리거 #

CronJob은 일정이 도래해야 Job을 만들기 때문에, 시험에서 동작을 즉시 확인하려면 수동으로 트리거합니다.

# CronJob 생성
k apply -f cronjob.yaml

# 등록 확인 (SCHEDULE,LAST SCHEDULE,ACTIVE 열)
k get cronjob db-backup

# 일정을 기다리지 않고 지금 한 번 실행 (CronJob으로부터 Job 생성)
k create job manual-run --from=cronjob/db-backup

# 수동 실행한 Job의 로그
k logs job/manual-run

# CronJob이 만든 Job 목록
k get jobs

k create job <이름> --from=cronjob/<CronJob 이름>은 CronJob의 jobTemplate을 그대로 복제해 Job을 즉시 생성합니다. 채점 전에 작업이 제대로 도는지 확인하는 데 가장 빠른 방법입니다.

시험 포인트 #

  • Job은 batch/v1. apiVersion을 틀리면 리소스가 만들어지지 않습니다. generator로 뽑으면 자동으로 맞습니다.
  • restartPolicy는 Never 또는 OnFailure. Job과 CronJob의 Pod 템플릿에 Always를 쓰면 거부됩니다.
  • backoffLimit은 단골. “최대 N 번 재시도” 요구가 나오면 이 필드입니다. 기본값 6을 기억하면 빠릅니다.
  • concurrencyPolicy 세 값. Allow(기본),Forbid(건너뜀),Replace(교체)의 차이를 정확히 구분하는 문제가 자주 나옵니다.
  • CronJob은 3 단 중첩. jobTemplate.spec.template.spec까지 들어가는 구조라 들여쓰기 사고가 잦습니다. generator로 뼈대를 만든 뒤 필드만 고치는 편이 안전합니다.
  • 수동 트리거. CronJob을 즉시 검증하려면 k create job --from=cronjob/<이름>을 씁니다.
  • completions와 parallelism의 조합으로 단일,고정,큐 패턴이 나뉩니다. 둘 다 기본값은 1 입니다.

정리 #

이번 글에서 잡은 것:

  • Job은 한 번 실행하고 끝나는 배치 워크로드. completions로 목표 완료 수를, parallelism으로 동시 실행 수를 정합니다.
  • 재시도와 한도. backoffLimit(횟수),activeDeadlineSeconds(시간)으로 실패를 제어하고, ttlSecondsAfterFinished로 끝난 Job을 자동 정리합니다.
  • restartPolicy는 Never 또는 OnFailure. Job에서 Always는 쓸 수 없습니다.
  • CronJob은 Job을 cron 일정으로 반복. schedule,concurrencyPolicy,startingDeadlineSeconds,suspend,히스토리 한도로 동작을 다듬습니다.
  • 실습. Job은 k logs job/<이름>으로, CronJob은 k create job --from=cronjob/<이름>으로 수동 트리거해 검증합니다.

배치 워크로드의 개념을 더 넓게 잡고 싶다면 쿠버네티스 중급 시리즈도 함께 보면 좋습니다.

다음: Deployment 전략 #

워크로드 3 종(Job,CronJob)까지 정리하며 #5〜#7의 워크로드 묶음을 마쳤습니다. 다음은 그 워크로드를 어떻게 교체하느냐의 문제로 돌아갑니다.

#8 Deployment 전략: Blue-green, canary에서는 한 버전을 통째로 옮기는 blue-green 배포와, 일부 트래픽만 새 버전으로 흘려 보내는 canary 배포를 다루겠습니다. Deployment와 Service, 그리고 레이블 셀렉터를 조합해 시험에서 직접 두 전략을 구현해 보며 정리하겠습니다.

X