- Published on
KServe v0.12 503와 콜드스타트 줄이는 실전 튜닝
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
KServe v0.12에서 추론 요청이 간헐적으로 503으로 떨어지거나, 첫 요청이 유독 느린(콜드스타트) 문제는 대부분 트래픽 라우팅 계층(게이트웨이/Knative/KServe queue-proxy) 과 Pod 준비 상태(readiness)·모델 로딩 시간의 불일치에서 시작합니다.
특히 GPU 모델(Transformers, vLLM, TensorRT 등)은 컨테이너가 Ready가 된 뒤에도 실제 모델 로딩/워밍업이 끝나지 않아 첫 요청이 타임아웃으로 503이 되거나, 스케일-투-제로 이후 재기동 과정에서 지연이 커지는 패턴이 흔합니다.
이 글에서는 KServe v0.12 기준으로 503을 줄이고 콜드스타트를 완화하는 진단 순서와 설정/아키텍처 선택지를 실전 위주로 정리합니다.
1) KServe 503의 “어디서” 발생하는지 먼저 분리
503 Service Unavailable은 단일 원인이 아니라, 다음 중 하나가 “업스트림을 못 찾거나(엔드포인트 없음)”, “준비가 안 됐거나(Ready 아님)”, “시간 내 응답을 못 받았거나(타임아웃)”일 때 발생합니다.
1-1. 가장 흔한 503 경로
- Gateway/Ingress: 라우팅 대상 서비스가 없거나 업스트림 연결 실패
- Knative activator / queue-proxy: 스케일-투-제로에서 활성화 중이거나, 컨테이너 준비/응답 타임아웃
- KServe predictor 컨테이너: 애플리케이션이 5xx를 직접 반환하거나, 모델 서버가 아직 로딩 중
따라서 “503이 어디서 찍히는지”를 로그로 분리해야 합니다.
1-2. 필수 로그/이벤트 확인
아래 커맨드로 InferenceService 이벤트, Knative 서비스 상태, Pod readiness 전환 시점을 먼저 확인합니다.
# InferenceService 상태/이벤트
kubectl -n ml get isvc
kubectl -n ml describe isvc my-model
# Knative Service/Revision 상태
kubectl -n ml get ksvc
kubectl -n ml get revision
kubectl -n ml describe ksvc my-model-predictor
# Pod 이벤트(스케줄링 지연, 이미지 풀, 볼륨 마운트 등)
kubectl -n ml get pods -l serving.kserve.io/inferenceservice=my-model
kubectl -n ml describe pod -l serving.kserve.io/inferenceservice=my-model
# queue-proxy / activator 로그(존재한다면)
kubectl -n ml logs deploy/activator -c activator --tail=200
여기서 다음 신호가 보이면 원인을 좁힐 수 있습니다.
RevisionMissing/NoReadyEndpoints류: 엔드포인트 자체가 준비되지 않음Readiness probe failed: 준비 프로브가 너무 엄격하거나 경로가 틀림context deadline exceeded/upstream request timeout: 게이트웨이/프록시 타임아웃이 모델 로딩/첫 추론 시간보다 짧음
네트워크 타임아웃/데드라인 이슈의 진단 관점은 gRPC지만 HTTP에도 그대로 통합니다. 타임아웃이 어디 계층에서 걸리는지 분해하는 접근은 이 글도 참고할 만합니다: Go gRPC context deadline exceeded 원인 9가지
2) 콜드스타트의 3대 원인: 스케일-투-제로, 이미지/볼륨, 모델 로딩
콜드스타트는 “Pod가 뜨는 시간”만이 아니라 다음 합입니다.
- 스케일-투-제로에서 0
Pod→ 1Pod로 늘어나는 시간 - 이미지 pull, GPU 노드 스케줄링, PVC 마운트 시간
- 모델 다운로드/로딩 + 그래프 컴파일 + 첫 토큰/첫 배치 워밍업
따라서 해결도 1)~3) 각각에 걸어야 합니다.
3) 503 줄이기: 타임아웃과 프로브를 모델 특성에 맞추기
3-1. readiness/liveness는 “서버 기동”과 “모델 준비”를 분리
많은 모델 서버가 프로세스는 뜨지만 모델이 아직 준비되지 않은 상태가 길게 존재합니다. 이때 readiness가 너무 빨리 Ready로 바뀌면, 첫 요청이 들어와 모델 로딩 중 타임아웃 → 503으로 이어집니다.
권장 패턴은 다음 중 하나입니다.
- 모델 준비 완료 시점까지 readiness를
fail시키기 - 또는 readiness는 가볍게 두되, 첫 요청 타임아웃을 충분히 늘리기
가능하다면 첫 번째가 더 안전합니다. 예를 들어 모델 서버가 /v1/health/ready 같은 엔드포인트를 제공한다면 readiness를 그쪽으로 연결합니다.
apiVersion: serving.kserve.io/v1beta1
kind: InferenceService
metadata:
name: my-model
namespace: ml
spec:
predictor:
containers:
- name: kserve-container
image: myrepo/myserver:latest
ports:
- containerPort: 8080
readinessProbe:
httpGet:
path: /v1/health/ready
port: 8080
initialDelaySeconds: 5
periodSeconds: 2
timeoutSeconds: 1
failureThreshold: 60
livenessProbe:
httpGet:
path: /v1/health/live
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
failureThreshold를 크게 잡는 이유: 모델 로딩이 1~2분 걸리는 환경에서 readiness가 계속 실패해도 “정상적인 준비 과정”으로 간주해야 하기 때문입니다.- liveness는 너무 공격적으로 두면 로딩 중 재시작 루프가 생깁니다.
3-2. 요청 타임아웃을 “첫 추론” 기준으로 재설계
KServe/Knative 경로에는 여러 타임아웃이 겹칩니다. 흔한 실수는:
- 게이트웨이 타임아웃 30초
- queue-proxy 타임아웃 30초
- 애플리케이션 내부 타임아웃 15초
처럼 서로 다른 기본값이 섞여, 어느 계층에서 먼저 끊기는지 모른 채 503만 보게 됩니다.
운영 관점에서는 다음 2개를 분리하는 게 좋습니다.
- 콜드스타트/워밍업 요청의 타임아웃(길게)
- 정상 상태 P99 타임아웃(짧게)
이를 구현하는 방법은 대표적으로 2가지입니다.
- 스케일-투-제로를 끄거나 최소 레플리카를 유지해서 “콜드 요청” 자체를 없애기
- 워밍업 전용 엔드포인트/잡을 두고, 외부 요청은 준비 이후에만 받기(readiness로 차단)
4) 콜드스타트 줄이기: minScale, 초기 스케일, 동시성 튜닝
4-1. 스케일-투-제로를 피하는 가장 확실한 방법: minScale
실시간 트래픽(사용자가 기다리는 UI)이라면 스케일-투-제로는 비용 절감 대신 사용자 경험을 크게 해칠 수 있습니다. 이때는 최소 1개 Pod를 유지하는 것이 가장 단순하고 효과적입니다.
KServe는 Knative 애너테이션을 통해 최소 스케일을 지정할 수 있습니다.
apiVersion: serving.kserve.io/v1beta1
kind: InferenceService
metadata:
name: my-model
namespace: ml
annotations:
autoscaling.knative.dev/minScale: "1"
autoscaling.knative.dev/initialScale: "1"
spec:
predictor:
containers:
- name: kserve-container
image: myrepo/myserver:latest
minScale: "1"은 콜드스타트를 사실상 제거합니다.initialScale은 배포 직후 트래픽 급증에서 503을 줄이는 데 도움이 됩니다.
4-2. 동시성(Concurrency)과 큐잉: 503을 “부하”로 착각하지 않기
LLM/비전 모델은 GPU 메모리와 KV 캐시, 배치 전략에 따라 적정 동시성이 크게 달라집니다. 동시성을 과도하게 올리면:
- 지연이 폭증하고
- queue-proxy에서 대기열이 길어지고
- 결국 타임아웃으로 503이 납니다.
반대로 너무 낮추면 오토스케일이 과도하게 일어나 비용이 증가합니다.
모델이 OOM 또는 지연 폭증을 보인다면, 동시성보다 먼저 메모리/캐시 튜닝이 선행돼야 합니다. 로컬 LLM 기준이지만, “KV 캐시가 지연과 OOM의 핵심 변수”라는 점은 서버 추론에서도 동일합니다: Transformers 로컬 LLM OOM - 4-bit·KV 캐시 튜닝
5) 모델 로딩 시간을 줄이는 배포 패턴
5-1. 모델을 컨테이너 이미지에 굽기 vs 런타임 다운로드
콜드스타트의 가장 큰 변수는 “모델을 어디서 가져오느냐”입니다.
- 런타임 다운로드(S3, GCS, HF Hub): 네트워크/권한/레이트리밋에 취약
- 이미지에 포함: 이미지 크기 증가, 배포 느림, 하지만 실행 시점은 안정적
- PVC에 미리 캐시: 노드 이동 시 마운트/캐시 정책에 따라 편차
실시간 서비스라면 다음 우선순위를 추천합니다.
- PVC 캐시 + 노드 로컬 캐시 최적화(가능하면)
- 또는 이미지에 모델 포함(배포 파이프라인이 받쳐준다면)
- 런타임 다운로드는 개발/실험 단계에서만
5-2. initContainer로 “다운로드와 준비”를 메인 컨테이너에서 분리
메인 컨테이너가 모델 다운로드까지 책임지면 readiness 설계가 복잡해지고, 재시작 시마다 같은 작업을 반복할 수 있습니다. initContainer로 모델을 준비해두면 메인 컨테이너는 “서빙”에만 집중할 수 있습니다.
apiVersion: serving.kserve.io/v1beta1
kind: InferenceService
metadata:
name: my-model
namespace: ml
annotations:
autoscaling.knative.dev/minScale: "1"
spec:
predictor:
volumes:
- name: model-cache
persistentVolumeClaim:
claimName: model-cache-pvc
containers:
- name: kserve-container
image: myrepo/myserver:latest
volumeMounts:
- name: model-cache
mountPath: /models
env:
- name: MODEL_PATH
value: /models/my-model
initContainers:
- name: fetch-model
image: curlimages/curl:8.5.0
command:
- sh
- -c
- |
set -e
test -f /models/my-model/READY && exit 0
mkdir -p /models/my-model
# 예: 오브젝트 스토리지에서 다운로드
curl -L -o /models/my-model/model.bin "$MODEL_URL"
echo ok > /models/my-model/READY
env:
- name: MODEL_URL
valueFrom:
secretKeyRef:
name: model-url
key: url
volumeMounts:
- name: model-cache
mountPath: /models
핵심은:
- initContainer에서
READY마커를 만들고 - 메인 컨테이너는
READY가 있는 경로만 로딩 - 재시작 시 다운로드를 스킵
여기서 마커 문자열 READY처럼 부등호가 없는 값은 안전하지만, 경로/템플릿에 <> 형태를 쓰지 않도록 주의하세요(Next.js MDX 빌드 환경에서 JSX로 오인될 수 있음).
6) 워밍업(Pre-warm)으로 첫 요청 지연과 503을 동시에 줄이기
모델이 준비되자마자 첫 추론이 느린 이유는 다음이 겹치기 때문입니다.
- CUDA 커널 로딩
- 그래프/엔진 빌드(TensorRT 등)
- 토크나이저/캐시 초기화
- 첫 배치에서만 발생하는 메모리 할당
따라서 배포 직후 또는 스케일 아웃 직후에 워밍업 요청을 내부에서 1회 보내면 효과가 큽니다.
6-1. postStart 훅으로 간단 워밍업
lifecycle:
postStart:
exec:
command:
- sh
- -c
- |
# 서버가 떠도 모델 준비까지 시간이 걸릴 수 있으니 재시도
for i in $(seq 1 60); do
curl -sf http://127.0.0.1:8080/v1/health/ready && break
sleep 1
done
# 최소 워밍업 요청(예: 짧은 입력)
curl -sf -X POST http://127.0.0.1:8080/v1/models/my-model:predict \
-H 'Content-Type: application/json' \
-d '{"inputs":"warmup"}' || true
주의점:
- 워밍업이 무거우면 오히려 시작 시간을 늘립니다. “가장 가벼운 입력”으로 1회만.
- 서버가 아직 바인딩 전이면 실패하므로,
ready체크 후 호출합니다.
6-2. CronJob 워밍업(스케일-투-제로 유지하면서 503 완화)
비용 때문에 minScale을 못 쓰는 경우, 트래픽 패턴이 정해져 있다면(예: 오전 9시 피크) CronJob으로 미리 깨우는 방식도 실무에서 자주 씁니다.
apiVersion: batch/v1
kind: CronJob
metadata:
name: warmup-my-model
namespace: ml
spec:
schedule: "55 8 * * 1-5"
jobTemplate:
spec:
template:
spec:
restartPolicy: Never
containers:
- name: warmup
image: curlimages/curl:8.5.0
command:
- sh
- -c
- |
# 외부 라우트(게이트웨이)로 호출해 activator까지 깨우기
curl -sf -X POST "$PREDICT_URL" \
-H 'Content-Type: application/json' \
-d '{"inputs":"warmup"}'
env:
- name: PREDICT_URL
value: "http://my-model.ml.example.com/v1/models/my-model:predict"
이 방식은 “사용자 첫 요청”을 “시스템 워밍업 요청”으로 바꿔 503 체감률을 낮춥니다.
7) GPU 스케줄링 지연이 503로 보일 때: 노드/리소스 전략
GPU Pod은 다음 이유로 Pending 시간이 길어집니다.
- GPU 노드 부족
nodeSelector/taints/tolerations불일치- 큰 이미지 pull
- 디스크 압박으로 이미지 GC
이때 외부에서는 단순히 “엔드포인트 없음”으로 보여 503이 납니다.
운영 팁:
- GPU 노드에 이미지 프리풀(daemonset) 적용
- 모델 캐시 PVC를 해당 노드 풀에 가까운 스토리지로 구성
- 오토스케일 정책이 GPU 노드를 충분히 빨리 늘릴 수 있는지 점검
8) 실전 체크리스트: 503·콜드스타트 개선 순서
- 503이 발생하는 계층을 로그로 특정(게이트웨이 vs queue-proxy vs 앱)
- readiness가 “모델 준비 완료”를 반영하는지 확인
- 타임아웃을 첫 추론 기준으로 재설정(짧은 기본값 방치 금지)
minScale또는initialScale로 콜드스타트 자체를 제거/완화- initContainer로 모델 준비를 분리하고 PVC 캐시로 재시작 비용 절감
- postStart 또는 CronJob으로 워밍업 추가
- GPU 스케줄링/이미지 pull 병목을 제거(프리풀, 노드풀, 스토리지)
9) 마무리: “503은 증상, 원인은 준비·시간·용량”
KServe v0.12에서 503과 콜드스타트는 대개 모델 서버 자체의 문제가 아니라, 준비 상태 신호(readiness)와 라우팅/타임아웃 정책, 그리고 스케일링 전략이 모델의 현실적인 로딩/추론 시간과 맞지 않을 때 발생합니다.
가장 효과가 큰 조합은 보통 다음입니다.
- 실시간 서비스:
minScale: "1"+ readiness를 모델 준비 완료에 연결 + 가벼운 워밍업 - 비용 우선 서비스: 스케일-투-제로 유지 + CronJob 워밍업 + 타임아웃 상향 + 캐시/PVC 최적화
환경(게이트웨이 종류, Knative 설정, 모델 서버)에 따라 세부 파라미터는 달라지니, 현재 사용 중인 KServe 설치 방식(예: Istio/Envoy 기반인지, Knative Serving 버전, GPU/모델 크기)과 함께 503 로그 샘플을 기준으로 튜닝 포인트를 더 좁혀가면 좋습니다.