Published on

EKS CrashLoopBackOff인데 로그가 0줄? 원인 8가지

Authors

서버리스처럼 보이던 EKS가 갑자기 CrashLoopBackOff에 빠졌는데, 정작 kubectl logs0줄(빈 출력) 이라면 디버깅이 급격히 어려워집니다. 하지만 대부분의 케이스는 “애플리케이션이 로그를 안 남겼다”가 아니라, 로그가 생성되기 전에 컨테이너가 죽었거나, 내가 보고 있는 대상이 잘못됐거나, 로그 파이프라인/런타임 이슈로 로그를 못 읽는 상황입니다.

이 글에서는 EKS에서 자주 만나는 “CrashLoopBackOff + 로그 0줄” 조합의 원인 8가지를 우선순위대로 정리하고, 각 원인별로 증상, 확인 방법, 해결 팁을 제공합니다.


먼저: ‘0줄’이 진짜 0인지부터 확인

아래 3가지는 모든 케이스에서 공통으로 먼저 수행하세요.

1) 종료된 이전 컨테이너 로그 확인 (--previous)

CrashLoopBackOff는 “재시작 중” 상태이므로 현재 컨테이너가 막 떠 있거나 이미 죽었을 수 있습니다.

kubectl logs -n <ns> <pod> -c <container> --previous
  • --previous에서 로그가 나오면: 원인은 애플리케이션/프로세스 레벨일 가능성이 큼
  • --previous도 0줄이면: 컨테이너가 로그를 남기기 전에 죽었거나, 로그 접근 자체가 안 되는 상황일 수 있음

2) 이벤트로 “왜 죽는지”를 먼저 본다

로그가 없을 때 가장 강력한 단서는 이벤트입니다.

kubectl describe pod -n <ns> <pod>
kubectl get events -n <ns> --sort-by=.lastTimestamp | tail -n 50

3) 컨테이너 상태/종료코드 확인

kubectl get pod -n <ns> <pod> -o jsonpath='{.status.containerStatuses[*].state.terminated.exitCode}{"\n"}'
kubectl get pod -n <ns> <pod> -o jsonpath='{.status.containerStatuses[*].lastState.terminated.reason}{"\n"}'
  • exitCode=137: OOMKill/강제 종료 계열 가능성
  • reason=Error, ContainerCannotRun, StartError: 런타임/권한/엔트리포인트 문제 가능성

원인 1) 컨테이너가 너무 빨리 죽어서 stdout/stderr에 아무것도 못 씀

전형적 증상

  • kubectl logs--previous 모두 빈 출력
  • 이벤트에는 Back-off restarting failed container 정도만 보임
  • 컨테이너 시작 직후 즉시 종료 (exitCode 1 등)

확인 방법

  • describe pod에서 Started/Finished 시간이 거의 동일
  • 엔트리포인트/커맨드 오타, 필수 env 누락 등

해결 팁

  1. 임시로 sleep를 넣어 컨테이너를 붙잡고, 내부에서 수동 실행해 원인을 확인합니다.
# (디버그용) command를 잠시 바꿔서 컨테이너가 바로 죽지 않게 함
spec:
  containers:
  - name: app
    image: <image>
    command: ["/bin/sh", "-c"]
    args: ["echo 'debug mode'; sleep 3600"]
  1. 그 다음 exec로 들어가 원래 커맨드를 실행해 봅니다.
kubectl exec -n <ns> -it <pod> -c app -- /bin/sh
# 내부에서 원래 엔트리포인트/바이너리 실행

원인 2) kubectl logs 대상이 틀림 (컨테이너/파드/레이블)

멀티 컨테이너 파드(사이드카 포함)에서 가장 흔합니다.

전형적 증상

  • kubectl logs <pod>는 0줄인데, 실제로는 다른 컨테이너가 죽고 있음
  • 혹은 내가 보고 있는 파드가 이미 교체되어 새 파드에는 로그가 없음

확인 방법

kubectl get pod -n <ns> <pod> -o wide
kubectl get pod -n <ns> <pod> -o jsonpath='{.spec.containers[*].name}{"\n"}'

해결 팁

  • 컨테이너 지정해서 확인
kubectl logs -n <ns> <pod> -c <container>
kubectl logs -n <ns> <pod> -c <container> --previous
  • 레플리카셋 교체가 잦다면, 레이블로 “최근 파드들”을 한 번에 확인
kubectl get pod -n <ns> -l app=<label> --sort-by=.status.startTime

원인 3) 이미지가 사실상 실행 불가 (ENTRYPOINT/CMD/아키텍처 불일치)

이미지가 pull은 되었지만 실행이 안 되는 케이스입니다. 이 경우 애플리케이션 로그가 생성될 기회 자체가 없습니다.

전형적 증상

  • ContainerCannotRun, StartError, exec format error
  • ARM/AMD64 혼용(Graviton 노드에서 x86 이미지 등)

확인 방법

kubectl describe pod -n <ns> <pod> | sed -n '/State:/,/Events:/p'

해결 팁

  • 멀티 아키텍처 이미지로 빌드하거나 노드 아키텍처에 맞는 이미지를 사용
  • ENTRYPOINT/CMD가 존재하는지, 실행 권한이 있는지 확인

원인 4) OOMKilled(메모리 부족)로 죽어서 로그가 남지 않음

메모리 부족은 “로그 0줄”과 자주 엮입니다. 특히 앱이 초기화 중 대량 메모리를 먹고 바로 죽으면 stdout에 찍을 틈이 없습니다.

전형적 증상

  • exitCode=137, reason=OOMKilled
  • 노드/파드 메모리 압박 이벤트

확인 방법

kubectl describe pod -n <ns> <pod> | egrep -i 'oom|killed|memory'

해결 팁

  • requests/limits 재조정
  • 초기화 단계의 메모리 피크(빌드/압축/캐시) 제거
  • Node 사이징/오토스케일 조정

빌드/런타임에서 메모리 피크가 나는 워크로드라면 캐시/메모리 튜닝 관점도 함께 보세요: Next.js 14 빌드 OOM·느려짐 해결 - SWC 캐시·메모리 튜닝


원인 5) liveness/readiness probe가 너무 공격적이라 “정상 프로세스”도 죽임

프로세스는 살아있는데 liveness probe 실패로 kubelet이 계속 죽여서 CrashLoopBackOff가 됩니다. 이때 앱 로그가 거의 없거나(혹은 readiness 로그만) 0줄처럼 보일 수 있습니다.

전형적 증상

  • 이벤트에 Liveness probe failed 반복
  • 앱은 부팅 시간이 긴데 initialDelay가 짧음

확인 방법

kubectl describe pod -n <ns> <pod> | egrep -i 'liveness|readiness|probe|failed'

해결 팁

  • startupProbe를 추가해 “부팅 완료 전”에는 liveness를 유예
  • initialDelaySeconds, timeoutSeconds, failureThreshold 완화
startupProbe:
  httpGet:
    path: /healthz
    port: 8080
  failureThreshold: 60
  periodSeconds: 2
livenessProbe:
  httpGet:
    path: /healthz
    port: 8080
  periodSeconds: 10
  timeoutSeconds: 2
  failureThreshold: 3

원인 6) 권한/파일시스템 문제로 앱이 시작 전에 죽음 (읽기 전용, 권한, Secret/Config)

EKS에서 보안 강화를 위해 readOnlyRootFilesystem: true, non-root 실행, 엄격한 fsGroup 등을 적용하면, 앱이 /tmp, 로그 파일 경로, 캐시 디렉터리 등에 쓰려다 즉시 죽을 수 있습니다.

전형적 증상

  • 로그 0줄 또는 아주 짧은 에러만 남고 종료
  • 이벤트/종료 사유는 단순 Error

확인 방법

  • describe에서 컨테이너 securityContext 확인
  • Secret/ConfigMap 마운트 경로 존재 여부 확인

해결 팁

  • 쓰기 경로를 emptyDir로 제공
  • 앱의 로그를 파일이 아니라 stdout/stderr로 내보내기
volumeMounts:
- name: tmp
  mountPath: /tmp
volumes:
- name: tmp
  emptyDir: {}

원인 7) 로그 드라이버/런타임/노드 문제로 kubectl logs가 비어 보임

kubectl logs는 노드의 컨테이너 런타임 로그 파일(또는 CRI 로그)을 읽어옵니다. 노드의 디스크 압박, containerd 문제, 로그 파일 손상/권한 문제 등이 있으면 컨테이너가 뭔가를 찍었어도 API로 못 가져와 0줄처럼 보일 수 있습니다.

전형적 증상

  • 같은 노드에 뜬 다른 파드도 로그가 이상함
  • 이벤트에 NodeHasDiskPressure, containerd 관련 메시지

확인 방법

  • 파드가 어느 노드에 있는지 확인
kubectl get pod -n <ns> <pod> -o wide
kubectl describe node <node>

해결 팁

  • 노드 디스크 정리(특히 /var/log, 컨테이너 이미지/레이어)
  • 문제 노드 cordon/drain 후 교체
kubectl cordon <node>
kubectl drain <node> --ignore-daemonsets --delete-emptydir-data

원인 8) 애플리케이션이 stdout/stderr로 로그를 안 남김 (파일로만 로깅)

의외로 많습니다. 로컬에서는 파일 로그가 잘 쌓이는데, 컨테이너에서는 파일이 ephemeral이거나 경로가 없어서 조용히 실패하고, stdout에는 아무 것도 안 찍는 구성입니다.

전형적 증상

  • kubectl logs 0줄
  • exec로 들어가 보면 /var/log/app.log 같은 파일이 존재하거나(혹은 생성 실패)

확인 방법

디버그 모드로 컨테이너를 잠깐 살려서 파일을 확인합니다.

kubectl exec -n <ns> -it <pod> -c <container> -- /bin/sh
ls -al /var/log
find / -maxdepth 3 -type f -name "*.log" 2>/dev/null | head

해결 팁

  • 기본은 stdout/stderr 로깅으로 전환
  • 파일 로깅이 필요하면 사이드카(Fluent Bit 등) 또는 볼륨 마운트로 수집

예: Python이라면 기본 로거를 stdout으로

import logging
import sys

logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s %(levelname)s %(message)s",
    handlers=[logging.StreamHandler(sys.stdout)],
)

logging.info("app started")

실전 체크리스트: 3분 안에 결론 내는 순서

  1. kubectl describe podEvents 확인
  2. kubectl logs --previous이전 컨테이너 로그 확인
  3. exitCode/reason으로 OOM/StartError 분기
  4. probe 실패 여부 확인
  5. 권한/FS/Secret 마운트 점검
  6. 노드 디스크/런타임 이상 점검

부록: 자주 쓰는 원샷 커맨드 모음

파드 상태/이벤트/컨테이너 상태 한번에

NS=<ns>
POD=<pod>

kubectl get pod -n $NS $POD -o wide
kubectl describe pod -n $NS $POD
kubectl get pod -n $NS $POD -o jsonpath='{range .status.containerStatuses[*]}{.name}{"\t"}{.restartCount}{"\t"}{.lastState.terminated.reason}{"\t"}{.lastState.terminated.exitCode}{"\n"}{end}'

레이블 기준으로 최근 파드 로그 훑기

NS=<ns>
LABEL='app=myapp'

for p in $(kubectl get pod -n $NS -l $LABEL --sort-by=.status.startTime -o jsonpath='{.items[*].metadata.name}'); do
  echo "==== $p ===="
  kubectl logs -n $NS $p --all-containers --previous --tail=50 2>/dev/null || true
done

함께 보면 좋은 글


CrashLoopBackOff에서 로그가 0줄일 때 핵심은 “로그가 없어서 못 고친다”가 아니라, 로그가 생기기 전에 죽는지, 내가 로그를 잘못 보고 있는지, 런타임/노드가 로그를 못 주는지를 빠르게 분기하는 것입니다. 위 8가지를 순서대로 체크하면 대부분의 케이스는 10분 안에 원인을 좁힐 수 있습니다.