- Published on
KServe+Seldon으로 GPU 모델 롤링배포·카나리
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서빙 중인 GPU 모델을 자주 업데이트해야 하는 팀이라면, 단순히 Deployment 이미지 태그만 바꾸는 방식은 금방 한계에 부딪힙니다. 새 모델이 로드되는 동안 GPU 메모리가 순간적으로 두 배 필요할 수 있고, 워밍업이 끝나기 전에 트래픽이 유입되면 지연이 튀거나 오류가 발생합니다. 또한 새 모델이 품질 이슈를 내면 즉시 되돌릴 수 있어야 합니다.
이 글에서는 Kubernetes 환경에서 KServe와 Seldon을 활용해 다음을 구현하는 방법을 다룹니다.
- GPU 추론 워크로드의 무중단 롤링 배포
- 새 버전을 소량 트래픽으로 검증하는 카나리 배포
- 오토스케일, 워밍업, 리소스 설계, 관측과 롤백까지 포함한 운영 체크리스트
관측과 디버깅은 결국 분산 추적이 핵심이므로, 운영 단계에서 트레이싱을 붙이는 패턴은 AutoGPT 툴콜 무한루프, OpenTelemetry로 추적하기 글의 접근을 같이 참고하면 도움이 됩니다.
왜 KServe와 Seldon을 같이 쓰나
둘 다 “모델 서빙” 영역에서 자주 언급되지만 역할이 약간 다릅니다.
- KServe: Kubernetes 네이티브 모델 서빙 컨트롤 플레인.
InferenceServiceCRD로 배포, 스케일링, 네트워킹, 트래픽 분할(특정 구성) 등을 제공합니다. Knative 기반인 경우 scale-to-zero, 요청 기반 오토스케일 등 운영 편의가 큽니다. - Seldon Core: 추론 그래프(프리/포스트 프로세싱), A/B 테스트, 멀티모델 라우팅, 설명가능성 등 “서빙 파이프라인”을 유연하게 구성하는 데 강점이 있습니다.
현실적인 조합은 보통 다음 중 하나입니다.
- KServe로 표준화된 배포/오토스케일을 가져가고, 모델 서버는
predictor로 붙인다. - Seldon으로 추론 그래프와 실험을 구성하고, 내부에서 모델 서버(예: Triton)를 사용한다.
이 글은 “GPU 모델을 안전하게 교체”하는 문제에 초점을 두므로, KServe의 배포/운영 표준화와 Seldon의 카나리/실험 운영 패턴을 함께 보는 관점으로 설명합니다.
GPU 모델 롤링 배포의 핵심 함정
GPU 추론은 CPU 웹 서비스 롤링 배포보다 까다롭습니다.
1) 순간 GPU 메모리 2배 문제
롤링 업데이트 동안 구버전 Pod와 신버전 Pod가 동시에 떠 있으면, 노드 GPU 메모리가 부족해 스케줄링이 실패하거나 OOM이 납니다. 특히 큰 LLM, 멀티 GPU 텐서 병렬 모델에서 흔합니다.
해결 방향
maxSurge=0에 가깝게 “한 번에 하나”로 교체하거나- 카나리 Pod를 별도 노드풀에 띄우거나
- MIG 또는 GPU 파티셔닝으로 가용 단위를 쪼개거나
- 새 버전은 소수 replica로 먼저 띄우고, 안정화 후 점진 확장
2) 워밍업 없는 트래픽 유입
모델 로딩, 엔진 빌드, 캐시 워밍업이 끝나기 전에 트래픽이 들어오면 p95, p99 지연이 급증합니다.
해결 방향
readinessProbe를 “모델 준비 완료” 기준으로 엄격히- 초기 워밍업 요청을
postStart훅 또는 별도 Job로 수행 - 서버 레벨에서
--strict-readiness같은 옵션을 활용
3) 오토스케일이 GPU 현실을 모른다
요청 기반 오토스케일이 GPU 메모리, 배치 크기, KV 캐시 등을 고려하지 않으면 쉽게 과부하가 납니다.
해결 방향
- 동시성 제한(
containerConcurrency또는 서버 설정) - 큐잉(서버 내부 배치)과 HPA 지표를 분리
- GPU 노드풀과 스케일 정책을 별도 운영
아키텍처: 카나리 + 롤링의 “안전한 순서”
운영에서 가장 안전한 순서는 보통 이렇습니다.
- 신버전 Pod를 소수로 먼저 기동 (트래픽 거의 없음)
- 워밍업 완료 및 readiness 통과 확인
- **카나리 트래픽 1%~10%**로 품질/지연/에러율 관측
- 문제 없으면 25% → 50% → 100%로 점진 전환
- 완전 전환 후 구버전 제거
이때 “롤링”은 단순 이미지 교체가 아니라, 트래픽을 분할한 상태에서 두 버전을 공존시키는 것을 의미합니다.
KServe로 GPU InferenceService 구성
아래는 Triton 기반 GPU 추론 서버를 KServe InferenceService로 올리는 예시입니다. 핵심은 다음입니다.
resources.limits에 GPU를 명시readinessProbe를 모델 준비 완료 기준으로- 모델 저장소는 PVC 또는 오브젝트 스토리지(환경에 따라)
apiVersion: serving.kserve.io/v1beta1
kind: InferenceService
metadata:
name: llm-triton
namespace: ml-serving
spec:
predictor:
containers:
- name: triton
image: nvcr.io/nvidia/tritonserver:24.01-py3
args:
- tritonserver
- --model-repository=/models
- --http-port=8000
- --grpc-port=8001
- --metrics-port=8002
ports:
- containerPort: 8000
name: http
- containerPort: 8002
name: metrics
resources:
limits:
nvidia.com/gpu: "1"
cpu: "4"
memory: 16Gi
requests:
cpu: "2"
memory: 8Gi
volumeMounts:
- name: model-repo
mountPath: /models
readinessProbe:
httpGet:
path: /v2/health/ready
port: 8000
initialDelaySeconds: 10
periodSeconds: 5
timeoutSeconds: 2
failureThreshold: 12
volumes:
- name: model-repo
persistentVolumeClaim:
claimName: triton-model-pvc
롤링 배포에서 중요한 KServe 설정 포인트
- PodDisruptionBudget: 노드 유지보수, 드레인 시 동시 다운 방지
- terminationGracePeriodSeconds: 진행 중 요청 처리 시간 확보
- preStop hook: 로드밸런서에서 트래픽을 빼고 종료(서버가 지원한다면)
예를 들어 “종료 전에 드레인” 시간을 주려면 다음 같은 패턴을 씁니다.
spec:
predictor:
containers:
- name: triton
lifecycle:
preStop:
exec:
command: ["/bin/sh", "-c", "sleep 15"]
terminationMessagePolicy: FallbackToLogsOnError
terminationGracePeriodSeconds: 60
sleep는 만능은 아니지만, Ingress나 서비스 메시가 엔드포인트 제거를 반영할 시간을 확보하는 데 유용합니다.
Seldon으로 카나리 트래픽 분할(개념과 구현)
Seldon에서 카나리는 전통적으로 predictor를 두 개 두고 traffic weight로 분산하는 방식이 흔합니다. 또한 프리/포스트 프로세싱, 라우팅, 실험 로직을 함께 넣을 수 있습니다.
아래는 Seldon SeldonDeployment에서 “기본 모델”과 “카나리 모델”을 90/10으로 분할하는 예시(개념 예시)입니다.
apiVersion: machinelearning.seldon.io/v1
kind: SeldonDeployment
metadata:
name: llm-canary
namespace: ml-serving
spec:
name: llm-canary
predictors:
- name: stable
replicas: 2
traffic: 90
graph:
name: triton-stable
implementation: TRITON_SERVER
modelUri: pvc://triton-model-pvc/stable
componentSpecs:
- spec:
containers:
- name: triton-stable
resources:
limits:
nvidia.com/gpu: "1"
- name: canary
replicas: 1
traffic: 10
graph:
name: triton-canary
implementation: TRITON_SERVER
modelUri: pvc://triton-model-pvc/canary
componentSpecs:
- spec:
containers:
- name: triton-canary
resources:
limits:
nvidia.com/gpu: "1"
주의할 점
- 실제 필드/구현체는 설치된 Seldon 버전, 사용 런타임에 따라 달라질 수 있습니다. 위 예시는 “트래픽 weight로 두 버전을 공존”시키는 구조를 보여주기 위한 것입니다.
- 카나리 Pod는 별도 노드풀로 보내는 것이 안전합니다.
nodeSelector,tolerations,affinity로 분리하세요.
실전 운영 패턴: “두 단계 배포”로 GPU 리스크 줄이기
GPU 모델 교체를 안정적으로 하려면, 배포를 두 단계로 나누는 것이 좋습니다.
1단계: 카나리 전용 InferenceService 또는 Predictor 추가
- 신버전은
replicas=1로 최소화 - 워밍업 완료 확인
- 내부 트래픽(운영자, 배치 리퀘스트)으로 먼저 검증
2단계: 트래픽 분할 시작
- 1%에서 시작해 지표가 안정적이면 5% → 10% → 25% → 50% → 100%
- 각 단계는 “최소 관측 윈도우”를 둡니다(예: 10분, 30분)
이때 “재시도 폭주”가 생기면 카나리의 에러가 전체 트래픽을 흔듭니다. 클라이언트 백오프/리트라이를 반드시 점검하세요. API 호출 계층이 있다면 OpenAI 429/RateLimitError 실전 백오프·리트라이에서 소개한 지수 백오프, 지터, 부분 실패 처리 전략을 내부 호출에도 동일하게 적용하는 것이 안전합니다.
GPU 노드 스케줄링: 카나리 격리 전략
카나리에서 가장 무서운 상황은 “신버전이 GPU를 잡아먹어 안정 버전이 축출”되는 것입니다. 이를 피하려면 물리적으로 격리하세요.
stable은gpu-pool-stable노드풀canary는gpu-pool-canary노드풀
예시 스니펫입니다.
spec:
template:
spec:
nodeSelector:
nodepool: gpu-pool-canary
tolerations:
- key: "nvidia.com/gpu"
operator: "Exists"
effect: "NoSchedule"
또는 affinity로 특정 GPU SKU(A10, L4, A100)를 고정해 “동일 성능 조건에서만” 비교가 가능하게 만드세요.
워밍업과 Readiness: 카나리 성공의 80%
카나리 실패의 상당수는 “모델이 아직 준비 안 됐는데 트래픽이 들어온 것”입니다.
권장 체크리스트
- readinessProbe는 “프로세스 살아있음”이 아니라 “모델 로드 완료”를 의미해야 함
- 모델 로딩이 긴 경우
initialDelaySeconds를 늘리는 대신failureThreshold와periodSeconds로 총 대기 시간을 설계 - 워밍업 요청을 고정된 입력으로 10회~수십 회 수행해 커널 컴파일, 캐시 준비
Kubernetes Job으로 워밍업을 수행하는 예시입니다.
apiVersion: batch/v1
kind: Job
metadata:
name: llm-warmup
namespace: ml-serving
spec:
template:
spec:
restartPolicy: Never
containers:
- name: warmup
image: curlimages/curl:8.5.0
command:
- /bin/sh
- -c
- |
for i in $(seq 1 30); do
curl -sS -X POST \
http://llm-triton.ml-serving.svc.cluster.local/v2/models/your_model/infer \
-H 'Content-Type: application/json' \
-d '{"inputs":[]}' > /dev/null || exit 1
sleep 1
done
본문의 엔드포인트 경로는 사용하는 런타임에 맞게 조정하세요.
관측: 카나리에서 봐야 할 지표 6가지
카나리는 “정확도”만 보면 실패합니다. 운영 관점의 지표가 같이 필요합니다.
- 에러율: 5xx, 타임아웃, OOM
- 지연: p50, p95, p99(특히 p99)
- GPU 메모리/사용률: 메모리 누수, KV 캐시 폭증
- 큐 길이/대기 시간: 내부 배치 큐가 병목인지
- 스케일 이벤트: 오토스케일로 replica가 늘 때 지연이 안정적인지
- 품질 지표: 오프라인 골든셋, 온라인 A/B(가능하면)
요청 단위로 병목을 찾으려면 OpenTelemetry 트레이싱을 붙여 “Ingress → Router → Model server” 구간을 쪼개 보세요. 앞서 언급한 AutoGPT 툴콜 무한루프, OpenTelemetry로 추적하기의 방식처럼, 스팬을 나눠 어디서 시간이 쓰이는지 확인하면 카나리 판단이 빨라집니다.
롤백 전략: 트래픽 weight를 되돌리는 것이 최우선
GPU 모델 롤백은 “이미지 롤백”보다 트래픽을 즉시 안정 버전으로 되돌리는 것이 먼저입니다.
- Seldon traffic을
canary=0,stable=100으로 즉시 변경 - 카나리 Pod는 남겨두되(디버깅 목적), 외부 트래픽은 차단
- 원인이 모델 파일인지, 런타임 옵션인지, GPU 드라이버/노드인지 분리
GitOps를 쓰는 경우, weight 변경은 작은 PR로 빠르게 반영되며 감사 추적도 남습니다.
배포 파이프라인 팁: 이미지/모델 아티팩트 캐시
모델 서빙 업데이트가 잦으면 “배포 시간”이 결국 비용이 됩니다.
- 컨테이너 이미지 레이어 캐시 최적화
- 모델 아티팩트는 오브젝트 스토리지에 두고 노드 로컬 캐시 또는 PVC로 캐싱
- CI에서 캐시가 자주 깨지면 빌드가 느려지고 릴리스 템포가 무너집니다. 파이프라인 최적화는 GitHub Actions 캐시 미스 원인 7가지와 해결도 함께 참고할 만합니다.
정리: 운영에서 통하는 최소 원칙
- GPU 모델은 “롤링”만으로는 부족하고, 카나리 트래픽 분할이 사실상 필수
- readiness는 “서버 기동”이 아니라 “모델 준비 완료”로 정의
- 카나리는 안정 버전을 침범하지 않도록 노드풀 격리가 가장 안전
- 롤백은 이미지가 아니라 트래픽 weight를 즉시 되돌리는 것부터
- 지표는 지연, 에러, GPU, 큐, 스케일, 품질을 함께 본다
KServe와 Seldon을 조합하면 Kubernetes 위에서 이 원칙들을 비교적 일관된 방식으로 구현할 수 있습니다. 다음 단계로는 서비스 메시(Istio) 기반 트래픽 미러링, 요청 샘플링 기반 품질 평가, 자동 승격/자동 롤백(Argo Rollouts 연계)까지 확장해 “사람이 덜 개입하는 카나리”로 발전시키는 것을 권장합니다.