Published on

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

Authors
Binance registration banner

서버리스처럼 보이던 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분 안에 원인을 좁힐 수 있습니다.