Published on

KServe GPU 서빙 CrashLoopBackOff 8가지 원인

Authors

KServe에서 GPU 추론 서버를 올렸는데 CrashLoopBackOff가 반복되면, 대부분은 "GPU 문제가 아니라" 컨테이너 프로세스가 즉시 종료하거나 쿠버네티스가 헬스체크 실패로 재시작시키는 케이스입니다. 특히 KServe는 InferenceService 하나가 predictor / transformer / explainer 등 여러 컨테이너로 확장될 수 있어, "어느 컨테이너가" 죽는지부터 정확히 잡아야 합니다.

아래는 현장에서 가장 자주 만나는 GPU 모델 서빙 CrashLoopBackOff 8가지 원인을, 증상과 재현 로그, 해결 순서까지 묶어 정리한 체크리스트입니다. 일반적인 CrashLoopBackOff 원인 전반은 Kubernetes CrashLoopBackOff 12가지 원인·해결도 함께 참고하면 진단 속도가 빨라집니다.


먼저: "어느 컨테이너"가 죽는지 3분 컷으로 확인

KServe는 보통 isvc 하나가 predictor Deployment/Pod로 내려가고, 그 안에 kserve-container(모델 서버) 외에 queue-proxy(Knative), storage-initializer(init) 등이 섞입니다. CrashLoopBackOff는 특정 컨테이너에만 걸리는 경우가 많습니다.

# 1) Pod 찾기
kubectl get pods -n ml | grep -i <YOUR-ISVC-NAME>

# 2) 어떤 컨테이너가 재시작되는지 확인
kubectl describe pod -n ml <POD_NAME>

# 3) 직전 크래시 로그 확인 (핵심)
kubectl logs -n ml <POD_NAME> -c <CONTAINER_NAME> --previous

# 4) 이벤트에서 OOMKilled, probe 실패, device plugin 에러 등을 확인
kubectl get events -n ml --sort-by=.metadata.creationTimestamp | tail -n 50

--previous 로그가 비어 있으면 프로세스가 너무 빨리 죽었거나, 컨테이너 런타임/권한 문제일 수 있습니다. 그럴 땐 describeLast StateExit Code를 먼저 보세요.


1) GPU 리소스 미할당: nvidia.com/gpu 요청 누락 또는 스케줄 실패

대표 증상

  • Pod가 뜨긴 뜨는데 앱 로그에 CUDA driver not found 또는 Torch not compiled with CUDA enabled
  • 또는 아예 스케줄이 안 되고 Pending에 머무름

확인 포인트

kubectl get pod -n ml <POD_NAME> -o jsonpath='{.spec.containers[*].resources}'
kubectl describe pod -n ml <POD_NAME> | sed -n '1,200p'
  • resources.limits["nvidia.com/gpu"]가 없으면 GPU가 붙지 않습니다.
  • describe 이벤트에 0/.. nodes are available: Insufficient nvidia.com/gpu가 보이면 노드에 GPU가 없거나 이미 할당됨.

해결

  • InferenceService에 GPU limit 명시
  • GPU 노드에 taint가 있다면 toleration/노드셀렉터 추가
apiVersion: serving.kserve.io/v1beta1
kind: InferenceService
metadata:
  name: gpu-isvc
spec:
  predictor:
    containers:
      - name: kserve-container
        image: <YOUR_IMAGE>
        resources:
          limits:
            nvidia.com/gpu: "1"
            cpu: "2"
            memory: "8Gi"

2) NVIDIA Device Plugin / Runtime 미구성: 컨테이너는 뜨지만 GPU 접근 불가

대표 증상

  • nvidia-smi가 컨테이너에서 실행되지 않음
  • 로그에 libcuda.so.1: cannot open shared object file
  • Failed to initialize NVML 또는 Unknown Error

확인 포인트

노드에 GPU가 붙어 있어도, NVIDIA device plugin컨테이너 런타임 설정이 빠지면 GPU가 컨테이너로 노출되지 않습니다.

# 노드에서 allocatable 확인
kubectl get node -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.status.allocatable.nvidia\.com/gpu}{"\n"}{end}'

# device plugin DaemonSet 확인
kubectl get ds -A | grep -i nvidia

해결

  • nvidia-device-plugin DaemonSet 설치/정상화
  • containerd 사용 시 nvidia-container-runtime 설정 확인
  • MIG 사용 중이면 리소스 키가 nvidia.com/mig-...로 바뀌는지 확인

3) 모델 서버 프로세스 즉시 종료: 엔트리포인트/명령어/인자 오류

GPU 문제처럼 보여도 실제로는 서버가 실행되지 않아 즉시 종료하는 케이스가 매우 흔합니다.

대표 증상

  • Exit Code 1 또는 Exit Code 2
  • python: can't open file / ModuleNotFoundError
  • command not found / exec format error

확인 포인트

kubectl logs -n ml <POD_NAME> -c kserve-container --previous
kubectl get pod -n ml <POD_NAME> -o jsonpath='{.spec.containers[0].command}'
kubectl get pod -n ml <POD_NAME> -o jsonpath='{.spec.containers[0].args}'

해결

  • 이미지에 포함된 실행 파일/모듈 경로를 재검증
  • WORKDIR/ENTRYPOINT/CMD 조합이 KServe 오버라이드와 충돌하는지 확인
  • 가능하면 컨테이너 시작 시점에 환경/파일 존재를 출력하도록 부팅 로그 강화
# 예: 시작 시점에 버전/경로를 로그로 남기기
CMD ["bash","-lc","python -V && ls -al /app && python /app/server.py"]

4) VRAM 부족 또는 CUDA 메모리 파편화로 초기 로딩 단계에서 크래시

대형 모델은 서버가 요청을 받기 전에 가중치 로딩/엔진 빌드에서 VRAM을 크게 잡아먹습니다. 이때 OOM이 나면 컨테이너는 그대로 종료하고 CrashLoopBackOff로 들어갑니다.

대표 증상

  • 로그에 CUDA out of memory
  • cublas/cudnn 초기화 실패
  • TensorRT/torch compile 단계에서 죽음

확인 포인트

  • 애플리케이션 로그에서 OOM 메시지 확인
  • 노드에서 다른 GPU 작업이 동시 실행 중인지 확인

해결

KServe/Knative 환경에서는 동시성도 중요합니다. 예를 들어 vLLM/TGI 같은 서버는 기본 동시성이 높아 초기 메모리 압박이 커질 수 있습니다.

# 예: 동시성/리소스 보수적으로 시작
metadata:
  annotations:
    autoscaling.knative.dev/target: "1"
    autoscaling.knative.dev/minScale: "1"

5) CPU/RAM OOMKilled: "GPU만" 올렸는데 메인 메모리에서 죽는 케이스

가중치를 GPU로 올리더라도, CPU 메모리에서 전처리/토크나이저/캐시가 크게 잡히거나, 모델 파일을 메모리 매핑하다가 RSS가 튀는 경우가 있습니다.

대표 증상

  • describe podOOMKilled
  • Exit Code 137

확인 포인트

kubectl describe pod -n ml <POD_NAME> | grep -n "OOMKilled" -n
kubectl top pod -n ml | grep -i <POD_NAME>

해결

  • resources.limits.memory 상향 또는 메모리 사용량 감소
  • tokenizer 병렬화/캐시 전략 조정
  • 모델 로딩 시 불필요한 복사 방지
resources:
  requests:
    cpu: "1"
    memory: "8Gi"
  limits:
    cpu: "4"
    memory: "16Gi"
    nvidia.com/gpu: "1"

6) 헬스체크(Probe) 설정 불일치: "로딩이 느린" GPU 모델을 K8s가 죽여버림

GPU 모델은 cold start에서 수십 초~수 분이 걸릴 수 있습니다. 그런데 readinessProbe/livenessProbe가 기본값이거나 너무 공격적이면, 서버가 준비되기 전에 실패로 판정되어 재시작됩니다.

대표 증상

  • 이벤트에 Readiness probe failed / Liveness probe failed
  • 로그는 정상적으로 로딩 중인데 일정 시간 후 재시작

확인 포인트

kubectl describe pod -n ml <POD_NAME> | sed -n '1,220p'

해결

  • startupProbe를 추가하거나, initialDelaySeconds/failureThreshold/timeoutSeconds를 모델 로딩 시간에 맞게 조정
readinessProbe:
  httpGet:
    path: /health
    port: 8080
  initialDelaySeconds: 30
  periodSeconds: 10
  timeoutSeconds: 5
  failureThreshold: 12
livenessProbe:
  httpGet:
    path: /health
    port: 8080
  initialDelaySeconds: 60
  periodSeconds: 20
  timeoutSeconds: 5
  failureThreshold: 6
startupProbe:
  httpGet:
    path: /health
    port: 8080
  periodSeconds: 10
  failureThreshold: 60

KServe 프레임워크(예: TorchServe, Triton, custom server)에 따라 헬스 엔드포인트가 /v2/health/ready 같은 형태일 수 있으니, 실제 서버가 제공하는 경로로 맞추는 게 핵심입니다.


7) 모델 아티팩트 다운로드/마운트 실패: storage-initializer 또는 권한 문제

KServe는 모델을 PVC, S3, GCS 등에서 가져오며, 이 과정이 initContainer 또는 사이드카에서 일어납니다. GPU 컨테이너가 아니라 모델 다운로드 단계에서 실패해도 전체 Pod가 반복 재시작될 수 있습니다.

대표 증상

  • kserve-container 로그는 비어 있고, storage-initializer에서 에러
  • 403/AccessDenied/NoSuchKey/Permission denied

확인 포인트

kubectl logs -n ml <POD_NAME> -c storage-initializer --previous
kubectl describe pod -n ml <POD_NAME> | sed -n '1,260p'

해결

  • Secret/ServiceAccount 권한(IAM, Workload Identity 등) 재점검
  • 모델 경로 오타 확인
  • PVC 마운트 권한(fsGroup, runAsUser) 정리
securityContext:
  runAsUser: 1000
  runAsGroup: 1000
  fsGroup: 1000

8) CUDA/드라이버/라이브러리 버전 불일치: 이미지와 노드 드라이버의 ABI 충돌

"GPU는 잡혔는데" 특정 라이브러리 로딩에서 죽는다면, 드라이버와 CUDA 런타임 조합을 의심해야 합니다. 예를 들어 노드 드라이버가 너무 낮거나, 컨테이너에 포함된 CUDA 라이브러리가 기대하는 최소 드라이버 버전이 더 높으면 런타임에서 크래시가 납니다.

대표 증상

  • CUDA driver version is insufficient for CUDA runtime version
  • libcudart.so/libcuda.so 관련 로딩 실패
  • 특정 프레임워크(TensorRT, PyTorch) import 시점에 종료

확인 포인트

  • 노드에서 nvidia-smi로 드라이버 버전 확인
  • 컨테이너 이미지의 CUDA 베이스 태그 확인
# 노드에서 (권한이 있다면)
ssh <GPU_NODE> nvidia-smi

# 컨테이너에서 CUDA 런타임 확인(가능한 경우)
kubectl exec -n ml -it <POD_NAME> -c kserve-container -- bash -lc "python -c 'import torch; print(torch.version.cuda); print(torch.cuda.is_available())'"

해결

  • 노드 드라이버 업그레이드 또는 CUDA 런타임 낮춘 이미지 사용
  • 프레임워크 버전 매트릭스에 맞춰 재빌드
  • 가능하면 공식 CUDA 베이스 이미지(nvidia/cuda)와 검증된 조합 사용

실전 트러블슈팅 순서(추천)

CrashLoopBackOff를 가장 빠르게 줄이는 순서는 아래가 효율적입니다.

  1. kubectl describe podExit Code / OOMKilled / probe 실패 여부 확인
  2. kubectl logs --previous직전 크래시 로그 확보
  3. nvidia.com/gpu 할당 여부와 노드 allocatable 확인
  4. storage-initializer 로그로 모델 다운로드/권한 문제 배제
  5. VRAM/CPU 메모리 부족이면 리소스/동시성/precision부터 보수적으로 낮춰 재시도

유사하게 "원인은 단순한데 로그를 못 찾아서" 오래 걸리는 유형은 이미지 풀 단계에서 많이 발생합니다. GPU 이슈와 별개로 이미지 다운로드가 꼬였을 가능성도 있으니 K8s ImagePullBackOff·ErrImagePull 원인 12가지도 함께 체크해 두면 좋습니다.


마무리: CrashLoopBackOff는 "원인"이 아니라 "결과"다

KServe GPU 서빙에서 CrashLoopBackOff는 대개 다음 중 하나로 수렴합니다.

  • 프로세스가 즉시 종료(엔트리포인트/의존성/권한)
  • 쿠버네티스가 강제로 재시작(probe 실패)
  • 리소스 부족(OOMKilled, VRAM OOM)
  • GPU 런타임 계층 문제(device plugin, 드라이버/CUDA 불일치)

위 8가지를 순서대로 배제하면, "GPU라서 어렵다"기보다 관측 지점을 어디에 둘지가 문제였다는 걸 체감하게 됩니다. 다음 장애 대응을 위해서는 --previous 로그 수집, 이벤트 수집, 그리고 모델 로딩 시간을 반영한 probe 설계까지를 기본 템플릿으로 만들어 두는 것을 권합니다.