Published on

KServe+Seldon으로 GPU 모델 롤링배포·카나리

Authors

서빙 중인 GPU 모델을 자주 업데이트해야 하는 팀이라면, 단순히 Deployment 이미지 태그만 바꾸는 방식은 금방 한계에 부딪힙니다. 새 모델이 로드되는 동안 GPU 메모리가 순간적으로 두 배 필요할 수 있고, 워밍업이 끝나기 전에 트래픽이 유입되면 지연이 튀거나 오류가 발생합니다. 또한 새 모델이 품질 이슈를 내면 즉시 되돌릴 수 있어야 합니다.

이 글에서는 Kubernetes 환경에서 KServeSeldon을 활용해 다음을 구현하는 방법을 다룹니다.

  • GPU 추론 워크로드의 무중단 롤링 배포
  • 새 버전을 소량 트래픽으로 검증하는 카나리 배포
  • 오토스케일, 워밍업, 리소스 설계, 관측과 롤백까지 포함한 운영 체크리스트

관측과 디버깅은 결국 분산 추적이 핵심이므로, 운영 단계에서 트레이싱을 붙이는 패턴은 AutoGPT 툴콜 무한루프, OpenTelemetry로 추적하기 글의 접근을 같이 참고하면 도움이 됩니다.

왜 KServe와 Seldon을 같이 쓰나

둘 다 “모델 서빙” 영역에서 자주 언급되지만 역할이 약간 다릅니다.

  • KServe: Kubernetes 네이티브 모델 서빙 컨트롤 플레인. InferenceService CRD로 배포, 스케일링, 네트워킹, 트래픽 분할(특정 구성) 등을 제공합니다. Knative 기반인 경우 scale-to-zero, 요청 기반 오토스케일 등 운영 편의가 큽니다.
  • Seldon Core: 추론 그래프(프리/포스트 프로세싱), A/B 테스트, 멀티모델 라우팅, 설명가능성 등 “서빙 파이프라인”을 유연하게 구성하는 데 강점이 있습니다.

현실적인 조합은 보통 다음 중 하나입니다.

  1. KServe로 표준화된 배포/오토스케일을 가져가고, 모델 서버는 predictor로 붙인다.
  2. 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 노드풀과 스케일 정책을 별도 운영

아키텍처: 카나리 + 롤링의 “안전한 순서”

운영에서 가장 안전한 순서는 보통 이렇습니다.

  1. 신버전 Pod를 소수로 먼저 기동 (트래픽 거의 없음)
  2. 워밍업 완료 및 readiness 통과 확인
  3. **카나리 트래픽 1%~10%**로 품질/지연/에러율 관측
  4. 문제 없으면 25% → 50% → 100%로 점진 전환
  5. 완전 전환 후 구버전 제거

이때 “롤링”은 단순 이미지 교체가 아니라, 트래픽을 분할한 상태에서 두 버전을 공존시키는 것을 의미합니다.

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를 잡아먹어 안정 버전이 축출”되는 것입니다. 이를 피하려면 물리적으로 격리하세요.

  • stablegpu-pool-stable 노드풀
  • canarygpu-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를 늘리는 대신 failureThresholdperiodSeconds로 총 대기 시간을 설계
  • 워밍업 요청을 고정된 입력으로 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가지

카나리는 “정확도”만 보면 실패합니다. 운영 관점의 지표가 같이 필요합니다.

  1. 에러율: 5xx, 타임아웃, OOM
  2. 지연: p50, p95, p99(특히 p99)
  3. GPU 메모리/사용률: 메모리 누수, KV 캐시 폭증
  4. 큐 길이/대기 시간: 내부 배치 큐가 병목인지
  5. 스케일 이벤트: 오토스케일로 replica가 늘 때 지연이 안정적인지
  6. 품질 지표: 오프라인 골든셋, 온라인 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 연계)까지 확장해 “사람이 덜 개입하는 카나리”로 발전시키는 것을 권장합니다.