Published on

K8s CrashLoopBackOff 원인 9가지와 즉시 복구

Authors

CrashLoopBackOff는 쿠버네티스가 컨테이너를 실행했지만 프로세스가 빠르게 종료되어 재시작을 반복하고, 결국 재시작 간격을 점점 늘리는 백오프(back-off) 상태를 의미합니다. 핵심은 왜 프로세스가 죽는지를 빠르게 좁히는 것입니다.

운영에서 중요한 포인트는 두 가지입니다.

  • 즉시 복구(응급 처치): 트래픽을 살리고 장애를 확산시키지 않는 조치
  • 근본 원인 제거(RCA): 재발 방지용 수정과 검증

아래는 현장에서 가장 흔한 9가지 원인을 우선순위 높은 순서로 정리하고, 각 케이스마다 확인 방법즉시 복구를 함께 제공합니다.

0) 먼저 해야 할 3분 트리아지 체크리스트

CrashLoopBackOff를 보면 바로 아래 순서로 증거를 모읍니다.

1) 이벤트와 종료 사유부터 확인

# 네임스페이스/파드 지정
NS=prod
POD=myapp-7c9d6f8b7d-abcde

kubectl -n "$NS" describe pod "$POD"

Events 섹션에 Back-off restarting failed container가 보이고, Last StateReasonExit Code가 찍힙니다.

2) 직전 크래시 로그를 본다

# 현재 컨테이너 로그
kubectl -n "$NS" logs "$POD" -c myapp

# 직전 재시작(previous) 로그가 핵심
kubectl -n "$NS" logs "$POD" -c myapp --previous

3) 재시작 횟수와 상태를 한 번에 본다

kubectl -n "$NS" get pod "$POD" -o wide
kubectl -n "$NS" get pod "$POD" -o jsonpath='{.status.containerStatuses[0].restartCount}{"\n"}'

여기까지가 기본입니다. 이제 원인별로 들어갑니다.

1) 애플리케이션 즉시 종료(Exit Code 1 등)

가장 흔한 케이스입니다. 프로세스가 설정 누락, 필수 환경변수 없음, 외부 의존성 실패 등으로 시작 직후 exit(1)로 죽습니다.

확인 방법

  • kubectl logs --previous에서 스택트레이스나 missing env 같은 메시지
  • describe podLast State: TerminatedExit Code: 1

즉시 복구

  • 롤백이 최우선: 직전 정상 버전으로 되돌려 서비스 복구
kubectl -n "$NS" rollout undo deploy/myapp
kubectl -n "$NS" rollout status deploy/myapp
  • 설정 누락이면 ConfigMap 또는 Secret을 빠르게 주입 후 재배포
kubectl -n "$NS" set env deploy/myapp REQUIRED_FLAG=true
kubectl -n "$NS" rollout restart deploy/myapp

재발 방지

  • 시작 시 필수 설정 검증 로직을 명확한 에러 메시지로 출력
  • readinessProbe로 트래픽 유입 전 검증

2) OOMKilled: 메모리 부족으로 커널이 강제 종료

컨테이너가 메모리를 초과하면 커널 OOM killer가 프로세스를 죽이고, 쿠버네티스는 재시작을 반복합니다.

확인 방법

kubectl -n "$NS" describe pod "$POD" | sed -n '1,200p'
# Last State에 Reason: OOMKilled

kubectl -n "$NS" top pod "$POD"

즉시 복구

  • 임시로 메모리 limit을 올려서 생존시킨 뒤 원인 분석
kubectl -n "$NS" set resources deploy/myapp \
  -c myapp --limits=memory=1024Mi --requests=memory=512Mi
kubectl -n "$NS" rollout restart deploy/myapp
  • 트래픽 급증이 원인이면 레플리카 수를 늘려 분산
kubectl -n "$NS" scale deploy/myapp --replicas=6

재발 방지

  • heap 기반 런타임은 heap 상한을 limit에 맞춤
  • 메모리 폭증 구간에 대한 프로파일링과 캐시 정책 점검

3) livenessProbe 오탐지로 강제 재시작

애플리케이션은 살아있는데 livenessProbe가 실패해서 kubelet이 컨테이너를 죽입니다. 특히 부팅이 느린 서비스, GC가 긴 서비스에서 흔합니다.

확인 방법

  • describe pod 이벤트에 Liveness probe failed 반복
  • 로그에는 정상인데 재시작만 증가

즉시 복구

  • 부팅이 느리면 startupProbe를 추가하거나 liveness 임계치를 완화
# 예시 (Deployment 일부)
startupProbe:
  httpGet:
    path: /health
    port: 8080
  failureThreshold: 30
  periodSeconds: 2

livenessProbe:
  httpGet:
    path: /health
    port: 8080
  initialDelaySeconds: 30
  timeoutSeconds: 2
  periodSeconds: 10
  failureThreshold: 3
  • 즉시 복구가 목적이면, 임시로 liveness를 제거하고 재배포 후 원인 분석

재발 방지

  • readinessProbelivenessProbe의 목적을 분리
  • 외부 의존성(DB, API) 체크를 liveness에 넣지 않기

4) CrashLoopBackOff처럼 보이는 ImagePull 계열 혼동

엄밀히 말하면 ImagePullBackOff는 다른 상태지만, 현장에서는 “계속 안 뜬다”로 묶여 대응이 늦어집니다. 이미지 태그 오타, 레지스트리 인증 실패가 원인입니다.

확인 방법

kubectl -n "$NS" describe pod "$POD" | sed -n '1,200p'
# Events에 Failed to pull image, ErrImagePull

즉시 복구

  • 태그/레포 경로 수정, imagePullSecret 확인
kubectl -n "$NS" get secret
kubectl -n "$NS" patch serviceaccount default \
  -p '{"imagePullSecrets": [{"name": "regcred"}]}'
  • 이미지를 긴급히 이전 태그로 롤백
kubectl -n "$NS" set image deploy/myapp myapp=myrepo/myapp:previous
kubectl -n "$NS" rollout status deploy/myapp

5) ConfigMap 또는 Secret 마운트 실패/키 누락

환경변수나 파일 마운트가 실패하면 앱이 즉시 종료하거나, 컨테이너 자체가 시작하지 못합니다.

확인 방법

  • describe pod 이벤트에 configmap not found, secret not found
  • 앱 로그에 No such file 또는 설정 파싱 실패

즉시 복구

  • 누락된 리소스를 생성하거나 이름을 맞춤
kubectl -n "$NS" get cm,secret | grep myapp

# 예: 급한 대로 빈 키라도 생성해 부팅시키고 이후 수정
kubectl -n "$NS" create secret generic myapp-secret \
  --from-literal=DB_PASSWORD=temporary

kubectl -n "$NS" rollout restart deploy/myapp

재발 방지

  • 배포 파이프라인에서 kubectl apply --dry-run=server로 사전 검증
  • 차트/매니페스트에서 이름 하드코딩 최소화

6) 권한 문제: 파일/디렉터리 접근 실패(특히 PV)

컨테이너가 루트가 아니거나, PV의 소유권이 맞지 않으면 로그 디렉터리 생성, 소켓 생성, PID 파일 생성에 실패하면서 앱이 종료할 수 있습니다.

확인 방법

  • 로그에 Permission denied
  • PV 마운트 경로에 쓰기 실패
kubectl -n "$NS" exec -it "$POD" -c myapp -- sh -lc 'id; ls -al /data; touch /data/test'

즉시 복구

  • securityContextrunAsUser, fsGroup 조정
securityContext:
  runAsNonRoot: true
  runAsUser: 1000
  fsGroup: 1000
  • initContainer로 퍼미션 선조정
initContainers:
  - name: fix-permission
    image: busybox:1.36
    command: ["sh", "-lc", "chown -R 1000:1000 /data"]
    volumeMounts:
      - name: data
        mountPath: /data

리눅스 권한/보안 정책 이슈는 컨테이너 밖에서도 자주 터집니다. 운영 서버에서 로그 파일 권한 때문에 작업이 멈춘 사례는 이 글의 체크리스트가 유사한 사고에 도움이 됩니다: 리눅스 logrotate가 안 돎? 권한·SELinux 점검

7) 의존 서비스 연결 실패로 부팅 중단(DB, 메시지 큐 등)

앱이 시작 시 DB 연결을 강제하고, 실패하면 즉시 종료하는 패턴에서 CrashLoopBackOff가 발생합니다. DB 장애, DNS 문제, 네트워크 정책, 인증서 만료 등이 원인이 됩니다.

확인 방법

  • 로그에 connection refused, timeout, authentication failed
  • 파드 내부에서 DNS 및 포트 체크
kubectl -n "$NS" exec -it "$POD" -c myapp -- sh -lc 'cat /etc/resolv.conf; nslookup mysql.default.svc.cluster.local'
kubectl -n "$NS" exec -it "$POD" -c myapp -- sh -lc 'nc -vz mysql 3306 || true'

즉시 복구

  • 앱을 degraded mode로 부팅하도록 플래그를 주거나, 재시도(backoff)를 앱 레벨에서 하도록 변경
  • DB가 병목이라면 커넥션 풀/쿼리 문제를 함께 의심

DB 락/데드락이 원인으로 연결 실패 또는 지연이 누적되는 경우도 있습니다. MySQL 기반이라면 아래 글의 “재현과 로그 기반 원인 추적” 방식이 그대로 응용됩니다: MySQL Deadlock 1213 재현·로그·인덱스로 해결

8) CPU throttling 또는 과도한 기동 부하로 타임아웃 연쇄

CPU limit이 너무 낮으면 부팅이 느려지고, probe 타임아웃으로 재시작이 반복되면서 CrashLoopBackOff로 이어질 수 있습니다. 또는 JVM 워밍업, 마이그레이션 작업이 길어지는 경우도 포함됩니다.

확인 방법

  • kubectl top pod에서 CPU가 limit에 붙어있음
  • probe 실패 이벤트가 동반

즉시 복구

  • CPU 요청/제한을 올리고, startupProbe로 기동 시간을 벌어줌
kubectl -n "$NS" set resources deploy/myapp \
  -c myapp --requests=cpu=500m --limits=cpu=1500m
kubectl -n "$NS" rollout restart deploy/myapp

재발 방지

  • 기동 시 무거운 작업을 분리(예: 마이그레이션 Job)
  • 오토스케일링 정책이 과도하게 흔들리면 안정화 필요

트래픽 변동으로 HPA가 요동치며 기동/종료가 반복되는 환경이라면 큐 기반으로 안정화하는 접근이 도움이 됩니다: EKS HPA 폭주를 KEDA 큐기반 오토스케일링으로 안정화

9) 잘못된 엔트리포인트/커맨드, 또는 실행 파일 누락

이미지는 빌드됐지만 CMD 또는 ENTRYPOINT가 잘못되었거나, 실행 파일 경로가 달라 exec: not found로 즉시 종료합니다. 멀티스테이지 빌드에서 바이너리를 복사하지 못한 경우도 흔합니다.

확인 방법

  • 로그에 exec format error, no such file or directory
  • 아키텍처 불일치(예: ARM 노드에 AMD64 이미지)

즉시 복구

  • 올바른 커맨드로 임시 패치
kubectl -n "$NS" patch deploy myapp --type='json' -p='[
  {"op":"replace","path":"/spec/template/spec/containers/0/command","value":["/app/myapp"]},
  {"op":"replace","path":"/spec/template/spec/containers/0/args","value":["--port","8080"]}
]'
  • 이미지 플랫폼을 명시하여 재빌드/재푸시
# 예: 도커 빌드 (멀티 아키텍처)
docker buildx build --platform linux/amd64,linux/arm64 -t myrepo/myapp:fix --push .

즉시 복구를 위한 운영자 액션 플랜

원인 분석과 별개로, “지금 당장” 서비스 복구가 목표라면 다음 순서가 가장 안전합니다.

1) 영향 범위 축소

  • 문제가 있는 버전의 롤아웃을 멈춥니다.
kubectl -n "$NS" rollout pause deploy/myapp

2) 정상 버전으로 롤백

kubectl -n "$NS" rollout undo deploy/myapp
kubectl -n "$NS" rollout status deploy/myapp

3) 디버그용 임시 파드로 내부 상태 점검

CrashLoop 중인 컨테이너는 exec가 어려울 수 있어, 같은 네임스페이스에 디버그 파드를 띄우는 것이 빠릅니다.

kubectl -n "$NS" run netdebug --rm -it --image=busybox:1.36 -- sh
# 내부에서 nslookup, wget, nc 등으로 점검

4) 로그 확보 자동화

재시작이 빠르면 로그가 금방 휘발됩니다. 가능하면 중앙 로그로 보내고, 최소한 아래처럼 직전 로그를 즉시 수집합니다.

kubectl -n "$NS" logs "$POD" -c myapp --previous > /tmp/myapp.prev.log
kubectl -n "$NS" describe pod "$POD" > /tmp/myapp.describe.txt

현장에서 자주 먹히는 “원인별 빠른 힌트”

  • Exit Code: 137이면 대부분 OOM 또는 강제 종료 계열
  • 이벤트에 Liveness probe failed가 반복되면 probe 설정부터 의심
  • 로그가 아예 없고 이벤트에 pull 실패면 이미지/레지스트리
  • 특정 노드에서만 반복되면 노드 리소스 압박, 런타임 이슈, 아키텍처 불일치 확인

마무리: CrashLoopBackOff는 증상, 해법은 관찰 순서

CrashLoopBackOff 자체는 “쿠버네티스가 재시작을 반복하고 있다”는 증상일 뿐이고, 해법은 이벤트직전 로그를 가장 먼저 확보하는 관찰 순서에 달려 있습니다.

  • describe pod로 종료 사유와 이벤트를 먼저 확정
  • logs --previous로 직전 크래시 원인을 잡기
  • 즉시 복구는 롤백 또는 리소스 상향으로 서비스부터 살리기
  • 이후 probe, 리소스, 권한, 의존성, 이미지 빌드 체인을 차근히 교정

이 글의 9가지 원인만 체계적으로 점검해도 대부분의 CrashLoopBackOff는 10분 내에 방향이 잡히고, 30분 내에 임시 복구까지 도달할 수 있습니다.