Published on

KServe+Knative GPU 추론 503·콜드스타트 해결

Authors

서빙이 잘 되던 GPU 추론 서비스가 어느 날부터 간헐적으로 503 Service Unavailable 을 뿜거나, 첫 요청이 수십 초에서 수 분까지 늘어지는 문제는 KServe와 Knative 조합에서 매우 흔합니다. 특히 scale-to-zero 가 켜져 있거나, 모델 로딩과 CUDA 초기화가 길고, readiness 설계가 부정확할 때 증상이 폭발합니다.

이 글은 KServe InferenceService 를 Knative Serving 위에서 운영할 때 발생하는 503과 콜드스타트를 관측 지점별로 분해하고, 재현 가능한 체크리스트적용 가능한 설정 예시로 해결하는 방법을 정리합니다.

운영에서 503은 “원인이 하나”인 경우가 거의 없습니다. API 게이트웨이, Istio, Knative queue-proxy, activator, 사용자 컨테이너, 모델 서버, GPU 드라이버까지 레이어가 많기 때문입니다. 따라서 먼저 어디서 503이 만들어지는지부터 좁히는 것이 핵심입니다.

참고로 503과 타임아웃을 원인별로 쪼개는 접근은 서버리스 계열에서도 동일합니다. 문제 분해 방식은 GCP Cloud Run 503/504 원인별 해결 - 타임아웃·동시성 글의 관점과도 통합니다.

1) 503이 만들어지는 위치부터 특정하기

1-1. 증상별로 레이어를 추정

  • 첫 요청에서만 503 또는 긴 지연: scale-to-zero 이후 콜드스타트, activator 경유, 모델 로딩, GPU 초기화 지연 가능성이 큼
  • 부하가 늘 때 503 증가: 동시성 설정, HPA, queue-proxy backlog, GPU 메모리 부족, OOM-kill, 노드 스케줄링 지연 가능성
  • 짧은 시간에 연속 503: readiness / liveness 설계 문제, probe 실패로 재시작 루프, 또는 containerConcurrency 대비 처리량 부족

1-2. 가장 먼저 확인할 로그와 지표

  1. Knative Revision 상태
kubectl get ksvc -n ml
kubectl get revision -n ml
kubectl describe ksvc <service-name> -n ml
  1. Pod 이벤트로 스케줄링 지연 확인
kubectl get pods -n ml -l serving.knative.dev/service=<service-name>
kubectl describe pod <pod-name> -n ml

여기서 FailedScheduling 이 보이면 GPU 노드 부족, taint/toleration 미스매치, 리소스 요청 과다, 이미지 pull 지연이 주요 원인입니다.

  1. queue-proxy 로그
kubectl logs -n ml <pod-name> -c queue-proxy

context deadline exceeded 류가 보이면 대개 요청이 사용자 컨테이너까지 전달되었으나 응답이 늦어 queue-proxy가 타임아웃을 낸 케이스입니다.

  1. 사용자 컨테이너(모델 서버) 로그
kubectl logs -n ml <pod-name> -c kserve-container

모델 로딩, CUDA 초기화, 엔진 빌드(TensorRT), 가중치 다운로드, 캐시 미스 등이 첫 요청을 폭발시키는 대표 원인입니다.

2) 콜드스타트가 길어지는 GPU 특유의 원인

GPU 추론의 콜드스타트는 CPU 웹서버 콜드스타트보다 변수가 많습니다.

2-1. 모델 로딩과 가중치 다운로드

  • 원격 스토리지(S3, GCS, PVC)에서 가중치를 매번 읽으면 첫 요청이 느려짐
  • KServe storage-initializer 가 모델을 내려받는 동안 readiness가 애매하면, 트래픽이 너무 일찍 유입되어 503이 날 수 있음

대응:

  • 모델 아티팩트는 가능한 한 노드 로컬 캐시 또는 PVC 캐시를 활용
  • 이미지에 모델을 bake-in 하는 방식은 재배포 비용이 있지만 콜드스타트에는 강력

2-2. CUDA 컨텍스트 초기화와 커널 JIT

PyTorch, TensorFlow, Triton, vLLM 류는 첫 GPU 호출에서 CUDA 컨텍스트 생성, 커널 로딩, JIT 컴파일이 발생할 수 있습니다.

대응:

  • 프로세스 시작 시점에 dummy warmup을 수행하고 readiness를 그 이후에 통과시키기
  • 모델 서버가 제공하는 warmup 옵션을 사용

2-3. GPU 스케줄링 지연

Pod가 뜨지 못하는 시간은 “콜드스타트”로 체감됩니다.

  • GPU 노드가 부족하거나, MIG 파티션이 맞지 않거나, nvidia.com/gpu 리소스 요청이 과도하면 스케줄링이 막힘
  • GPU 노드에 taint가 걸려 있는데 toleration이 없으면 영원히 Pending

3) Knative에서 흔한 503 원인: 타임아웃과 동시성

Knative는 요청이 queue-proxy 를 거쳐 사용자 컨테이너로 전달됩니다. 여기서 타임아웃과 동시성 설정이 맞지 않으면 503이 쉽게 발생합니다.

3-1. Revision timeout 조정

기본 타임아웃이 짧으면 첫 요청이나 긴 추론에서 503이 발생합니다. KServe에서는 InferenceServicetimeout 또는 Knative annotation으로 조정합니다.

예시: KServe InferenceService 에서 timeout 늘리기

apiVersion: serving.kserve.io/v1beta1
kind: InferenceService
metadata:
  name: gpu-llm
  namespace: ml
spec:
  predictor:
    timeout: 300
    containers:
      - name: kserve-container
        image: myrepo/llm-server:latest
        resources:
          limits:
            nvidia.com/gpu: 1
            cpu: "4"
            memory: "16Gi"

환경에 따라 timeout 필드 대신 annotation을 쓰는 구성이 있을 수 있습니다. 그 경우 아래처럼 Revision 템플릿에 Knative timeout annotation을 넣습니다.

metadata:
  annotations:
    serving.knative.dev/timeoutSeconds: "300"

주의: timeout을 무작정 늘리면 “느린 서비스가 계속 매달리는” 상태가 되어 backlog가 커질 수 있습니다. 반드시 동시성과 오토스케일 설정을 함께 봐야 합니다.

3-2. containerConcurrency 와 추론 워커 수의 정합성

GPU 추론 서버는 동시 요청을 무한히 받지 못합니다. 동시성이 높으면 GPU 메모리 스파이크, OOM, latency 폭증 후 503으로 이어집니다.

Knative 서비스 수준에서 동시성을 제한하는 방식이 효과적입니다.

metadata:
  annotations:
    autoscaling.knative.dev/metric: "concurrency"
    autoscaling.knative.dev/target: "1"
    autoscaling.knative.dev/minScale: "1"
    autoscaling.knative.dev/maxScale: "5"
spec:
  template:
    spec:
      containerConcurrency: 1
  • containerConcurrency: 1 은 GPU 1장당 1요청 처리로 강제해 tail latency를 줄이는 데 유리
  • 처리량이 필요하면 maxScale 을 늘리거나, 모델 서버 내부에서 배치/큐잉을 지원하는지 확인

3-3. activator 경유로 인한 지연

scale-to-zero 상태에서 첫 요청은 activator를 거쳐 Pod가 뜰 때까지 대기합니다. 이 대기 시간이 timeout을 넘으면 503이 발생할 수 있습니다.

대응:

  • GPU 추론은 minScale: 1 로 scale-to-zero를 끄는 것이 가장 확실
  • 비용 때문에 0으로 내려야 한다면, 별도의 워밍 스케줄러나 프리워밍 Job을 고려

4) 콜드스타트 최소화: minScale, 초기화, readiness 설계

4-1. 가장 확실한 처방: minScale 고정

GPU 추론에서 “첫 요청이 느리다”는 문제를 가장 확실히 없애는 방법은 항상 1개 이상 Pod를 유지하는 것입니다.

metadata:
  annotations:
    autoscaling.knative.dev/minScale: "1"
    autoscaling.knative.dev/maxScale: "10"

운영 팁:

  • 트래픽이 일정 시간대에만 있다면, 시간대별로 minScale 을 조정하는 방식도 가능

4-2. readiness는 “서버가 뜸”이 아니라 “추론 가능”을 의미해야 함

readiness probe가 단순히 HTTP 서버 포트 오픈만 확인하면, 모델 로딩 중에도 트래픽이 들어와 첫 요청이 실패하거나 매우 느려집니다.

권장:

  • readiness endpoint는 모델 로딩 완료, GPU 메모리 할당 완료, 워밍업 완료 이후에만 200을 반환

예시: KServe 컨테이너에 readiness 설정

spec:
  predictor:
    containers:
      - name: kserve-container
        image: myrepo/gpu-model:latest
        readinessProbe:
          httpGet:
            path: /health/ready
            port: 8080
          initialDelaySeconds: 5
          periodSeconds: 2
          timeoutSeconds: 1
          failureThreshold: 60

failureThreshold 를 늘려 초기 준비 시간이 길어도 Pod가 재시작 루프에 빠지지 않게 합니다.

4-3. startup probe로 초기화 구간을 분리

초기화가 긴 컨테이너는 readiness/liveness만으로는 의도치 않은 재시작이 발생할 수 있습니다. Kubernetes startupProbe 를 지원하는 런타임이라면 초기화 구간을 분리하는 것이 안정적입니다.

startupProbe:
  httpGet:
    path: /health/startup
    port: 8080
  periodSeconds: 2
  failureThreshold: 150
livenessProbe:
  httpGet:
    path: /health/live
    port: 8080
  periodSeconds: 10
readinessProbe:
  httpGet:
    path: /health/ready
    port: 8080
  periodSeconds: 2

5) KServe 특유 포인트: storage-initializer, runtime, 모델 서버

5-1. storage-initializer가 병목인지 확인

KServe는 모델을 내려받는 init 단계가 있습니다. 이 시간이 길면 Pod 생성부터 준비 완료까지가 길어져 콜드스타트가 악화됩니다.

체크:

kubectl get pods -n ml <pod-name> -o jsonpath='{.status.initContainerStatuses[*].name}'
kubectl logs -n ml <pod-name> -c storage-initializer

대응:

  • 모델 파일 크기 줄이기(양자화, 샤딩)
  • 스토리지 대역폭 개선
  • 캐시 전략 도입

5-2. 모델 서버 선택에 따른 워밍업/배치 전략

  • Triton: dynamic batching, model repository 구조, warmup 설정을 통해 첫 요청 지연을 줄이기 쉬움
  • vLLM/TGI: KV cache, continuous batching으로 처리량을 올릴 수 있으나, 초기 GPU 메모리 확보와 엔진 준비 시간이 길 수 있음

결론은 “Knative 설정만”으로 해결이 끝나지 않는다는 점입니다. 모델 서버가 제공하는 warmup과 배치 기능을 적극 활용해야 합니다.

6) 부하 시 503: 오토스케일, GPU 메모리, 큐잉

6-1. 스케일아웃이 느리면 503이 먼저 난다

GPU는 노드가 제한적이라 스케일아웃이 즉시 되지 않습니다. 이때 queue-proxy backlog가 증가하고, 결국 타임아웃으로 503이 납니다.

대응:

  • maxScale 을 충분히 크게 잡되, 실제 GPU 노드 수와 맞추기
  • 스케일업 속도를 높이기 위해 노드 오토스케일러 설정 점검
  • 요청을 무조건 밀어넣기보다 클라이언트에서 재시도와 백오프를 설계

재시도 설계는 503뿐 아니라 429에서도 동일한 원리로 동작합니다. 백오프/지터 설계는 OpenAI API 429 Rate Limit 재시도·백오프 설계 의 패턴을 그대로 적용할 수 있습니다.

6-2. GPU OOM은 503으로 보일 때가 많다

컨테이너가 OOM-kill 되면, 외부에서는 “갑자기 503”으로만 관측될 수 있습니다.

체크:

kubectl describe pod <pod-name> -n ml | grep -n "OOM" -n
kubectl get events -n ml --sort-by=.lastTimestamp | tail -n 50

대응:

  • 동시성 낮추기
  • 배치/시퀀스 길이 제한
  • 모델 양자화
  • GPU 메모리 상한을 고려한 워커 수 조정

7) 실전 권장 설정 조합(안정성 우선)

아래는 “GPU 추론 503과 콜드스타트를 줄이는” 쪽으로 치우친 기본값입니다.

  • minScale: 1 로 scale-to-zero 비활성화
  • containerConcurrency: 1 또는 매우 보수적으로 시작
  • timeout을 추론 SLA에 맞게 늘리되, 무한정은 금지
  • readiness는 모델 로딩과 워밍업 완료 이후에만 통과
  • 스토리지 다운로드 병목 제거(PVC 캐시, 로컬 캐시, 이미지 bake-in)

예시 템플릿(핵심만)

apiVersion: serving.kserve.io/v1beta1
kind: InferenceService
metadata:
  name: gpu-infer
  namespace: ml
  annotations:
    autoscaling.knative.dev/minScale: "1"
    autoscaling.knative.dev/maxScale: "8"
    autoscaling.knative.dev/metric: "concurrency"
    autoscaling.knative.dev/target: "1"
    serving.knative.dev/timeoutSeconds: "300"
spec:
  predictor:
    containers:
      - name: kserve-container
        image: myrepo/gpu-infer:latest
        resources:
          limits:
            nvidia.com/gpu: 1
            cpu: "4"
            memory: "16Gi"
        readinessProbe:
          httpGet:
            path: /health/ready
            port: 8080
          periodSeconds: 2
          failureThreshold: 60

8) 디버깅 체크리스트: 30분 안에 원인 좁히기

  1. 503이 어디서 발생하는지
  • Ingress/Istio access log에서 503인지
  • queue-proxy 로그에 timeout이 찍히는지
  • 사용자 컨테이너가 죽는지(OOM, crash) 또는 단순히 느린지
  1. Pod가 늦게 뜨는지
  • FailedScheduling 이벤트
  • 이미지 pull 시간
  • storage-initializer 다운로드 시간
  1. 준비가 늦는지
  • readiness가 너무 일찍 통과하는지 또는 너무 엄격해서 계속 실패하는지
  • 워밍업이 실제로 수행되는지
  1. 부하에서만 터지는지
  • containerConcurrency 대비 요청이 몰리는지
  • GPU 메모리 사용량과 OOM
  • 오토스케일이 실제로 늘어나는지

9) 결론: 503을 “콜드스타트+타임아웃+동시성”으로 분해하라

KServe+Knative GPU 추론에서 503과 콜드스타트는 대개 다음 조합으로 발생합니다.

  • scale-to-zero로 인해 activator 대기 시간이 생기고
  • 모델 다운로드와 GPU 초기화가 길어 readiness 전에 요청이 들어오거나
  • queue-proxy timeout 또는 과도한 동시성으로 tail latency가 폭발

따라서 해결도 세 갈래로 나눠야 합니다.

  • 비용보다 안정성이 중요하면 minScale: 1 로 콜드스타트 자체를 제거
  • 비용을 지켜야 하면 “초기화 완료 후 readiness 통과”와 워밍업으로 첫 요청 실패를 방지
  • 부하 503은 동시성 제한과 timeout 조정, 그리고 GPU 메모리 관리를 함께 적용

이 과정을 거치면 503은 “랜덤 장애”가 아니라, 재현 가능한 병목으로 바뀌고 운영 난이도가 급격히 내려갑니다.