- Published on
KServe+Knative로 GPU 모델 오토스케일 배포
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서론
GPU 추론 서버를 쿠버네티스에 올리면 가장 먼저 부딪히는 문제는 비용과 지연 시간의 줄다리기입니다. GPU는 비싸서 유휴 시간을 최소화하고 싶지만, 모델 로딩과 워밍업 때문에 요청이 들어왔을 때 바로 응답하지 못하면 사용자 경험이 무너집니다. 이 글에서는 KServe와 Knative를 결합해 GPU 모델을 배포하고, 트래픽 기반 오토스케일과 scale-to-zero를 현실적으로 운영하는 방법을 다룹니다.
핵심 목표는 다음과 같습니다.
- KServe
InferenceService로 추론 엔드포인트 표준화 - Knative Serving을 통한 요청 기반 오토스케일링
- GPU 리소스 요청과 노드 스케줄링, 콜드스타트 완화 전략
- 관측과 장애 대응 포인트 정리
전체 아키텍처 개요
KServe는 모델 서빙을 위한 CRD와 라우팅, 런타임 추상화(예: torchserve, triton, sklearnserver)를 제공합니다. Knative Serving은 HTTP 트래픽을 기준으로 Pod 수를 자동 조절하며, minScale: 0일 때는 요청이 없으면 Pod를 0으로 내릴 수 있습니다.
흐름을 간단히 요약하면 다음과 같습니다.
- 사용자가 추론 요청을 보냄
- Knative가 요청을 받아 해당 Revision으로 라우팅
- 트래픽이 증가하면 Knative Autoscaler가 Pod를 늘림
- KServe는 InferenceService 단위로 모델 서버 컨테이너를 관리
- GPU는
nvidia.com/gpu리소스 요청으로 스케줄링
중요한 전제는 GPU 노드에 NVIDIA Device Plugin이 설치되어 있어야 하며, 클러스터 오토스케일러를 쓴다면 GPU 노드 그룹이 확장 가능해야 합니다.
사전 준비 체크리스트
1) GPU 노드와 디바이스 플러그인
GPU 리소스가 보이려면 노드에 다음이 준비되어야 합니다.
- NVIDIA 드라이버 및
containerd또는docker런타임 설정 - NVIDIA Device Plugin DaemonSet
GPU 리소스가 정상인지 확인합니다.
kubectl get nodes -o custom-columns=NAME:.metadata.name,GPU:.status.allocatable.nvidia\.com/gpu
값이 비어 있거나 0이면 디바이스 플러그인, 드라이버, 런타임 설정을 먼저 점검해야 합니다.
2) KServe와 Knative 설치
KServe는 내부적으로 Knative를 쓰는 구성이 일반적입니다. 설치 방식은 환경마다 다르지만, 운영 관점에서는 다음을 확인하는 것이 더 중요합니다.
knative-serving네임스페이스가 존재kserve또는kserve-system네임스페이스가 존재istio또는kourier등 인그레스 레이어가 정상 동작
KServe InferenceService로 GPU 모델 배포
여기서는 가장 이해하기 쉬운 패턴인 predictor에 직접 컨테이너를 넣는 방식을 예로 듭니다. 모델 아티팩트는 S3, GCS, PVC 등으로 가져올 수 있는데, GPU 추론에서는 컨테이너 이미지에 모델을 포함하거나, 시작 시 다운로드하는 두 방식이 흔합니다.
예시: GPU 추론 컨테이너를 사용하는 InferenceService
아래 예시는 GPU 1장을 요청하고, Knative 오토스케일 설정을 어노테이션으로 넣는 형태입니다. 본문에서 부등호는 MDX 빌드 에러를 피하기 위해 > 또는 인라인 코드로 처리합니다.
apiVersion: serving.kserve.io/v1beta1
kind: InferenceService
metadata:
name: llama-gpu
namespace: ml
annotations:
autoscaling.knative.dev/minScale: "0"
autoscaling.knative.dev/maxScale: "5"
autoscaling.knative.dev/target: "2"
autoscaling.knative.dev/scaleDownDelay: "60s"
autoscaling.knative.dev/window: "30s"
spec:
predictor:
containers:
- name: user-container
image: ghcr.io/your-org/llama-infer:0.1.0
ports:
- containerPort: 8080
env:
- name: MODEL_PATH
value: /models
resources:
limits:
nvidia.com/gpu: "1"
cpu: "4"
memory: 16Gi
requests:
cpu: "2"
memory: 8Gi
readinessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 10
periodSeconds: 5
포인트는 다음과 같습니다.
nvidia.com/gpu는 보통requests가 아니라limits에 선언하는 패턴이 많습니다. 다만 클러스터 정책에 따라 다를 수 있으니 표준을 정해 일관되게 운영하세요.target은 동시 요청 기반 튜닝의 시작점입니다. 값이 낮을수록 더 빨리 스케일 아웃하지만 비용이 늘 수 있습니다.scaleDownDelay는 트래픽이 잠깐 줄었을 때 과도하게 스케일 인되어 콜드스타트를 유발하는 것을 완화합니다.
배포 후 상태를 확인합니다.
kubectl -n ml get inferenceservice llama-gpu
kubectl -n ml describe inferenceservice llama-gpu
Knative 오토스케일 동작 방식 이해하기
Knative는 크게 두 가지 지표 기반으로 스케일링합니다.
- 동시성 기반:
autoscaling.knative.dev/target - RPS 기반:
autoscaling.knative.dev/metric: rps설정 시
GPU 추론은 요청당 처리 시간이 길고, 배치 처리 또는 스트리밍 여부에 따라 병목이 달라집니다. 그래서 초기에는 동시성 기반이 운영 단순성이 높습니다.
추천 튜닝 순서
- 먼저
target을 보수적으로 낮게 잡아 SLO를 맞춤 - GPU 사용률과 지연 시간을 관측하며 점진적으로
target을 올림 maxScale로 비용 상한선을 설정- 콜드스타트가 치명적이면
minScale: 1또는 워밍업 전략을 고려
GPU 스케줄링에서 자주 터지는 문제와 해결
1) GPU 노드로 안 올라가는 경우
가장 흔한 원인은 다음입니다.
- GPU 노드에만 있는
taint를 toleration으로 허용하지 않음 - 노드 셀렉터 또는 어피니티 설정 누락
- GPU 리소스가 이미 다른 워크로드에 점유됨
GPU 노드풀을 분리했다면 보통 nodeSelector와 tolerations를 같이 씁니다.
spec:
predictor:
containers:
- name: user-container
image: ghcr.io/your-org/llama-infer:0.1.0
resources:
limits:
nvidia.com/gpu: "1"
nodeSelector:
nodepool: gpu
tolerations:
- key: "nvidia.com/gpu"
operator: "Exists"
effect: "NoSchedule"
환경마다 taint 키가 다르니 kubectl describe node로 실제 값을 확인 후 맞추세요.
2) 콜드스타트가 너무 길다
scale-to-zero에서 첫 요청이 느린 건 어느 정도 필연입니다. 완화책은 여러 층위로 나뉩니다.
- 이미지 풀 최적화: 모델이 이미지에 포함되어 크면 pull 시간이 폭증
- 모델 로딩 최적화: 양자화, lazy load, 메모리 매핑
- 워밍업 요청: 배포 직후 또는 트래픽 예측 시점에 내부적으로 프리웜
minScale조정: 비용을 내고 지연을 줄이는 가장 확실한 방법
운영에서 많이 쓰는 절충안은 minScale: 0을 유지하되, 업무 시간대에만 minScale: 1로 올리는 방식입니다.
3) 스케일 아웃은 되는데 GPU가 부족해 Pending이 쌓인다
이 경우 Knative는 Pod를 늘리지만 스케줄러가 GPU를 못 찾아 Pending이 증가합니다. 해결은 두 갈래입니다.
maxScale을 GPU 총량에 맞게 제한- 클러스터 오토스케일러가 GPU 노드를 늘릴 수 있게 설정
GPU 노드가 늘어나는 데 시간이 걸리므로, 트래픽 스파이크가 잦다면 scaleUp 관련 파라미터를 조정하거나 사전 워밍업을 병행해야 합니다.
트래픽 라우팅과 카나리 배포
KServe는 Revision 기반 배포를 Knative에 위임합니다. 모델 버전 업 시에는 전체 트래픽을 한 번에 바꾸기보다 카나리로 천천히 옮기는 게 안전합니다.
KServe에서는 canaryTrafficPercent 같은 필드를 통해 간단한 카나리를 구성할 수 있습니다. 예시는 환경별로 다르니, 운영에서는 다음을 꼭 확인하세요.
- 새 Revision의 초기 로딩 시간
- 토큰 길이, 입력 크기에 따른 지연 분포 변화
- 에러율 상승 여부
관측: 무엇을 봐야 운영이 쉬워지는가
GPU 모델 서빙에서 최소한 다음을 대시보드로 고정해두면 장애 대응이 빨라집니다.
- Knative: Activator, Autoscaler 관련 지표, 요청 수, 동시성, 큐 길이
- Pod: CPU, 메모리, 재시작 횟수, OOMKilled
- GPU: 사용률, 메모리 사용량, ECC 에러, 온도
- 애플리케이션: p50, p95, p99 지연, 타임아웃, 모델 로딩 시간
네트워크 계층에서 502나 401 같은 문제가 섞이면 원인 분리가 어려워집니다. 인그레스나 프록시를 함께 운영한다면 아래 글의 트러블슈팅 관점이 도움이 됩니다.
운영 팁: 실패 모드별 대응 패턴
1) 이미지 풀 실패, 레지스트리 인증 문제
GPU 이미지는 크기가 커서 풀 실패가 치명적입니다. ImagePullBackOff가 보이면 레지스트리 권한, 토큰 만료, 네트워크를 먼저 의심하세요. 특히 관리형 쿠버네티스에서는 ACR, ECR 같은 레지스트리 연동 이슈가 자주 납니다.
2) 노드 네트워크 이상으로 서빙이 간헐적으로 끊김
GPU 노드풀은 커널, 드라이버, CNI 조합이 달라서 네트워크 이슈가 더 잘 드러나는 편입니다. EKS에서 iptables 모드 충돌 같은 문제는 증상이 애매하게 나타날 수 있습니다.
로컬에서 최소 기능 검증용 간단 서버 예시
아래는 FastAPI로 /v1/models와 /v1/completions 비슷한 형태를 흉내 내는 최소 예시입니다. 실제로는 텐서RT, vLLM, Triton 등으로 대체하겠지만, KServe 배포 파이프라인 검증에는 이런 최소 서버가 유용합니다.
from fastapi import FastAPI
from pydantic import BaseModel
import time
app = FastAPI()
class Req(BaseModel):
prompt: str
@app.get("/health")
def health():
return {"ok": True}
@app.post("/infer")
def infer(req: Req):
# 워밍업 전제의 더미 추론
time.sleep(0.2)
return {"text": f"echo: {req.prompt}"}
컨테이너의 readinessProbe는 반드시 실제로 모델이 요청을 처리할 준비가 되었을 때 성공하도록 설계해야 합니다. 단순히 프로세스가 떠 있다는 이유로 200을 주면, 콜드스타트 시점에 초반 요청이 대량 실패할 수 있습니다.
결론
KServe와 Knative 조합은 GPU 모델 서빙을 쿠버네티스에서 표준화하면서도, 트래픽 기반 오토스케일과 scale-to-zero로 비용을 통제할 수 있는 강력한 선택지입니다. 다만 GPU 환경에서는 스케줄링 제약과 콜드스타트가 운영 난이도를 올리므로, 다음을 우선순위로 잡는 것이 좋습니다.
maxScale로 비용과 GPU 총량을 먼저 가드- 콜드스타트가 SLO를 깨면
minScale또는 워밍업 도입 - GPU 노드풀 taint, selector, 디바이스 플러그인 상태를 표준 점검 항목으로 고정
- 지표와 로그를 Knative 계층과 애플리케이션 계층으로 나눠 관측
이 기본기를 잡아두면, 이후에는 Triton, vLLM, 텐서RT 같은 런타임을 바꿔도 배포와 스케일링의 큰 틀을 유지한 채 성능 최적화에 집중할 수 있습니다.