- Published on
KServe LLM 서빙 503·스케일0 지연 해결법
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서빙 트래픽이 들쭉날쭉한 LLM 서비스에서 KServe의 scale-to-zero는 비용을 크게 줄여주지만, 첫 요청이 503으로 떨어지거나 응답이 수십 초 이상 지연되는 문제가 자주 발생합니다. 특히 GPU 노드 프로비저닝, 대형 모델 가중치 다운로드, 워밍업 미흡이 겹치면 “살아나긴 하는데 첫 요청은 실패” 같은 체감이 생깁니다.
이 글은 KServe 기반 LLM 서빙에서 503과 스케일0 지연을 어디서 발생하는지 레이어별로 쪼개서 진단하고, 재현 가능한 설정 변경으로 해결하는 방법을 정리합니다.
증상 패턴 정리: 503은 어디서 나오나
KServe는 보통 Knative Serving 위에서 동작합니다. 요청 경로를 단순화하면 다음과 같습니다.
- Ingress Gateway 또는 Kourier/Istio
- Knative Activator(스케일0일 때)
- Revision Pod의
queue-proxy - 사용자 컨테이너(예: vLLM, TGI, Triton, custom server)
503은 주로 아래 상황에서 발생합니다.
- 스케일0에서 스케일업 중: Activator가 백엔드가 준비되지 않았다고 판단해
503을 반환 - 프로브 실패: readiness/liveness가 계속 실패해 트래픽이 붙지 못함
- 컨테이너는 떴지만 모델 로딩이 끝나지 않음: 서버 포트는 열렸으나 실제 추론 준비가 안 된 상태
- 리소스 부족: GPU 할당 실패, 이미지 풀 실패, 노드 부족으로 Pod가 Pending에 머무름
이미지 풀/권한 문제로 Pod가 올라오지 못하는 케이스도 흔합니다. 이 경우 503은 “결과”이고, 실제 원인은 이벤트에 남습니다. 관련 진단 루틴은 EKS Pod ImagePullBackOff 401 해결 가이드나 K8s Pod ImagePullBackOff - ECR 403 해결 가이드도 함께 참고하면 좋습니다.
1단계: 503의 원인 레이어를 로그로 식별
Knative/Activator에서 503이 나는지 확인
KServe가 Knative를 사용 중이라면 Activator 로그가 가장 먼저입니다.
kubectl -n knative-serving get pods -l app=activator
kubectl -n knative-serving logs -l app=activator --tail=200
Activator 로그에 “no ready endpoints” 류 메시지가 반복되면, 스케일업은 시작됐지만 Revision Pod가 Ready가 되지 못한 상태입니다.
Revision Pod 상태와 이벤트 확인
InferenceService가 만든 Revision을 찾기 어렵다면, 우선 KServe 리소스부터 따라갑니다.
kubectl get inferenceservice -A
kubectl describe inferenceservice -n <namespace> <name>
describe 출력에서 URL, predictor deployment/revision 정보를 확인한 뒤 Pod 이벤트를 봅니다.
kubectl -n <namespace> get pods
kubectl -n <namespace> describe pod <pod-name>
kubectl -n <namespace> get events --sort-by=.metadata.creationTimestamp | tail -n 50
여기서 Pending, ImagePullBackOff, CrashLoopBackOff, Readiness probe failed 같은 단서가 대부분 나옵니다. CrashLoopBackOff로 이어진다면 원인별 진단은 Kubernetes CrashLoopBackOff 10가지 원인과 15분 진단를 같이 보면 빠릅니다.
2단계: scale-to-zero 콜드스타트 지연의 핵심 원인 4가지
LLM은 일반 웹앱과 달리 콜드스타트 비용이 큽니다. 지연을 구성요소로 분해하면 대략 아래입니다.
- 노드 준비 시간: GPU 노드가 없으면 Cluster Autoscaler 또는 Karpenter가 노드를 띄움
- 이미지 Pull: 수 GB 이미지면 수십 초가 걸릴 수 있음
- 모델 가중치 다운로드/마운트: S3, PVC, HF Hub에서 가져오면 병목이 큼
- 엔진 워밍업: vLLM/TGI가 그래프 컴파일, KV 캐시 준비, 첫 토큰 지연
따라서 “KServe 설정만” 만져서는 한계가 있고, 노드/이미지/모델/서버를 같이 최적화해야 합니다.
3단계: 503 방지용 최소 설정: 프로브와 타임아웃을 현실화
readinessProbe를 “포트 오픈”이 아니라 “모델 준비 완료”로
LLM 서버가 포트만 열고 모델 로딩 중이면, readiness가 너무 빨리 성공해 트래픽이 붙고 실패할 수 있습니다. 반대로 readiness가 너무 엄격하면 스케일업이 늦어져 Activator가 503을 오래 반환합니다.
권장 패턴은 다음 중 하나입니다.
- 서버가
/health/ready같은 엔드포인트에서 “모델 로딩 완료”를 반환 - 최소한
/v1/models같은 모델 목록 API가 정상 응답할 때 Ready
아래는 KServe InferenceService에서 predictor 컨테이너에 프로브를 주는 예시입니다.
apiVersion: serving.kserve.io/v1beta1
kind: InferenceService
metadata:
name: llm
namespace: ai
spec:
predictor:
containers:
- name: user-container
image: myrepo/vllm:latest
ports:
- containerPort: 8000
readinessProbe:
httpGet:
path: /health/ready
port: 8000
initialDelaySeconds: 5
periodSeconds: 2
timeoutSeconds: 1
failureThreshold: 60
livenessProbe:
httpGet:
path: /health/live
port: 8000
initialDelaySeconds: 30
periodSeconds: 10
포인트는 failureThreshold를 늘려서 “모델 로딩이 길어도 죽지 않게” 하고, readiness 기준을 모델 준비 완료로 맞추는 것입니다.
Knative 요청 타임아웃 조정
콜드스타트가 길면 첫 요청이 라우팅되기 전에 타임아웃으로 끊길 수 있습니다. Knative는 리비전 단위 timeout을 어노테이션으로 줄 수 있습니다.
metadata:
annotations:
serving.knative.dev/timeoutSeconds: "300"
이 값은 무작정 늘리기보다, “최악의 콜드스타트 시간”을 측정해서 그보다 약간 크게 잡는 게 좋습니다.
4단계: scale-to-zero는 유지하되 503을 줄이는 트래픽 전략
Activator가 버티는 동안 첫 요청을 실패시키지 않기
현실적으로 콜드스타트 중에는 응답이 늦어집니다. 사용자 경험을 위해 다음 중 하나를 선택합니다.
- 클라이언트 재시도: 첫 요청
503을 재시도/백오프로 흡수 - 프론트 프록시 큐잉: API Gateway에서 큐잉 또는 서킷브레이커
LLM API 호출 측 재시도는 특히 중요합니다. OpenAI 호환 API를 제공한다면, 레이트리밋뿐 아니라 일시적 503도 같은 방식으로 다루는 게 실전적입니다. 재시도/백오프 설계는 OpenAI 429/Rate Limit 대응 - 재시도·백오프·큐잉에서 다룬 패턴을 그대로 가져올 수 있습니다.
예시로, Python에서 503에 지수 백오프를 적용합니다.
import time
import random
import requests
URL = "https://llm.example.com/v1/chat/completions"
def post_with_retry(payload, max_attempts=6):
for attempt in range(1, max_attempts + 1):
r = requests.post(URL, json=payload, timeout=60)
if r.status_code < 500:
return r
# 5xx: exponential backoff + jitter
sleep = min(2 ** attempt, 30) + random.random()
time.sleep(sleep)
return r
서버가 스케일업되는 동안 1~2회 정도의 재시도로 대부분 흡수됩니다.
5단계: “스케일0 지연” 자체를 줄이는 6가지 실전 최적화
1) minScale로 완전 0을 피하는 하이브리드
비용이 조금 들더라도, 특정 시간대나 특정 모델은 minScale=1이 체감 품질을 크게 올립니다. KServe/Knative 조합에 따라 어노테이션이 달라질 수 있지만, 일반적으로는 Knative autoscaling 설정을 씁니다.
metadata:
annotations:
autoscaling.knative.dev/minScale: "1"
autoscaling.knative.dev/maxScale: "5"
트래픽이 낮아도 최소 1개 Pod를 유지해 503과 첫 토큰 지연을 거의 제거합니다.
2) GPU 노드 웜풀 유지
스케일0의 가장 큰 적은 “GPU 노드가 없다”입니다. 해결책은 두 가지입니다.
- GPU 노드를 1대 이상 상시 유지
- Karpenter 사용 시 GPU 노드 프로비저닝을 빠르게 만들고 AMI/드라이버 준비를 최적화
운영에서는 “모델 Pod는 0으로 내려가도 GPU 노드는 1대 유지”가 비용 대비 효과가 좋습니다.
3) 이미지 Pull 최적화
- 이미지 사이즈 줄이기(불필요한 빌드 아티팩트 제거)
- 노드에 이미지 프리풀(daemonset로
crictl pull) - 레지스트리 네트워크 병목 제거
이미지 풀 실패나 지연이 의심되면 Pod 이벤트에 Pulling image 구간이 얼마나 오래 걸리는지 확인하세요.
4) 모델 가중치 배포 전략 바꾸기
대형 LLM에서 가장 큰 지연은 가중치입니다. 대표적인 전략은 아래입니다.
- PVC에 미리 다운로드: Pod 시작 시 다운로드를 없애고 마운트만 수행
- 노드 로컬 캐시: 동일 노드에서 재기동 시 재다운로드 방지
- 오브젝트 스토리지 + initContainer: 시작 전에 검증된 경로로 동기화
initContainer로 모델을 미리 받아두는 예시입니다.
spec:
predictor:
containers:
- name: user-container
image: myrepo/tgi:latest
volumeMounts:
- name: model
mountPath: /models
volumes:
- name: model
persistentVolumeClaim:
claimName: llm-model-pvc
핵심은 “서빙 컨테이너가 뜨는 동안 다운로드를 하지 않게” 만드는 것입니다.
5) 서버 워밍업 엔드포인트 추가
모델 로딩이 끝난 직후 첫 요청이 느리면, readiness가 성공한 뒤에 워밍업을 한 번 수행해두는 방식이 효과적입니다.
- Pod 시작 시
postStart훅에서 간단한 프롬프트 1회 실행 - 또는 별도 워머 Job이 서비스 URL을 주기적으로 호출
주의할 점은 워밍업이 과하면 비용이 늘고, 토큰 생성이 길면 readiness 타이밍과 충돌할 수 있으니 아주 짧게 합니다.
6) 동시성(concurrency)과 큐 설정
LLM은 동시성이 높아지면 지연이 급증합니다. Knative는 컨테이너 동시성 제한을 둘 수 있습니다.
metadata:
annotations:
autoscaling.knative.dev/target: "1"
autoscaling.knative.dev/metric: "concurrency"
이렇게 하면 Pod 하나가 처리하는 동시 요청 수를 낮춰 품질을 유지하고, 대신 더 빨리 스케일아웃하도록 유도할 수 있습니다.
6단계: 체크리스트로 빠르게 수렴하기
운영에서 시간을 아끼려면 아래 순서로 체크하면 됩니다.
kubectl describe inferenceservice에서 Ready 조건과 URL 확인- Activator 로그에서
no ready endpoints여부 확인 - Revision Pod 이벤트에서 Pending 원인 확인
- readinessProbe가 “모델 준비 완료” 기준인지 확인
- 콜드스타트가 노드, 이미지, 모델, 워밍업 중 어디가 병목인지 시간 측정
- 비용 허용 범위 내에서
minScale=1또는 GPU 노드 웜풀 적용
결론: 503은 설정 문제가 아니라 “준비 신호” 문제인 경우가 많다
KServe에서 LLM을 scale-to-zero로 운영할 때 503은 단순 장애라기보다, 트래픽이 들어왔는데 아직 준비가 안 됐다는 신호로 발생하는 경우가 많습니다. 따라서 해결도 “무조건 스케일0 끄기”가 아니라,
- 준비 완료를 정확히 표현하는 프로브
- 콜드스타트 구간을 버티는 타임아웃과 재시도
- 노드, 이미지, 모델 배포를 포함한 엔드투엔드 최적화
이 3가지를 함께 맞추는 방향이 가장 현실적입니다.
다음 단계로는, 실제 콜드스타트 시간을 노드 준비, 이미지 pull, 모델 mount, 모델 load, 첫 토큰으로 계측해서 병목을 수치로 잡아두면 튜닝이 훨씬 빨라집니다.