Published on

KServe vLLM 배포 후 502? InferenceService 8단계

Authors

서빙은 떴는데 호출만 하면 502 Bad Gateway가 떨어지는 상황은 KServe에서 꽤 흔합니다. 특히 vLLM은 모델 로딩 시간이 길고(다운로드, 샤딩, KV 캐시 초기화), GPU/메모리 요구량이 크며, OpenAI 호환 API 경로가 KServe 프록시 경로와 섞이기 쉬워서 502가 “원인 불명”처럼 보이기 쉽습니다.

이 글은 KServe의 InferenceService로 vLLM을 배포한 뒤 502가 발생했을 때, 원인을 8단계로 쪼개서 빠르게 좁히는 실무 체크리스트입니다. 단계별로 “무엇을 확인”하고 “어떤 명령/로그를 보며” “어떻게 고치는지”까지 정리합니다.

참고로 readiness/health 문제로 겉보기엔 정상 로그인데 트래픽만 실패하는 패턴은 아래 글에서도 다룬 적이 있습니다.


1단계: 502가 어디서 나는지(게이트웨이 vs 파드) 먼저 분리

502는 대개 “프록시가 업스트림에 연결 못 함”입니다. KServe 경로에서는 보통 다음 중 하나가 프록시 역할을 합니다.

  • Istio IngressGateway(또는 Kourier/Knative)
  • Knative activator(스케일-투-제로/콜드스타트 구간)
  • KServe 내부 networking 레이어

먼저 요청이 어디까지 들어가는지 확인합니다.

# InferenceService 상태
kubectl get inferenceservice -A
kubectl describe inferenceservice -n <namespace> <name>

# KServe가 만든 Knative Service(사용 중이라면)
kubectl get ksvc -n <namespace>

# IngressGateway 로그(istio 사용 시)
kubectl -n istio-system logs deploy/istio-ingressgateway -c istio-proxy --tail=200

판별 기준:

  • IngressGateway 로그에 upstream connect error 또는 no healthy upstream가 보이면 “파드 readiness/엔드포인트 문제” 가능성이 큽니다.
  • Ingress까지는 정상인데 응답이 늦다가 502라면 “타임아웃(라우팅/프로브/콜드스타트)” 가능성이 큽니다.

2단계: InferenceService Ready가 진짜 True인지, 조건을 읽기

KServe 리소스는 “겉으로 Ready처럼 보이는데 세부 조건이 깨져있는” 경우가 있습니다.

kubectl get isvc -n <namespace> <name> -o yaml

아래를 집중해서 봅니다.

  • status.conditions[]에서 Ready, PredictorReady, RoutesReady 상태
  • status.url이 비어 있지 않은지
  • 이벤트에 RevisionMissing, ConfigurationFailed, ProgressDeadlineExceeded 같은 메시지가 있는지

특히 Knative 기반이면 RoutesReady=False가 흔한데, 이 경우는 파드가 살아도 라우팅이 붙지 않아 502가 나기 쉽습니다.


3단계: 엔드포인트가 실제로 생겼는지(Service/EndpointSlice)

프록시가 업스트림으로 붙으려면 K8s Service 뒤에 엔드포인트가 있어야 합니다.

# 서비스 확인
kubectl get svc -n <namespace>

# 엔드포인트/슬라이스 확인
kubectl get endpoints -n <namespace>
kubectl get endpointslice -n <namespace>

체크 포인트:

  • 엔드포인트가 비어 있으면 readiness가 실패했거나, selector가 맞지 않거나, 파드가 준비 상태가 아닙니다.
  • 엔드포인트는 있는데도 502면, 포트/프로토콜 불일치(예: 서비스는 80인데 컨테이너는 8000만 리슨) 가능성이 큽니다.

4단계: vLLM 컨테이너가 “리스닝”을 시작했는지 로그로 확인

vLLM은 모델 로딩이 끝나기 전까지 HTTP 서버가 뜨지 않거나, 뜨더라도 내부적으로 준비되지 않은 상태일 수 있습니다. 이때 KServe/Knative가 트래픽을 붙이면 502가 발생합니다.

kubectl logs -n <namespace> deploy/<predictor-deploy-name> -c <container> --tail=200

자주 보는 원인:

  • 모델 다운로드 실패(HF 토큰, 네트워크, 프라이빗 리포)
  • GPU 메모리 부족(OOM)으로 프로세스 재시작
  • --max-model-len 과 KV 캐시 설정으로 초기화 실패
  • --dtype/텐서 병렬 설정 불일치

특히 이미지 풀/레지스트리 문제면 파드가 뜨지도 못합니다. 이 경우는 ImagePullBackOff부터 해결해야 합니다.


5단계: 프로브(readiness/liveness/startup)가 vLLM 특성과 맞는지

502의 가장 흔한 실무 원인은 “모델 로딩이 긴데 readiness가 너무 공격적”인 설정입니다.

  • readiness가 실패하면 엔드포인트에서 제외되어 프록시가 no healthy upstream으로 502
  • liveness가 실패하면 컨테이너가 재시작 루프에 빠져 영원히 준비되지 않음

권장 패턴은 startupProbe를 적극적으로 쓰는 것입니다.

# Deployment 템플릿에 들어갈 수 있는 예시(개념 예시)
startupProbe:
  httpGet:
    path: /health
    port: 8000
  failureThreshold: 120
  periodSeconds: 5
readinessProbe:
  httpGet:
    path: /health
    port: 8000
  initialDelaySeconds: 5
  periodSeconds: 5
  timeoutSeconds: 2
livenessProbe:
  httpGet:
    path: /health
    port: 8000
  initialDelaySeconds: 30
  periodSeconds: 10

주의:

  • vLLM OpenAI 서버는 보통 GET /health를 제공합니다(이미지/버전에 따라 다를 수 있으니 실제 컨테이너 문서/로그 기준으로 확인).
  • KServe/Knative 조합에서는 프로브 경로가 별도 리라이트되는 경우도 있어, “파드 내부에서 직접 curl로 되는지”를 먼저 확인하는 게 안전합니다.
kubectl exec -n <namespace> -it <pod> -- sh -lc 'curl -sS localhost:8000/health'

프로브가 정상인데도 readiness가 실패한다면, 아래 글의 “로그는 정상인데 readiness만 실패” 유형을 같이 참고하면 좋습니다.


6단계: 라우팅 경로가 섞였는지(특히 OpenAI 호환 API)

KServe는 외부 URL에 라우팅 프리픽스가 붙을 수 있습니다. 반면 vLLM OpenAI 호환 서버는 보통 다음 경로를 기대합니다.

  • POST /v1/completions
  • POST /v1/chat/completions
  • GET /v1/models

여기서 자주 생기는 문제:

  • 사용자는 .../v1/chat/completions로 쐈는데, 실제로는 .../v1/models만 열려 있거나
  • Ingress에서 /로 들어오는 요청이 KServe 내부에서 특정 프리픽스로 리라이트되어 vLLM이 404를 내고, 그게 상위에서 502로 보이는 경우

진단 방법은 “동일 클러스터 내부에서 서비스로 직접 호출”과 “외부 인그레스 호출”을 비교하는 것입니다.

# 클러스터 내부에서 서비스로 직접 호출(예시)
kubectl run -n <namespace> tmp-curl --rm -it --image=curlimages/curl -- sh

# 파드 안에서
curl -sS http://<service-name>.<namespace>.svc.cluster.local:8000/v1/models

내부 호출이 되는데 외부만 502면 라우팅/VirtualService/Ingress 설정 문제일 확률이 큽니다.


7단계: 타임아웃(콜드스타트, 대형 모델 로딩, 스트리밍) 조정

대형 LLM은 첫 요청이 특히 느립니다.

  • Knative activator가 개입하는 구간에서 기본 타임아웃에 걸림
  • Istio 라우트 타임아웃이 짧음
  • 클라이언트(예: ALB, API Gateway, NGINX)가 먼저 끊음

증상:

  • 일정 시간(예: 30초, 60초) 후 규칙적으로 502 또는 504
  • vLLM 로그에는 요청이 들어왔는데 응답을 쓰기 전에 커넥션이 끊김

대응:

  • Knative 사용 시 revision timeout을 늘리거나, scale-to-zero를 끄고 최소 레플리카를 1로 유지
  • Istio VirtualService/DestinationRule의 타임아웃을 워크로드 특성에 맞게 증가
  • 스트리밍 응답을 쓰는 경우, 중간 프록시가 chunked를 제대로 통과시키는지 확인

이 단계는 “로그상 요청 처리 시간”과 “프록시 타임아웃”을 함께 봐야 합니다.


8단계: 리소스/GPU 스케줄링과 OOM 재시작 루프 확인

vLLM은 GPU가 없거나, GPU는 있는데 메모리가 부족하거나, 노드에 스케줄이 안 되면 준비 상태가 되지 않습니다. 이 경우 프록시는 계속 502를 냅니다.

kubectl get pod -n <namespace> -o wide
kubectl describe pod -n <namespace> <pod>

# 재시작/종료 코드 확인
kubectl get pod -n <namespace> <pod> -o jsonpath='{.status.containerStatuses[*].restartCount}'
kubectl logs -n <namespace> <pod> --previous --tail=200

자주 나오는 패턴:

  • OOMKilled로 계속 재시작
  • nvidia.com/gpu 요청은 했는데 노드에 여유가 없어 Pending
  • GPU는 잡았지만 CUDA out of memory로 초기 로딩 실패

실무 팁:

  • 모델 크기에 맞춰 --gpu-memory-utilization을 보수적으로 시작

  • --max-model-len을 줄여 KV 캐시 상한을 낮춤

  • 필요하면 양자화(4bit 등)로 VRAM 요구량을 줄이되, 실패 케이스가 많으니 체크리스트를 갖고 접근

  • PyTorch 모델 4bit 양자화 실패 7가지와 해법


재현 가능한 최소 예시: vLLM을 KServe InferenceService로 띄우는 형태

클러스터/버전마다 세부 필드는 달라질 수 있지만, “어디를 건드려야 하는지” 감을 잡기 위한 골격 예시입니다. 부등호 문자는 MDX 빌드 이슈가 있어 인라인 코드로 표기합니다.

apiVersion: serving.kserve.io/v1beta1
kind: InferenceService
metadata:
  name: vllm-llama
  namespace: default
spec:
  predictor:
    containers:
      - name: vllm
        image: vllm/vllm-openai:latest
        args:
          - --model
          - meta-llama/Llama-2-7b-hf
          - --host
          - 0.0.0.0
          - --port
          - "8000"
        ports:
          - containerPort: 8000
        env:
          - name: HF_TOKEN
            valueFrom:
              secretKeyRef:
                name: hf-token
                key: token
        resources:
          limits:
            nvidia.com/gpu: "1"
            memory: "24Gi"
            cpu: "4"
          requests:
            nvidia.com/gpu: "1"
            memory: "16Gi"
            cpu: "2"

이후 내부 점검은 다음 순서로 진행하면, 502의 원인을 빠르게 좁힐 수 있습니다.

kubectl get isvc -n default vllm-llama
kubectl describe isvc -n default vllm-llama
kubectl get pod -n default -l serving.kserve.io/inferenceservice=vllm-llama
kubectl logs -n default <pod> -c vllm --tail=200
kubectl exec -n default -it <pod> -- sh -lc 'curl -sS localhost:8000/health'

결론: 502는 “네트워크 에러”가 아니라 “준비 안 된 업스트림”인 경우가 대부분

KServe에서 vLLM 배포 후 502가 나면, 무작정 Ingress부터 의심하기보다 아래 8단계를 위에서 아래로 밟는 게 가장 빠릅니다.

  1. 502가 발생하는 레이어를 분리(게이트웨이 로그)
  2. InferenceService 조건(Ready/RoutesReady)을 YAML로 확인
  3. Service 뒤 엔드포인트 존재 여부 확인
  4. vLLM이 실제로 리스닝/모델 로딩 완료했는지 로그 확인
  5. 프로브를 vLLM 로딩 시간에 맞게 조정(startupProbe 중심)
  6. OpenAI 호환 API 경로와 KServe 라우팅 프리픽스 충돌 점검
  7. 콜드스타트/프록시 타임아웃 조정
  8. GPU 스케줄링, OOM, 재시작 루프 확인

이 순서를 지키면 “왜 502인지”가 로그와 오브젝트 상태로 설명 가능해지고, 재발 방지도 쉬워집니다.