오래 걸리는 Job 이 끝나기 전에 사라진다. 설치 작업이나 패키지 구성처럼 수십 분이 걸리는 Job 에서 자주 나타나며, 재실행해도 비슷한 시점에 다시 죽는다. Kubernetes 에서 Job 파드는 보호 대상이 아니다. 노드 drain, eviction, OOM, 데드라인 초과, 재배포 중 어느 것으로도 끝날 수 있으므로 먼저 종료 사유를 확정하고 그에 맞는 대처를 고른다.
kubectl describe pod <pod> -n <namespace>
kubectl get events -n <namespace> --sort-by=.lastTimestamp
kubectl get job <job> -n <namespace> -o yaml
describe 의 Last State 와 Reason 이 답을 준다.
| Reason | 뜻 | 볼 곳 |
|---|---|---|
OOMKilled |
컨테이너가 메모리 한도를 넘었다 | resources.limits.memory |
DeadlineExceeded |
Job 의 activeDeadlineSeconds 를 넘었다 |
Job 스펙 |
Evicted |
노드 자원 압박으로 kubelet 이 축출했다 | 노드 메모리·디스크 |
Error + 종료 코드 |
컨테이너가 스스로 실패로 끝났다 | 컨테이너 로그 |
BackoffLimitExceeded |
재시도 한도를 다 썼다 | Job 스펙 |
사유를 모르는 상태에서 설정을 바꾸면 증상만 옮겨 다닌다.
activeDeadlineSeconds 는 Job 전체의 제한 시간이다. 이 값을 넘으면 Job 은 실행 중이든 아니든 종료되고 실패로 표시되며, backoffLimit 에 재시도 여유가 남아 있어도 더 시도하지 않는다. 시간이 오래 걸리는 작업이면 값을 늘리거나 아예 빼야 한다.
backoffLimit 은 재시도 한도이고 기본값은 6 이다. 이 값을 넘기면 Job 이 실패로 끝난다. 다만 한도를 올리는 것은 실패 원인을 고치는 것이 아니라 실패 횟수를 늘려 주는 것뿐이므로, 같은 오류가 반복되면 한도보다 원인을 본다.
ttlSecondsAfterFinished 는 끝난 뒤 오브젝트를 지우는 값이다. 실행 중인 파드를 죽이지 않으므로 이 증상의 원인이 아니다. 다만 로그를 보기 전에 Job 이 사라져 "그냥 없어졌다" 로 보일 수는 있다.
apiVersion: batch/v1
kind: Job
metadata:
name: long-running-job
spec:
backoffLimit: 6
activeDeadlineSeconds: 21600
ttlSecondsAfterFinished: 86400
template:
spec:
restartPolicy: Never
containers:
- name: worker
image: registry.example.com/worker:1.0
resources:
requests:
cpu: "2"
memory: 4Gi
limits:
memory: 8Gi
OOMKilled 는 컨테이너가 limits.memory 를 넘어선 것이다. 한도를 올리는 것이 1차 대처이고, 그 전에 requests 를 실제 사용량에 가깝게 맞춰 스케줄러가 여유 있는 노드를 고르게 한다. 메모리 limit 을 아예 빼면 노드 전체를 위협하므로, 상한은 두되 실측에 맞춰 올리는 편이 낫다.
kubectl drain 은 파드를 축출한다. 복제본이 여럿인 워크로드는 PodDisruptionBudget 으로 동시 축출을 막지만, 파드가 하나뿐인 Job 에는 PDB 가 사실상 무의미하다. minAvailable: 1 을 걸면 그 파드는 축출되지 않는 대신 drain 이 끝나지 않고 멈춘다. 정비 창을 잡고 Job 이 끝난 뒤 drain 하는 편이 정직하다.
자원 압박에 의한 축출(eviction)은 우선순위로 완화한다. PriorityClass 값이 높은 파드가 나중에 축출된다.
kubectl create priorityclass high-priority-batch \
--value=100000 --global-default=false \
--description="장시간 배치 Job"
spec:
template:
spec:
priorityClassName: high-priority-batch
system-cluster-critical 같은 시스템 예약 클래스를 사용자 워크로드에 붙이는 것은 권하지 않는다. 클러스터 구성 요소보다 사용자 Job 이 먼저 살아남는 상태가 되어 노드 압박 때 클러스터 자체가 불안정해진다.
클러스터 오토스케일러가 도는 환경에서는 축소 대상 노드에 얹힌 Job 이 함께 사라진다. 이때는 파드에 다음 애너테이션을 달아 축소를 막는다.
metadata:
annotations:
cluster-autoscaler.kubernetes.io/safe-to-evict: "false"
Job 파드의 restartPolicy 는 Never 또는 OnFailure 만 허용한다. Always 는 쓸 수 없다.
| 값 | 실패했을 때 |
|---|---|
Never |
실패한 파드는 그대로 남고 새 파드가 만들어진다. 실패한 파드의 로그가 남아 원인 추적에 유리하다 |
OnFailure |
같은 파드 안에서 컨테이너만 다시 시작된다. 파드 수는 늘지 않지만 이전 시도의 로그가 덮인다 |
원인을 찾는 중이라면 Never 가 낫다.
"절대 종료되지 않는 Job" 은 Kubernetes 에 없다. 계속 살아 있어야 하는 프로세스라면 Job 이 아니라 Deployment 로 만드는 것이 맞다. Job 은 끝나는 일을, Deployment 는 끝나지 않는 일을 맡는 오브젝트다. 반대로 한 번 돌고 끝나야 할 작업을 Deployment 로 만들면 완료 후 계속 재시작되므로, 오브젝트 선택을 먼저 정리한다.
| 종료 사유 | 대처 |
|---|---|
OOMKilled |
limits.memory 상향, requests 실측 반영 |
DeadlineExceeded |
activeDeadlineSeconds 상향 또는 제거 |
Evicted |
PriorityClass 상향, 노드 여유 확보 |
| drain | 정비 창 조정. 단일 파드 Job 에 PDB 는 도움이 안 된다 |
| 오토스케일러 축소 | safe-to-evict: "false" 애너테이션 |
| 반복 실패 | backoffLimit 이 아니라 컨테이너 로그 |
| 끝나지 않아야 하는 작업 | Deployment 로 전환 |