쿠버네티스(Kubernetes)를 운영하다 보면 한 번쯤은 만나게 되는 에러가 있습니다. 바로 CrashLoopBackOff 에러죠. 파드 상태를 확인했는데 이 메시지가 떠있으면 마음이 급해지곤 합니다. 오늘은 CrashLoopBackOff 에러가 정확히 무엇인지, 왜 발생하는지, 그리고 어떻게 근본적으로 해결할 수 있는지 실제 사례와 함께 알아보겠습니다.

 

1. CrashLoopBackOff란 정확히 무엇인가?

CrashLoopBackOff는 쿠버네티스에서 컨테이너가 반복적으로 시작되고 실패하는 상태를 나타냅니다. 이름에서 알 수 있듯이 ‘Crash(충돌) → Loop(반복) → BackOff(지연)’의 순환 구조로 이루어져 있어요.

중요한 점은 CrashLoopBackOff 자체가 에러가 아니라는 것입니다. 실제로는 근본적인 문제가 있을 때 쿠버네티스가 보여주는 ‘증상’에 가깝습니다.

동작 원리

  1. 컨테이너가 시작됩니다
  2. 어떤 이유로 컨테이너가 실패하거나 종료됩니다
  3. 쿠버네티스가 컨테이너를 재시작합니다
  4. 다시 실패하면 백오프 딜레이를 적용합니다 (10초 → 20초 → 40초… 최대 5분)
  5. 문제가 해결될 때까지 이 과정을 반복합니다

이러한 백오프 딜레이는 시스템 리소스를 보호하고 관리자가 문제를 진단할 시간을 확보하기 위한 쿠버네티스의 똑똑한 메커니즘입니다.

 

 

2. CrashLoopBackOff 확인 방법

기본 확인 명령어

kubectl get pods

출력 예시:

NAME                    READY   STATUS             RESTARTS   AGE
nginx-5796d5bc7d-xtl6q  0/1     CrashLoopBackOff   4          1m

여기서 주목해야 할 포인트들:

  • READY: 0/1은 1개 컨테이너 중 0개가 준비된 상태
  • STATUS: CrashLoopBackOff 상태 표시
  • RESTARTS: 4는 이미 4번 재시작했다는 의미

네임스페이스 지정 확인

kubectl get pods -n <네임스페이스명>

 

 

3. CrashLoopBackOff 주요 원인들

실제 운영 환경에서 자주 발생하는 원인들을 정리해보겠습니다.

3.1 리소스 부족 문제

가장 흔한 원인 중 하나입니다. 메모리나 CPU 한계를 초과하면 컨테이너가 OOMKilled(Out of Memory Killed) 상태가 되어 재시작됩니다.

증상 확인:

kubectl describe pod <파드명>

해결 방법:

resources:
  limits:
    memory: "512Mi"    # 기존 256Mi에서 증가
    cpu: "500m"        # 기존 250m에서 증가
  requests:
    memory: "256Mi"
    cpu: "250m"

3.2 애플리케이션 설정 오류

환경변수 누락, 잘못된 포트 설정, 설정 파일 오류 등이 원인이 될 수 있습니다.

예시: 데이터베이스 연결 정보 누락

env:
- name: DB_HOST
  value: "mysql-service"    # 누락된 환경변수 추가
- name: DB_PORT
  value: "3306"

3.3 이미지 문제

잘못된 Docker 이미지나 태그, 권한 문제 등이 원인이 될 수 있습니다.

확인 방법:

# 이미지 Pull 상태 확인
kubectl describe pod <파드명> | grep -A5 "Events:"

3.4 의존성 서비스 문제

외부 서비스나 다른 파드와의 연결 문제로 발생할 수 있습니다.

 

 

4. 단계별 진단 및 해결 방법

4.1 1단계: 파드 상세 정보 확인

kubectl describe pod <파드명> -n <네임스페이스>

이 명령어로 확인해야 할 정보들:

  • 컨테이너 상태 (State: Waiting, Reason: CrashLoopBackOff)
  • 마지막 종료 원인 (Last State: Terminated, Reason: Error)
  • 종료 코드 (Exit Code)
  • 이벤트 목록 (Events 섹션)

4.2 2단계: 로그 분석

현재 로그 확인:

kubectl logs <파드명> -n <네임스페이스>

이전 컨테이너 로그 확인 (중요!):

kubectl logs <파드명> --previous -n <네임스페이스>

실시간 로그 모니터링:

kubectl logs -f <파드명> -n <네임스페이스>

4.3 3단계: 이벤트 확인

전체 이벤트 확인:

kubectl get events --sort-by=.metadata.creationTimestamp -n <네임스페이스>

특정 파드 이벤트만 확인:

kubectl get events --field-selector involvedObject.name=<파드명> -n <네임스페이스>

4.4 4단계: 디플로이먼트 확인

kubectl describe deployment <디플로이먼트명> -n <네임스페이스>

 

 

5. 실제 발생되어 해결된 몇 가지 케이스들…

사례 1: 메모리 부족으로 인한 OOMKilled

문제 상황:

Last State:     Terminated
  Reason:       OOMKilled
  Exit Code:    137

해결 방법:

spec:
  containers:
  - name: app
    image: myapp:latest
    resources:
      limits:
        memory: "1Gi"      # 기존 512Mi에서 증가
      requests:
        memory: "512Mi"

사례 2: 라이브니스 프로브 설정 오류

문제 상황:

Liveness probe failed: Get "http://10.244.0.5:8080/health": dial tcp 10.244.0.5:8080: connect: connection refused

해결 방법:

livenessProbe:
  httpGet:
    path: /health
    port: 8080
  initialDelaySeconds: 60     # 기존 30초에서 증가
  periodSeconds: 30           # 체크 간격 조정
  timeoutSeconds: 10          # 타임아웃 증가

사례 3: 환경변수 누락

문제 상황: 애플리케이션 로그에서 DB_HOST environment variable not found 에러 발생

해결 방법:

env:
- name: DB_HOST
  valueFrom:
    secretKeyRef:
      name: db-secret
      key: host
- name: DB_PASSWORD
  valueFrom:
    secretKeyRef:
      name: db-secret
      key: password

 

 

6. 추가적으로 디버깅 하는 방법

6.1 임시 컨테이너로 디버깅

컨테이너가 계속 죽어서 접근이 어려울 때 유용합니다.

# entrypoint를 sleep으로 변경하여 컨테이너를 살려두기
kubectl run debug-pod --image=<이미지명> --restart=Never -- sleep 3600

또는 디플로이먼트 YAML에서 임시로 command를 변경:

spec:
  containers:
  - name: app
    image: myapp:latest
    command: ["sleep", "3600"]  # 임시로 추가

6.2 kubectl exec으로 컨테이너 접근

kubectl exec -it <파드명> -- /bin/bash

컨테이너 내부에서 확인할 사항들:

  • 환경변수: env
  • 파일 시스템: ls -la /app
  • 프로세스: ps aux
  • 네트워크: netstat -tlnp

6.3 리소스 모니터링

# 파드별 리소스 사용량 확인
kubectl top pods

# 노드별 리소스 사용량 확인  
kubectl top nodes

 

 

7. CrashLoopBackOff 에러 예방 및 모니터링 방법

7.1 프로브 설정 최적화

레디니스 프로브:

readinessProbe:
  httpGet:
    path: /ready
    port: 8080
  initialDelaySeconds: 5
  periodSeconds: 5

라이브니스 프로브:

livenessProbe:
  httpGet:
    path: /health
    port: 8080
  initialDelaySeconds: 30
  periodSeconds: 30
  failureThreshold: 3

7.2 리소스 설정 가이드라인

애플리케이션 유형 Memory Request Memory Limit CPU Request CPU Limit
마이크로서비스 128Mi 256Mi 100m 200m
웹 애플리케이션 256Mi 512Mi 250m 500m
데이터베이스 1Gi 2Gi 500m 1000m

7.3 모니터링 알람 설정

Prometheus 알람 예시:

- alert: PodCrashLooping
  expr: rate(kube_pod_container_status_restarts_total[15m]) > 0
  for: 5m
  labels:
    severity: warning
  annotations:
    summary: "Pod {{ $labels.pod }} is crash looping"

 

 

8. 자주 하는 실수와 주의사항

실수 1: 로그를 제대로 보지 않기

많은 분들이 kubectl logs만 확인하고 --previous 옵션을 놓치는 경우가 많습니다. 크래시된 컨테이너의 로그는 이전 인스턴스에 있다는 점을 꼭 기억하세요.

실수 2: 리소스 제한을 너무 낮게 설정

특히 자바 애플리케이션의 경우 JVM 힙 메모리와 컨테이너 메모리 제한을 함께 고려해야 합니다.

실수 3: 헬스체크 경로 확인 누락

애플리케이션에서 실제로 /health 엔드포인트를 제공하는지 확인하지 않고 프로브를 설정하는 경우가 있습니다.

 

 

9. 유용한 kubectl 관리 명령어

# 파드 상태 실시간 모니터링
kubectl get pods --watch

# 특정 라벨의 파드들만 확인  
kubectl get pods -l app=myapp

# 파드 삭제 (강제)
kubectl delete pod <파드명> --force --grace-period=0

# 디플로이먼트 재시작
kubectl rollout restart deployment/<디플로이먼트명>

# 파드 리소스 사용량 확인
kubectl describe pod <파드명> | grep -A5 "Limits\|Requests"

# 이벤트를 시간순으로 정렬
kubectl get events --sort-by='.lastTimestamp'

 

 

CrashLoopBackOff 에러는 처음 만났을 때는 당황스럽지만, 체계적인 접근을 통해 충분히 해결할 수 있습니다. 가장 중요한 것은 증상이 아닌 근본 원인을 찾는 것입니다.

기억해야 할 핵심 포인트들:

  • CrashLoopBackOff는 증상이지 원인이 아닙니다
  • --previous 옵션으로 이전 컨테이너 로그를 확인하세요
  • 리소스 제한과 요청값을 적절히 설정하세요
  • 프로브 설정을 꼼꼼히 검토하세요

쿠버네티스 운영이 어렵게 느껴질 수 있지만, 이런 경험들이 쌓이다 보면 어느새 문제를 빠르게 진단하고 해결할 수 있는 실력이 늘어있을 거라 생각 합니다. 이 포스트가 도움이 되었으면 합니다.

 

댓글 남기기