- Published on
KServe+Knative GPU 추론 503 해결 - autoscale·warmup
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
KServe를 Knative Serving 위에 올려 GPU 추론을 운영하다 보면, 트래픽이 몰리거나 한동안 유휴 상태였다가 첫 요청이 들어오는 순간 503 Service Unavailable이 튀는 경우가 많습니다. 특히 GPU 컨테이너는 이미지가 크고 모델 로딩이 느리며, CUDA 초기화까지 겹치면 “스케일은 됐는데 아직 준비가 안 된” 구간이 길어집니다.
이 글은 KServe+Knative 환경에서 GPU 추론 503이 발생하는 전형적인 경로를 짚고, autoscale(동시성/메트릭/윈도우) 조정과 warmup(프리워밍) 전략으로 503을 실질적으로 줄이는 방법을 정리합니다.
운영 중 503이 네트워크/엔드포인트 정책 문제로 보일 때도 있어, EKS에서 특정 외부 서비스만 503이 나는 케이스는 별도로 확인해보세요: EKS Pod DNS는 되는데 S3만 503? 엔드포인트 정책
503의 실제 의미: 어디서 503이 나오는가
Knative/KServe 경로에서 503은 보통 아래 중 하나입니다.
- Queue-Proxy가 백엔드로 라우팅할 Ready 엔드포인트가 없음
- Pod가 아직
Ready가 아니거나, Revision이 스케일 중인데 트래픽이 먼저 들어옴
- Pod가 아직
- Activator/Autoscaler가 스케일 결정을 내렸지만 준비 시간이 더 김
scale-to-zero후 첫 요청이 Activator에 걸리고, 새 Pod가 뜨는 동안 타임아웃
- Istio/Ingress 레벨에서 업스트림 엔드포인트 부재
- 서비스 엔드포인트가 비었거나, readiness gating 때문에 엔드포인트 등록이 늦음
- 컨테이너는 뜨지만 모델 로딩이 느려 readiness가 늦게 통과
- GPU 모델 로딩, 토큰나이저 초기화, TRT 엔진 빌드 등
핵심은 “스케일링 속도”와 “준비(ready) 판정 시점”의 불일치입니다. GPU 추론은 준비 시간이 길어 이 불일치가 더 자주 터집니다.
빠른 진단 체크리스트
아래 순서대로 보면 503 원인을 빠르게 좁힐 수 있습니다.
1) Knative Revision/Pod 이벤트에서 스케일·준비 지연 확인
kubectl -n kserve get ksvc
kubectl -n kserve get revisions
kubectl -n kserve describe revision <REVISION_NAME>
kubectl -n kserve get pods -l serving.knative.dev/revision=<REVISION_NAME>
kubectl -n kserve describe pod <POD_NAME>
kubectl -n kserve logs <POD_NAME> -c user-container --tail=200
kubectl -n kserve logs <POD_NAME> -c queue-proxy --tail=200
user-container로그에서 모델 로딩이 몇 초/몇 분 걸리는지queue-proxy로그에서no ready endpoints류 메시지가 있는지
2) Activator가 개입하는지 확인
scale-to-zero가 켜져 있으면 Activator 경유 가능성이 큽니다.
kubectl -n knative-serving get deploy activator
kubectl -n knative-serving logs deploy/activator --tail=200
3) 503이 “외부 의존성” 때문에 발생하는지 분리
모델 로딩 중 S3/GCS에서 weight를 받거나, 외부 토큰나이저 리소스를 받다가 실패하면 readiness가 계속 false로 남을 수 있습니다. 이때는 애플리케이션 로그에 네트워크 오류가 찍히며 503이 연쇄적으로 보입니다.
VPC 엔드포인트/정책 이슈로 특정 서비스만 503이 나는 패턴은 앞서 링크한 글이 도움이 됩니다.
원인 1: scale-to-zero 콜드스타트로 첫 요청 503
GPU 추론은 콜드스타트 비용이 큽니다.
- 이미지 pull
- 드라이버/런타임 초기화
- 모델 다운로드 및 메모리 적재
- 워커 프로세스 기동
이 동안 Knative는 트래픽을 받을 준비가 안 된 Revision에 라우팅할 수 없고, 타임아웃/엔드포인트 부재로 503이 날 수 있습니다.
해결: 최소 레플리카 고정으로 콜드스타트 제거
가장 확실한 방법은 minScale을 1 이상으로 두어 항상 warm Pod를 유지하는 것입니다.
KServe InferenceService 예시:
apiVersion: serving.kserve.io/v1beta1
kind: InferenceService
metadata:
name: gpu-llm
namespace: kserve
annotations:
autoscaling.knative.dev/minScale: "1"
autoscaling.knative.dev/maxScale: "5"
autoscaling.knative.dev/target: "1"
spec:
predictor:
containers:
- name: kserve-container
image: <YOUR_IMAGE>
resources:
limits:
nvidia.com/gpu: "1"
cpu: "4"
memory: "16Gi"
minScale을 올리면 비용은 증가하지만 503과 p99 지연이 크게 줄어듭니다.- GPU는 특히
minScale: 1이 “운영 안정성” 관점에서 거의 필수인 경우가 많습니다.
원인 2: 동시성(concurrency) 과대 설정으로 준비는 됐지만 과부하 503
Knative는 기본적으로 “동시 요청 수”를 기준으로 스케일링합니다. GPU 추론은 동시성을 과하게 주면 다음이 발생합니다.
- GPU 메모리 급증으로 OOM
- 큐 적체로 타임아웃
- 워커가 과부하 상태에서 health/readiness 실패
해결: target 동시성 낮추고, burst를 제한
아래처럼 target을 낮춰 더 빨리 스케일아웃되게 하고, 순간 버스트를 통제합니다.
metadata:
annotations:
autoscaling.knative.dev/metric: "concurrency"
autoscaling.knative.dev/target: "1"
autoscaling.knative.dev/targetBurstCapacity: "0"
target: 1은 “Pod 하나가 동시에 처리할 요청”을 1로 본다는 의미에 가깝습니다.- LLM/디퓨전처럼 GPU 점유가 큰 워크로드는 동시성 1 또는 2부터 시작해 측정 기반으로 올리는 편이 안전합니다.
targetBurstCapacity는 Activator가 버스트를 흡수하는 방식과 연관되는데, 운영 환경/인그레스 구성에 따라 체감이 달라집니다. 503이 버스트에서 터진다면 “버스트를 줄이고 스케일아웃을 빠르게”가 기본 방향입니다.
원인 3: 스케일아웃은 되는데 준비 시간 때문에 라우팅 공백 발생
Pod가 생성된 뒤 Ready가 되기까지 시간이 길면, 스케일아웃 신호가 와도 트래픽이 계속 503을 받을 수 있습니다.
해결 A: readinessProbe를 “모델 준비 완료” 기준으로 설계
단순히 프로세스가 떠 있는지로 readiness를 통과시키면, 실제로는 모델이 로딩 중인데 트래픽이 들어와 실패할 수 있습니다. 반대로 너무 엄격하면 Ready가 늦어져 503이 늘어납니다.
권장 패턴은 애플리케이션 내부에 /readyz를 두고, 모델 로딩 완료 시점에만 200을 반환하는 것입니다.
spec:
predictor:
containers:
- name: kserve-container
readinessProbe:
httpGet:
path: /readyz
port: 8080
initialDelaySeconds: 5
periodSeconds: 2
timeoutSeconds: 1
failureThreshold: 60
failureThreshold를 크게 두면 “모델 로딩이 길어도” kubelet이 계속 기다립니다.- readiness가 늦어지는 동안 503이 발생한다면, 근본적으로는
minScale또는 warmup이 더 효과적입니다.
해결 B: startupProbe로 “초기 기동”과 “운영 readiness”를 분리
쿠버네티스 startupProbe를 쓰면 초기 기동(모델 로딩)을 별도로 길게 허용하고, 이후 readiness/liveness는 짧게 가져갈 수 있습니다.
startupProbe:
httpGet:
path: /readyz
port: 8080
periodSeconds: 2
failureThreshold: 180
readinessProbe:
httpGet:
path: /readyz
port: 8080
periodSeconds: 2
failureThreshold: 3
이 조합은 “초기에는 느려도 괜찮고, 운영 중에는 빠르게 이상 감지”에 유리합니다.
원인 4: GPU 스케줄링 지연으로 Pod Pending이 길어짐
스케일아웃 이벤트가 발생해도 GPU 노드에 여유가 없으면 Pod가 Pending에 오래 머물고, 그동안 503이 지속됩니다.
해결: maxScale을 현실적으로, GPU 노드 오토스케일 연동
maxScale을 GPU 노드 수용량 이상으로 올리면 “스케일은 하려는데 못 뜨는 Pod”가 늘어납니다.- Cluster Autoscaler 또는 Karpenter로 GPU 노드 프로비저닝 시간을 고려해야 합니다.
운영 팁:
kubectl -n kserve get pod -w
kubectl -n kserve describe pod <POD_NAME> | sed -n '1,200p'
Insufficient nvidia.com/gpu가 보이면 스케줄링 병목입니다.
Warmup(프리워밍) 전략 3가지
콜드스타트 503을 줄이는 가장 실전적인 접근은 warmup입니다. 형태는 크게 3가지가 있습니다.
1) minScale로 상시 warm 유지 (가장 단순, 가장 강력)
- 장점: 구현 난이도 최저, 효과 최고
- 단점: 비용 증가
GPU가 비싼 대신, 503과 p99를 없애야 하는 온라인 서비스에서는 가장 많이 선택합니다.
2) 스케줄 기반 프리워밍 (출근/피크 타임 대비)
트래픽 패턴이 명확하면, 피크 전에 미리 레플리카를 올려 “피크 시작 10분” 같은 구간을 커버할 수 있습니다.
- 방법: 크론잡으로
minScale을 일시적으로 올렸다가 내리기
예시(컨셉):
kubectl -n kserve patch ksvc gpu-llm \
--type merge \
-p '{"metadata":{"annotations":{"autoscaling.knative.dev/minScale":"2"}}}'
3) 애플리케이션 레벨 warmup 요청 (모델/커널 캐시 예열)
Pod가 떠도 첫 추론이 느린 경우가 있습니다. CUDA 커널 JIT, TRT 엔진 로딩, 토큰나이저 캐시 등이 원인입니다.
컨테이너 시작 시 warmup을 수행하거나, 별도 사이드카/잡이 내부 엔드포인트로 warmup 호출을 날리면 효과가 있습니다.
Python FastAPI 예시(개념 코드):
import asyncio
from fastapi import FastAPI
app = FastAPI()
model = None
ready = False
@app.on_event("startup")
async def startup():
global model, ready
# 1) 모델 로딩
model = load_model()
# 2) 더미 입력으로 1회 추론해 커널/캐시 예열
_ = model.infer("warmup")
ready = True
@app.get("/readyz")
def readyz():
return {"ready": ready}
이 방식은 minScale을 쓰더라도 “첫 요청만 유난히 느린” 문제를 줄여줍니다.
autoscale 튜닝 가이드: GPU 추론에 맞는 현실적인 기본값
GPU 추론은 CPU 웹서비스와 다르게 “동시성 낮게, 스케일은 빠르게, 준비는 길게 허용”이 기본입니다.
권장 출발점(워크로드에 따라 조정):
autoscaling.knative.dev/metric:concurrencyautoscaling.knative.dev/target:1또는2autoscaling.knative.dev/minScale:1(온라인이면 강력 추천)autoscaling.knative.dev/maxScale: GPU 노드 수용량 기반으로 제한startupProbe/readinessProbe: 모델 로딩 시간 반영
KServe InferenceService 예시(조합):
apiVersion: serving.kserve.io/v1beta1
kind: InferenceService
metadata:
name: gpu-embedder
namespace: kserve
annotations:
autoscaling.knative.dev/metric: "concurrency"
autoscaling.knative.dev/target: "1"
autoscaling.knative.dev/minScale: "1"
autoscaling.knative.dev/maxScale: "10"
spec:
predictor:
containers:
- name: kserve-container
image: <YOUR_IMAGE>
ports:
- containerPort: 8080
resources:
limits:
nvidia.com/gpu: "1"
cpu: "2"
memory: "8Gi"
startupProbe:
httpGet:
path: /readyz
port: 8080
periodSeconds: 2
failureThreshold: 180
readinessProbe:
httpGet:
path: /readyz
port: 8080
periodSeconds: 2
timeoutSeconds: 1
failureThreshold: 3
운영에서 자주 놓치는 함정
1) “503은 인그레스 문제”로 단정하고 시간 낭비
실제로는 준비 지연(모델 로딩) 또는 GPU Pending이 원인인 경우가 훨씬 많습니다. 먼저 Revision 이벤트와 Pod 상태를 보세요.
2) maxScale만 올려서 해결하려다 GPU Pending 폭증
GPU는 무한정 스케일되지 않습니다. maxScale은 “클러스터가 실제로 감당 가능한 상한”으로 잡아야 합니다.
3) readiness를 너무 빨리 통과시켜 첫 요청 실패
“프로세스는 떴지만 모델은 아직” 상태에서 트래픽이 들어오면 5xx가 늘어납니다. /readyz를 모델 준비 완료 기준으로 설계하세요.
4) 외부 스토리지/레지스트리 병목으로 콜드스타트가 길어짐
모델을 원격에서 받는다면 네트워크/권한/엔드포인트 정책이 콜드스타트를 악화시킵니다. 특히 EKS에서 특정 목적지로만 503이 난다면 VPC 엔드포인트 정책을 점검하세요: EKS Pod DNS는 되는데 S3만 503? 엔드포인트 정책
503을 줄이는 “현실적인” 우선순위
- 온라인 서비스면
minScale: 1부터 적용 target동시성을 낮춰 과부하성 503 제거startupProbe/readinessProbe로 준비 시간 모델링- warmup 더미 추론으로 첫 요청 지연/실패 줄이기
- GPU 노드 오토스케일과
maxScale정합성 맞추기
마무리
KServe+Knative에서 GPU 추론 503은 대개 “스케일링은 됐지만 준비가 안 된 시간”에서 발생합니다. CPU 서비스처럼 scale-to-zero를 공격적으로 쓰면 비용은 줄어도, 첫 요청 503과 p99 지연이 운영 이슈로 돌아옵니다.
가장 효과적인 처방은 minScale로 warm Pod를 유지하고, 동시성 기반 autoscale을 보수적으로 설정하며, 모델 로딩과 첫 추론을 readiness/warmup으로 명시적으로 다루는 것입니다. 이 3가지만 제대로 잡아도 503은 체감상 “거의 사라지는” 수준까지 줄일 수 있습니다.