- Published on
K8s CrashLoopBackOff 원인 9가지와 즉시 복구
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
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 State에 Reason과 Exit 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 pod의Last State: Terminated에Exit 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를 제거하고 재배포 후 원인 분석
재발 방지
readinessProbe와livenessProbe의 목적을 분리- 외부 의존성(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'
즉시 복구
securityContext의runAsUser,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분 내에 임시 복구까지 도달할 수 있습니다.