IT/Kubernets

Kubernetes Job, backoffLimit만 쓰면 OOM 한 번에 재시도 6번이 따라온다

gfrog 2026. 5. 26. 18:45
반응형

 

오늘 알게 된 건데, 의외로 Job spec에서 podFailurePolicy 안 쓰는 팀이 꽤 많더라. 같이 일하는 분이 "야 우리 배치가 새벽에 6번 OOM 나고 죽었는데 알람이 한 번에 6번 왔어"라고 메시지를 보내서 들여다봤다. 코드는 멀쩡한데 메모리 한도가 빡빡했고, backoffLimit: 6만 박혀 있었다. 그게 다였다.

근데 이게 별거 아닌 것 같아도 비용/알람/멘탈 다 갉아먹는다. 잠깐만 짚고 가자.

backoffLimit만 있을 때 무슨 일이 벌어지나

Job spec이 이렇게만 돼 있다고 치자.

apiVersion: batch/v1
kind: Job
metadata:
  name: nightly-report
spec:
  backoffLimit: 6
  template:
    spec:
      restartPolicy: Never
      containers:
      - name: report
        image: reporter:1.4
        resources:
          limits:
            memory: 512Mi

이러면 컨테이너가 어떤 이유로 죽든 — OOMKill이든, 노드 preemption이든, ImagePullBackOff에서 끝까지 못 받아오든 — Pod 실패 카운터가 1씩 올라간다. 7번째 실패에서야 Job이 Failed 상태로 굳는다. 사실 OOM 같은 결정론적 실패는 한 번 보고 멈췄어야 했다. 메모리 부족하면 재시도해도 또 부족하다. 그게 정의다.

반대로 노드가 spot으로 evict 당해서 죽은 거라면 그건 인프라 사정이지 코드 사정이 아니다. 이런 건 카운터에서 빼주는 게 맞다. backoffLimit 하나로 이 둘을 구분할 방법이 없다.

podFailurePolicy로 쪼개기

K8s 1.31부터 GA된 podFailurePolicy 필드를 같이 박아주면 이렇게 분리할 수 있다.

spec:
  backoffLimit: 6
  podFailurePolicy:
    rules:
    - action: FailJob
      onExitCodes:
        operator: In
        values: [137]  # SIGKILL — OOM 포함
    - action: Ignore
      onPodConditions:
      - type: DisruptionTarget  # preemption/eviction
  template:
    spec:
      restartPolicy: Never
      ...

규칙은 두 줄이지만 의미는 크다. 첫째 규칙은 "OOM처럼 137로 죽으면 더 시도하지 말고 Job을 즉시 실패시켜라". 둘째는 "노드 disruption 때문에 죽었으면 카운터에서 빼라, 그건 네 잘못 아니다". 이 둘만 박아도 야간 알람이 절반으로 줄어든다. 진짜로.

조금 더 욕심내면 exit code 별로 동작을 다르게 줄 수도 있다. 예를 들어 1번은 코드 버그라 즉시 FailJob, 42번은 일시적 외부 API 장애라 Count(재시도)로 보내는 식.

헷갈리기 쉬운 포인트 하나

restartPolicy: OnFailure로 두면 podFailurePolicy 규칙이 평가되기 전에 컨테이너가 그냥 같은 Pod에서 재시작돼 버린다. 그러면 OOM이 카운트에도 안 잡힌다. 실제로는 죽고 살고를 반복하는데 Job은 멀쩡해 보인다. 더 무섭다. podFailurePolicy 쓰려면 무조건 restartPolicy: Never다. 이건 옵션이 아니라 요구사항.

다음에는 podReplacementPolicy 쪽도 한 번 짚어보려고 한다. Failed 상태 Pod가 Terminating 중인데 새 Pod가 동시에 떠서 동시 실행이 일어나는 케이스 — 이게 또 사람 잡는다.

반응형