- Published on
Seldon Core로 GPU 추론 롤링배포·카나리 실전
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
프로덕션에서 GPU 추론을 운영할 때 가장 무서운 순간은 “모델을 바꿔야 하는데, GPU는 비싸고 트래픽은 끊기면 안 되는” 상황입니다. Seldon Core는 SeldonDeployment 중심으로 모델 서빙을 표준화하면서도, Kubernetes 네이티브 배포 전략(롤링, 카나리, 트래픽 미러링)을 연결하기 좋습니다.
이 글에서는 다음을 목표로 합니다.
- GPU 추론 서버를 Seldon Core로 배포하는 기본 골격
- 다운타임 없이 롤링 배포를 안정화하는 체크리스트
- 카나리로 모델 버전을 안전하게 전환하는 트래픽 분할 패턴
- GPU 특유의 워밍업·초기 로딩·메모리 이슈를 배포 전략에 반영
운영 중 자주 맞닥뜨리는 쿠버네티스 이슈(이미지 풀 실패, 크래시 루프, 노드 상태 이상)도 함께 엮어 보겠습니다.
아키텍처 전제: GPU 추론에서 배포가 어려운 이유
GPU 추론은 CPU 서비스와 다르게 배포 전략이 곧 비용과 안정성에 직결됩니다.
- 초기 로딩 비용: 모델 파일 로딩, CUDA 커널 초기화, TensorRT 엔진 로드 등으로
startup time이 길어짐 - 메모리 고정(프래그먼테이션 포함): 새 파드가 뜨는 순간 GPU 메모리를 크게 점유
- 동시 롤링의 비용 폭발: 롤링 배포 중 구버전과 신버전이 동시에 떠서 GPU가 2배로 필요해질 수 있음
- 레디니스의 중요성: “파드가 Running”과 “추론이 가능한 상태”는 다름
따라서 Seldon Core 배포에서 핵심은 아래 3가지입니다.
- 레디니스/라이브니스/스타트업 프로브를 제대로 설계
- 롤링 시 동시 파드 수를 강하게 제어
- 카나리로 트래픽을 점진적으로 이동시키며 지표 기반으로 승격/롤백
Seldon Core GPU 추론 배포 기본: SeldonDeployment
아래 예시는 컨테이너가 이미 모델 서버(예: Triton, TorchServe, FastAPI 기반 커스텀 서버 등)를 포함하고 있으며, HTTP 엔드포인트로 추론을 제공한다고 가정합니다.
핵심은 다음입니다.
resources.limits에nvidia.com/gpu: 1지정readinessProbe는 “모델 로딩 완료”를 확인- 롤링 전략에서
maxSurge,maxUnavailable을 보수적으로 설정
apiVersion: machinelearning.seldon.io/v1
kind: SeldonDeployment
metadata:
name: gpu-infer
namespace: ml
spec:
name: gpu-infer
predictors:
- name: default
replicas: 2
componentSpecs:
- spec:
terminationGracePeriodSeconds: 60
containers:
- name: model
image: my-registry.example.com/ml/gpu-infer:1.0.0
imagePullPolicy: IfNotPresent
ports:
- containerPort: 8000
resources:
limits:
nvidia.com/gpu: 1
cpu: "2"
memory: "8Gi"
requests:
cpu: "1"
memory: "4Gi"
env:
- name: MODEL_NAME
value: "reco-v1"
readinessProbe:
httpGet:
path: /ready
port: 8000
initialDelaySeconds: 10
periodSeconds: 5
timeoutSeconds: 2
failureThreshold: 12
livenessProbe:
httpGet:
path: /health
port: 8000
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 2
failureThreshold: 3
graph:
name: model
type: MODEL
endpoint:
type: REST
traffic: 100
updateStrategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 0
maxUnavailable: 1
maxSurge: 0을 먼저 고려하는 이유
GPU 노드가 여유롭지 않다면 롤링 시 신버전 파드를 “추가로” 띄우는 순간 스케줄링이 실패하거나, 노드가 과밀해져 성능이 급락할 수 있습니다.
maxSurge: 0: 새 파드를 먼저 띄우지 않고 기존 파드를 하나 내려서 자리를 만든 뒤 교체maxUnavailable: 1: 최소 1개 파드는 항상 서비스 유지
트래픽이 많아 가용성을 더 지켜야 한다면, 노드 풀을 확장하거나(예: GPU 노드 오토스케일), 카나리로 전환 폭을 줄여 리스크를 분산하세요.
GPU 추론에서 레디니스 설계: “프로세스 생존”이 아니라 “추론 가능”
대부분의 장애는 배포 직후 발생합니다.
- 컨테이너는 살아있지만 모델이 아직 로딩 중
- 첫 요청에서 JIT 컴파일/커널 로딩으로 타임아웃
- 의존 파일 다운로드가 느려서 준비가 끝나지 않음
따라서 /ready는 아래 조건을 만족하는 게 좋습니다.
- 모델 파일 로딩 완료
- CUDA 컨텍스트 초기화 완료
- (가능하면) 더미 입력으로 1회 워밍업 완료
예시(파이썬 FastAPI)입니다.
from fastapi import FastAPI
app = FastAPI()
state = {
"model_loaded": False,
"warmup_done": False,
}
@app.on_event("startup")
def startup():
# 1) 모델 로딩
# load_model()
state["model_loaded"] = True
# 2) 워밍업 1회
# warmup_inference()
state["warmup_done"] = True
@app.get("/ready")
def ready():
if state["model_loaded"] and state["warmup_done"]:
return {"status": "ready"}
return {"status": "starting"}
@app.get("/health")
def health():
return {"status": "ok"}
추론 API가 외부 호출 타임아웃에 민감하다면, 배포 전후 타임아웃/재시도 전략도 점검하세요. 네트워크·게이트웨이 레벨 타임아웃이 얽히면 “GPU가 느린 게 아니라 경로가 끊긴 것”처럼 보일 수 있습니다. 비슷한 맥락의 실전 트러블슈팅은 OpenAI Responses API 408 타임아웃 재현과 해결 실전 가이드도 참고할 만합니다.
롤링 배포 실전 체크리스트
Seldon Core 자체보다 Kubernetes 배포 구성에서 사고가 납니다. 아래는 GPU 추론에 특화된 체크리스트입니다.
1) preStop과 그레이스풀 셧다운
롤링 중 기존 파드를 내릴 때, 인플라이트 요청이 있으면 실패율이 튈 수 있습니다. preStop 훅으로 신규 요청을 막고 일정 시간 대기 후 종료하는 패턴을 권장합니다.
lifecycle:
preStop:
exec:
command: ["/bin/sh", "-c", "sleep 20"]
또는 애플리케이션 레벨에서 SIGTERM 수신 시:
- 리스너를 닫아 신규 요청을 거부
- 현재 처리 중인 요청만 마무리
- GPU 메모리 해제
2) PDB로 “동시에 내려가는 파드 수” 제한
노드 드레인, 업그레이드, 스팟 중단 이벤트가 겹치면 한 번에 여러 파드가 내려가기도 합니다.
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: gpu-infer-pdb
namespace: ml
spec:
minAvailable: 1
selector:
matchLabels:
app: gpu-infer
3) 이미지 풀 실패는 배포를 즉시 멈춘다
GPU 이미지가 크고 레지스트리 인증이 복잡하면 ImagePullBackOff가 자주 납니다. 특히 사설 레지스트리, ECR, 노드 IAM, imagePullSecrets가 얽히면 롤링이 멈춰 전체 전환이 지연됩니다.
- EKS에서 401로 못 당겨오는 케이스는 EKS Pod ImagePullBackOff 401 해결 가이드
- 범용적으로는 K8s ImagePullBackOff 해결 - 인증·레지스트리·태그
이 두 글의 요지는 “태그/권한/시크릿/노드 역할”을 한 번에 점검하는 것입니다.
4) CrashLoopBackOff는 보통 “준비되기 전에 죽는” 문제
GPU 추론에서 크래시는 대개 아래 원인입니다.
- 모델 파일 경로/권한 오류
- CUDA 드라이버/런타임 불일치
- OOMKilled(메모리, 혹은 GPU 메모리)
- 프로브가 너무 공격적이라 초기화 중 강제 재시작
원인별 로그 확인과 패턴은 Kubernetes CrashLoopBackOff 원인별 로그·해결 9가지 흐름을 그대로 적용할 수 있습니다.
카나리 배포: 트래픽을 쪼개서 모델을 바꾸는 방법
카나리는 “신버전이 준비되었는지”를 확인하는 수준을 넘어, 실제 사용자 트래픽에서 품질과 성능을 검증하는 절차입니다.
Seldon Core에서 카나리를 구현하는 대표 패턴은 2가지입니다.
- SeldonDeployment 2개를 나란히 두고 Istio/Ingress 레벨에서 가중치 라우팅
- Seldon 내부 트래픽 분할을 이용(환경과 버전에 따라 제약이 있어, 서비스 메시 기반이 더 일반적)
여기서는 운영에서 가장 많이 쓰는 1번(서비스 메시 가중치 라우팅)을 기준으로 설명합니다.
1) v1, v2를 별도 배포로 유지
gpu-infer-v1은 안정 버전gpu-infer-v2는 신규 버전
각각 SeldonDeployment로 배포하고, 외부 트래픽은 Istio VirtualService로 분할합니다.
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: gpu-infer
namespace: ml
spec:
hosts:
- gpu-infer.example.com
gateways:
- ml-gateway
http:
- route:
- destination:
host: gpu-infer-v1.ml.svc.cluster.local
port:
number: 8000
weight: 90
- destination:
host: gpu-infer-v2.ml.svc.cluster.local
port:
number: 8000
weight: 10
timeout: 30s
retries:
attempts: 2
perTryTimeout: 10s
이렇게 하면 v2에 10%만 흘려보내면서 다음을 확인할 수 있습니다.
- p95, p99 latency
- GPU utilization, memory
- 에러율(HTTP 5xx, 타임아웃)
- 모델 품질 지표(가능하면 온라인 평가)
2) 카나리 승격(runbook)
실전에서는 “몇 %까지 올릴 것인가”보다 “어떤 조건에서 다음 단계로 갈 것인가”가 중요합니다.
예시 runbook:
- 10분 관찰: v2 5%
- 에러율
0.1%이하 - p95 latency v1 대비
+10%이내
- 에러율
- 30분 관찰: v2 20%
- 1시간 관찰: v2 50%
- 2시간 관찰: v2 100%
- 안정화 후 v1 축소/삭제
GPU 워밍업이 필요한 모델이라면, 카나리 시작 전에 v2 파드를 미리 띄워 충분히 워밍업하고(트래픽 0%), 레디니스가 안정적으로 유지되는지 먼저 확인하세요.
GPU 자원과 오토스케일: HPA만으로는 부족한 경우
GPU 추론은 CPU 기반 HPA 지표만으로는 확장 타이밍이 어긋날 수 있습니다.
- CPU는 낮은데 GPU는 100%인 상황이 흔함
- 큐잉이 늘면서 latency만 악화
대안:
- NVIDIA DCGM Exporter로 GPU utilization 지표를 Prometheus에 노출
- KEDA 또는 Prometheus Adapter로 GPU 지표 기반 스케일
- 최소 레플리카를 보수적으로 유지(특히 야간 배치 트래픽이 있는 경우)
카나리 중에는 오토스케일이 개입하면서 결과가 흔들릴 수 있으니, 테스트 구간에서는 minReplicas를 고정하거나 스케일 정책을 완만하게 조정하는 편이 안전합니다.
장애 대응 포인트: 노드/네트워크 이벤트가 카나리를 망친다
카나리에서 “v2가 불안정하다”라고 결론내리기 전에, 클러스터 레벨 이벤트를 먼저 배제해야 합니다.
- GPU 노드가
NotReady로 흔들리면 특정 버전만 영향을 받는 것처럼 보일 수 있음 - CNI 장애는 추론 서버 문제처럼 관측될 수 있음
노드 상태 이상과 네트워크 플러그인 복구는 K8s NodeNotReady - CNI 플러그인 장애 복구 가이드를 체크리스트로 삼아, 카나리 관측 시간대에 노드 이벤트가 있었는지 반드시 확인하세요.
운영 팁: GPU 추론 배포를 “예측 가능”하게 만드는 습관
마지막으로 실제 운영에서 효과가 큰 팁만 정리합니다.
1) 모델 로딩과 다운로드를 분리
컨테이너 시작 시 외부 스토리지에서 모델을 받는 구조라면, 네트워크 흔들림이 곧 배포 실패로 이어집니다.
- 모델을 이미지에 포함(이미지가 커지지만 예측 가능)
- 또는 initContainer로 다운로드 후, 메인 컨테이너는 로컬 파일만 읽기
2) 프로브 타이밍을 “GPU 워밍업 시간”에 맞춘다
failureThreshold와 periodSeconds를 CPU 서비스처럼 짧게 잡으면, 초기화 중 재시작 루프에 빠질 수 있습니다.
readinessProbe는 길게 잡아도 됨(서비스에 편입만 늦어짐)livenessProbe는 더 보수적으로(진짜로 죽었을 때만 재시작)
3) 롤링과 카나리를 섞지 않는다
- 롤링 배포는 “동일 서비스의 무중단 교체”
- 카나리는 “두 버전을 공존시키며 트래픽으로 검증”
GPU 환경에서는 카나리로 검증하고, 승격 후에는 롤링이 아니라 “가중치 100% 전환”으로 마무리하는 방식이 더 예측 가능합니다.
마무리
Seldon Core로 GPU 추론을 운영할 때 배포 전략의 핵심은 “Kubernetes 기본기” 위에 “GPU 특성(워밍업, 메모리, 비용)”을 정확히 얹는 것입니다.
- 롤링 배포는
maxSurge: 0같은 보수적 설정으로 비용 폭발을 막고 - 레디니스는 “모델이 진짜 준비됨”을 기준으로 설계하며
- 카나리는 서비스 메시 가중치 라우팅으로 점진 전환하고 지표로 승격/롤백합니다.
이 3가지만 체계화해도 GPU 추론 배포는 훨씬 덜 공포스러운 작업이 됩니다.