Published on

KServe+Istio로 GPU 모델 카나리 배포 실전 가이드

Authors

서빙 중인 GPU 모델을 교체할 때 가장 무서운 순간은 새 이미지가 떴는데 성능이 나쁘거나, GPU 메모리 사용 패턴이 달라 OOM이 나거나, 지연 시간이 튀어 SLA를 깨는 상황입니다. 이때 한 번에 100% 트래픽을 새 버전으로 넘기는 대신, 카나리(canary) 배포로 일부 트래픽만 흘려보내며 지표를 확인하고 점진적으로 확대하는 것이 안전합니다.

이 글에서는 KServe InferenceService로 GPU 모델을 서빙하고, Istio로 트래픽 비율을 제어해 카나리 배포를 수행하는 과정을 실전 관점에서 정리합니다. 특히 “GPU 모델” 특유의 제약(콜드 스타트, VRAM, 노드 스케줄링, 오토스케일 지연)을 고려해, 배포 전 체크리스트부터 롤백까지 다룹니다.

아키텍처 개요: KServe와 Istio의 역할 분담

  • KServe: 모델 서빙 워크로드를 InferenceService로 추상화합니다. 버전별로 predictor를 배치하고, 오토스케일/롤링업데이트/트래픽 스플릿 같은 서빙 기능을 제공합니다.
  • Istio: 서비스 메시 레벨에서 라우팅, 가중치 분산, 헤더 기반 라우팅, mTLS, 관측(telemetry) 등을 담당합니다.

실무에서는 “KServe의 트래픽 스플릿”만으로도 카나리가 가능하지만, Istio를 함께 쓰면 다음이 좋아집니다.

  • 특정 사용자/헤더에만 새 모델을 붙이는 실험 트래픽 라우팅
  • 실패율/지연 시간 기반으로 빠르게 정책을 바꾸는 운영 유연성
  • 조직 표준이 Istio일 때, 관측/보안/정책을 통일

전제 조건(환경 준비)

아래 가정으로 예시를 구성합니다.

  • Kubernetes 클러스터에 NVIDIA GPU 노드가 있고, nvidia-device-plugin이 설치되어 nvidia.com/gpu 리소스가 노출됨
  • KServe 설치
  • Istio 설치 및 사이드카 주입 활성화(네임스페이스 라벨링)
  • 모델 서버는 예시로 triton 또는 커스텀 컨테이너를 사용(핵심은 GPU 리소스 요청)

네임스페이스 예시:

kubectl create ns ml-serving
kubectl label ns ml-serving istio-injection=enabled

GPU 모델 카나리의 핵심 난제 4가지

GPU 모델 카나리는 CPU 웹앱 카나리보다 변수가 많습니다.

  1. 콜드 스타트 비용: 첫 로딩 시 모델 가중치 로드와 CUDA 커널 워밍업으로 지연이 큼
  2. VRAM OOM: 새 버전이 KV 캐시/배치 전략/정밀도 변화로 VRAM 요구량이 달라짐
  3. 오토스케일 지연: GPU 노드가 부족하면 파드가 Pending 상태로 오래 머무름
  4. 성능 회귀가 지표에 늦게 반영: 지연/오류율뿐 아니라 품질(정확도, hallucination 등)은 별도 샘플링이 필요

OOM 관련으로는 로컬/서버 환경에서의 메모리 전략도 중요합니다. 모델이 갑자기 OOM을 내면 카나리 단계에서 바로 멈춰야 합니다. 필요하면 양자화나 KV 캐시 전략을 점검하세요. 관련해서는 Transformers 로컬 LLM OOM 해결 - 4bit+KV캐시도 같이 참고하면 좋습니다.

1) 베이스라인(Stable) InferenceService 만들기

먼저 안정 버전 v1을 배포합니다. 아래는 KServe InferenceService 예시입니다.

주의: MDX 환경에서 부등호 문자가 문제될 수 있으니, 본문에서는 < > 또는 백틱 인라인 코드로 표기합니다. YAML 블록 안에서는 그대로 사용해도 안전하지만, 본문 설명에서는 < >를 사용합니다.

apiVersion: serving.kserve.io/v1beta1
kind: InferenceService
metadata:
  name: gpu-model
  namespace: ml-serving
spec:
  predictor:
    containers:
      - name: predictor
        image: ghcr.io/acme/gpu-model-server:v1
        ports:
          - containerPort: 8080
        resources:
          requests:
            cpu: "1"
            memory: "4Gi"
            nvidia.com/gpu: "1"
          limits:
            cpu: "2"
            memory: "8Gi"
            nvidia.com/gpu: "1"
        env:
          - name: MODEL_NAME
            value: "gpu-model"
          - name: LOG_LEVEL
            value: "info"

배포:

kubectl apply -f gpu-model-v1.yaml
kubectl -n ml-serving get inferenceservice gpu-model -w

KServe는 내부적으로 Revision/Deployment/Service를 만들며, Istio가 있으면 Ingress Gateway를 통해 외부 트래픽을 받습니다.

2) 카나리 버전(v2) 추가: 트래픽 스플릿 기본

KServe는 canary 섹션으로 새 버전을 붙이고, canaryTrafficPercent로 비율을 조절할 수 있습니다.

apiVersion: serving.kserve.io/v1beta1
kind: InferenceService
metadata:
  name: gpu-model
  namespace: ml-serving
spec:
  predictor:
    containers:
      - name: predictor
        image: ghcr.io/acme/gpu-model-server:v1
        ports:
          - containerPort: 8080
        resources:
          requests:
            cpu: "1"
            memory: "4Gi"
            nvidia.com/gpu: "1"
          limits:
            cpu: "2"
            memory: "8Gi"
            nvidia.com/gpu: "1"
  canary:
    predictor:
      containers:
        - name: predictor
          image: ghcr.io/acme/gpu-model-server:v2
          ports:
            - containerPort: 8080
          resources:
            requests:
              cpu: "1"
              memory: "4Gi"
              nvidia.com/gpu: "1"
            limits:
              cpu: "2"
              memory: "8Gi"
              nvidia.com/gpu: "1"
  canaryTrafficPercent: 10

적용:

kubectl apply -f gpu-model-canary.yaml
kubectl -n ml-serving describe inferenceservice gpu-model

이 단계에서 중요한 포인트:

  • v2 파드가 Ready 되기 전에는 트래픽을 늘리지 않습니다.
  • GPU 파드가 Pending이면, 사실상 카나리 비율을 올려도 v2로 못 갑니다. 이때는 GPU 노드 여유/스케줄링을 먼저 해결해야 합니다.

3) Istio로 “헤더 기반 카나리”까지 확장하기

비율 카나리만으로는 “특정 고객/테스터 그룹”에만 새 모델을 붙이기 어렵습니다. Istio의 VirtualService를 사용하면 x-canary: true 헤더가 있는 요청만 v2로 보내는 식의 제어가 가능합니다.

먼저 KServe가 만든 서비스 이름을 확인합니다. 클러스터마다 다를 수 있으니 다음으로 확인하세요.

kubectl -n ml-serving get svc | grep gpu-model

예시로 서비스가 gpu-model-predictor-default 라고 가정하고, Istio DestinationRuleVirtualService를 구성합니다. (실제 서브셋 라벨은 워크로드 라벨에 맞춰 조정해야 합니다.)

apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
  name: gpu-model-dr
  namespace: ml-serving
spec:
  host: gpu-model-predictor-default.ml-serving.svc.cluster.local
  subsets:
    - name: stable
      labels:
        serving.kserve.io/canary: "false"
    - name: canary
      labels:
        serving.kserve.io/canary: "true"
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: gpu-model-vs
  namespace: ml-serving
spec:
  hosts:
    - gpu-model-predictor-default.ml-serving.svc.cluster.local
  http:
    - match:
        - headers:
            x-canary:
              exact: "true"
      route:
        - destination:
            host: gpu-model-predictor-default.ml-serving.svc.cluster.local
            subset: canary
          weight: 100
    - route:
        - destination:
            host: gpu-model-predictor-default.ml-serving.svc.cluster.local
            subset: stable
          weight: 100

테스트 호출:

curl -H "x-canary: true" -H "Content-Type: application/json" \
  -d '{"inputs":"hello"}' \
  http://YOUR_INGRESS_HOST/v1/models/gpu-model:predict

이 방식은 “비율 카나리”와 달리, 관측 가능한 실험군을 명확히 분리할 수 있습니다. 예를 들어 QA/내부 트래픽만 새 모델에 붙여 품질을 먼저 확인하고, 이후 비율 카나리로 넘어가는 식의 단계적 운영이 가능합니다.

4) GPU 카나리에서 반드시 봐야 할 지표

카나리의 성공/실패 판단 기준을 사전에 합의해두는 것이 중요합니다. 추천 지표는 다음과 같습니다.

L7 지표(요청 관점)

  • p50/p95/p99 latency
  • 5xx rate429 rate(큐잉/레이트리밋)
  • 타임아웃 비율

GPU/런타임 지표(리소스 관점)

  • GPU 메모리 사용량, 메모리 단편화 패턴
  • GPU utilization, SM occupancy(가능하면)
  • 컨테이너 RSS, 페이지 캐시

모델 품질 지표(도메인 관점)

  • 오프라인 샘플링 기반 정확도/점수
  • 프롬프트/입력 분포 변화에 대한 강건성

실무 팁:

  • v2에 트래픽 10%를 주더라도, 요청이 특정 시간대에 몰리면 GPU가 포화되어 지연이 급증할 수 있습니다. 비율을 올리기 전에 부하 테스트를 짧게라도 수행하세요.
  • 카나리 파드 수가 1개라면, 노드/파드 단일 장애가 곧바로 지표를 흔듭니다. 최소 2개 이상(가능하면)으로 시작하세요.

5) 롤아웃 절차(권장 시나리오)

GPU 모델 카나리는 아래 순서가 운영 리스크가 낮습니다.

  1. v2 배포만 먼저(트래픽 0% 또는 헤더 라우팅만 허용)
  2. 워밍업: 내부적으로 더미 요청을 보내 CUDA 커널/캐시를 예열
  3. 헤더 기반으로 QA 트래픽만 흘려 품질/로그 확인
  4. 비율 카나리 1%5%10%25%50%100%
  5. 100% 전환 후에도 일정 시간 v1 리소스를 남겨 빠른 롤백 대비

KServe 비율 변경은 다음처럼 patch로도 자주 합니다.

kubectl -n ml-serving patch inferenceservice gpu-model \
  --type merge \
  -p '{"spec":{"canaryTrafficPercent":25}}'

6) 장애 대응: 카나리에서 자주 터지는 3가지와 처방

6.1 ImagePullBackOff로 카나리 파드가 안 뜬다

GPU 모델 이미지는 용량이 크고, 프라이빗 레지스트리를 쓰는 경우가 많아 ImagePullBackOff가 흔합니다. 이 상태에서 카나리 비율을 올리면 “올렸는데도 새 버전으로 안 가는” 이상한 상황이 됩니다.

  • 노드의 이미지 캐시 상태
  • IRSA/ECR 토큰
  • 레지스트리 rate limit

을 빠르게 확인하세요. 필요하면 아래 글의 체크리스트가 그대로 도움이 됩니다.

6.2 파드가 CrashLoopBackOff인데 로그가 없다

모델 서버가 시작 직후 죽으면 로그가 남지 않거나, 사이드카/로깅 파이프라인 때문에 “로그가 안 보이는” 상황이 발생합니다. GPU 드라이버/라이브러리 mismatch, 엔트리포인트 오류, 모델 파일 경로 문제 등이 원인입니다.

이때는 이벤트/컨테이너 상태/종료 코드부터 확인하는 방식이 효과적입니다.

6.3 OOMKilled 또는 GPU OOM

  • 컨테이너 메모리 OOMKilled: resources.limits.memory 초과
  • GPU OOM: VRAM 부족으로 프레임워크가 실패

카나리에서는 다음을 즉시 수행합니다.

  • 트래픽을 0%로 내리거나, Istio 헤더 라우팅만 남기고 일반 트래픽 차단
  • 배치 크기/최대 토큰/동시성 제한을 낮춤
  • 필요하면 정밀도 변경(예: fp16, int8, 4bit)과 KV 캐시 정책 재검토

7) 운영 팁: GPU 노드 스케줄링과 안정성

노드 선택과 격리

GPU 모델은 노드 특성이 중요합니다. 가능하면 다음을 적용하세요.

  • nodeSelector 또는 nodeAffinity로 GPU 타입 고정
  • tolerations로 GPU 전용 노드에만 스케줄
  • podAntiAffinity로 같은 노드에 카나리/스테이블을 분산(노드 장애 격리)

예시(컨테이너가 아니라 파드 스펙 수준이 필요한 경우가 많아, KServe의 podSpec 확장 필드를 사용하는 패턴을 검토):

apiVersion: serving.kserve.io/v1beta1
kind: InferenceService
metadata:
  name: gpu-model
  namespace: ml-serving
spec:
  predictor:
    podSpec:
      nodeSelector:
        accelerator: nvidia
      tolerations:
        - key: "nvidia.com/gpu"
          operator: "Exists"
          effect: "NoSchedule"
    containers:
      - name: predictor
        image: ghcr.io/acme/gpu-model-server:v1
        resources:
          requests:
            nvidia.com/gpu: "1"
          limits:
            nvidia.com/gpu: "1"

클러스터/버전에 따라 podSpec 지원 방식이 다를 수 있으니, 사용 중인 KServe 버전 문서를 확인하세요.

스케일 전략

GPU는 비용이 크기 때문에 minReplicas=0 같은 공격적 설정을 하고 싶지만, 카나리에서는 콜드 스타트가 치명적일 수 있습니다.

  • stable은 minReplicas를 1 이상으로 유지
  • canary는 최소 1로 시작해 워밍업 후 트래픽을 붙임
  • HPA/KPA의 스케일 업 속도와 메트릭 소스를 미리 점검

8) 롤백: “즉시 0%”가 가능한가

카나리의 가장 큰 장점은 롤백이 빠르다는 점입니다.

KServe 스플릿 기반 롤백

kubectl -n ml-serving patch inferenceservice gpu-model \
  --type merge \
  -p '{"spec":{"canaryTrafficPercent":0}}'

문제가 해결될 때까지 v2는 살아있되 트래픽만 끊고, 원인 분석을 진행할 수 있습니다.

Istio 헤더 라우팅 기반 롤백

  • VirtualService에서 canary match 룰을 제거하거나
  • canary subset weight를 0으로 변경

이 방식은 “특정 테스터만 계속 v2를 보게” 유지할 수도 있어, 재현과 디버깅에 유리합니다.

9) 체크리스트(배포 직전 5분 점검)

  • v2 파드가 Ready 상태인가
  • GPU 노드에 여유가 있어 Pending이 발생하지 않는가
  • v2 워밍업 요청을 수행했는가
  • 타임아웃/리트라이 정책이 모델 특성에 맞는가(과도한 리트라이는 GPU를 더 죽임)
  • p95 latency, 5xx rate, GPU 메모리 사용량 대시보드가 stable/canary로 분리되어 있는가
  • 롤백 방법(KServe 0% 또는 Istio 라우팅 수정)이 문서화되어 있는가

마무리

KServe와 Istio를 조합하면 GPU 모델도 웹 서비스처럼 점진적으로 배포할 수 있지만, GPU 워크로드는 콜드 스타트/VRAM/OOM/스케줄링 지연 같은 변수가 커서 “카나리 절차” 자체가 곧 안정성의 핵심이 됩니다.

권장하는 접근은 헤더 기반(폐쇄 실험) → 소량 비율 카나리 → 단계적 확대 → 100% 전환 후 관찰 순서입니다. 그리고 실패했을 때는 과감하게 0% 롤백하고, 이미지 풀/크래시 루프/메모리 문제를 우선순위로 처리하세요.