Published on

KServe로 GPU 모델 서버 배포 - 스케일링·카나리

Authors

서빙 단계에서 GPU 모델을 운영하려면 단순히 파드를 띄우는 것만으로는 부족합니다. 요청 폭주 시 확장(스케일링), 유휴 시 비용 절감(스케일-투-제로), 새 모델의 점진적 롤아웃(카나리), 관측성(로그·지표)까지 한 번에 설계해야 합니다.

KServe는 Kubernetes 위에서 InferenceService라는 고수준 리소스로 이 문제를 상당 부분 표준화합니다. 특히 GPU 워크로드에서 자주 마주치는 cold start, 드라이버/런타임 불일치, 노드 스케줄링, 카나리 트래픽 분배 같은 운영 이슈를 구조적으로 다루기 좋습니다.

아래에서는 KServe로 GPU 모델 서버를 배포하고, 스케일링과 카나리 배포를 실무 관점에서 구성하는 방법을 단계별로 정리합니다.

전체 아키텍처 한 장 요약

KServe의 핵심은 InferenceService입니다. 내부적으로는 대략 다음 구성요소가 엮입니다.

  • InferenceService
    • predictor: 실제 추론 컨테이너(또는 모델 서버)
    • (선택) transformer: 전/후처리
    • (선택) explainer: 설명 가능성
  • 네트워크
    • (기본) Knative Serving 기반: 요청 기반 오토스케일, 스케일-투-제로
    • (대안) RawDeployment 모드: 일반 Deployment처럼 동작(스케일-투-제로 없음)
  • 트래픽 관리
    • canaryTrafficPercent로 새 리비전으로 트래픽 일부만 전달

GPU 모델 서빙에서는 보통 다음 2가지 모드 중 하나를 택합니다.

  • Knative 모드: 트래픽 탄력성과 비용 효율이 좋지만, cold start가 체감될 수 있음
  • RawDeployment 모드: 항상 상주(또는 HPA)시키기 쉬워 지연시간이 안정적이지만 유휴 비용이 발생

사전 준비: GPU 노드와 런타임 체크

KServe 설정 이전에, Kubernetes 클러스터에서 GPU가 정상적으로 노출되는지부터 확인해야 합니다.

1) NVIDIA Device Plugin 설치 확인

GPU 노드에서 nvidia.com/gpu 리소스가 잡히는지 확인합니다.

kubectl get nodes -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.status.allocatable.nvidia\.com/gpu}{"\n"}{end}'

출력이 비어있거나 0이면, NVIDIA device plugin 또는 GPU 드라이버/컨테이너 런타임 구성이 깨졌을 확률이 큽니다.

2) 스케줄링을 위한 노드 라벨/테인트 전략

GPU 노드를 일반 워크로드와 분리하려면 라벨과 테인트를 함께 씁니다.

  • 라벨 예: accelerator=nvidia
  • 테인트 예: nvidia.com/gpu=present:NoSchedule

이후 InferenceService에서 nodeSelectortolerations로 GPU 노드에만 뜨게 합니다.

KServe 설치 개요

KServe는 보통 Helm으로 설치합니다. 설치 자체는 환경별 편차가 커서 여기서는 핵심 포인트만 짚습니다.

  • Knative Serving(스케일-투-제로/요청 기반 스케일링이 필요하면)
  • Istio 또는 Kourier(인그레스)
  • cert-manager(웹훅, 인증서)

운영 중 설치/업그레이드에서 파드가 반복 재시작하거나 웹훅 때문에 생성이 막히는 경우가 꽤 흔합니다. 이때는 이벤트/로그를 기준으로 원인 분류를 빠르게 하는 게 중요합니다. 관련해서는 Kubernetes CrashLoopBackOff 원인별 로그·해결 9가지 같은 체크리스트가 도움이 됩니다.

GPU InferenceService 기본 배포 예제

아래 예시는 GPU 1장을 요청하는 추론 서버를 KServe로 띄우는 최소 구성입니다. 모델 서버는 커스텀 컨테이너를 쓰는 형태로 작성했습니다.

주의: MDX 렌더링 환경에서 부등호 문자가 본문에 노출되면 빌드 에러가 날 수 있으므로, 제네릭이나 화살표 표기 같은 문자는 코드 블록 또는 인라인 코드로만 표기합니다.

apiVersion: serving.kserve.io/v1beta1
kind: InferenceService
metadata:
  name: gpu-model
  namespace: ml
spec:
  predictor:
    containers:
      - name: predictor
        image: ghcr.io/acme/gpu-infer:1.0.0
        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_PATH
            value: /models/model.onnx
    nodeSelector:
      accelerator: nvidia
    tolerations:
      - key: nvidia.com/gpu
        operator: Equal
        value: present
        effect: NoSchedule

요청 테스트

클러스터 내부에서 서비스 URL을 얻는 방식은 설치한 인그레스/Knative 구성에 따라 다릅니다. KServe/Knative 환경에서는 보통 다음처럼 호스트를 얻고 호출합니다.

kubectl get inferenceservice gpu-model -n ml

출력의 URL을 확인한 뒤, 예를 들어 JSON을 POST 합니다.

curl -H 'Content-Type: application/json' \
  -d '{"instances": [[1.0, 2.0, 3.0]]}' \
  http://YOUR_MODEL_URL/v1/models/gpu-model:predict

엔드포인트 경로는 사용 중인 모델 서버 규약(V2, KFServing v1, 커스텀 라우팅)에 따라 달라질 수 있으니, 컨테이너가 어떤 프로토콜을 제공하는지 먼저 고정하세요.

스케일링 전략: GPU에서 중요한 3가지

GPU 서빙의 스케일링은 CPU 웹앱과 다르게 “파드 수”보다 “GPU 메모리와 배치 효율”이 병목이 되는 경우가 많습니다. 운영에서 특히 중요한 포인트는 다음 3가지입니다.

1) 스케일-투-제로를 쓸지 결정하기

  • 장점: 유휴 시 비용 절감
  • 단점: cold start로 첫 요청 지연이 커짐(모델 로드, CUDA 초기화)

대화형 API처럼 p99 지연이 민감하면 최소 1개는 항상 유지하는 편이 낫습니다. 반대로 비정기 배치성 호출이면 스케일-투-제로가 큰 비용 절감이 됩니다.

Knative 모드에서는 minScale로 하한을 설정할 수 있습니다.

apiVersion: serving.kserve.io/v1beta1
kind: InferenceService
metadata:
  name: gpu-model
  namespace: ml
  annotations:
    autoscaling.knative.dev/minScale: "1"
    autoscaling.knative.dev/maxScale: "5"
    autoscaling.knative.dev/target: "10"
spec:
  predictor:
    containers:
      - name: predictor
        image: ghcr.io/acme/gpu-infer:1.0.0
        resources:
          requests:
            nvidia.com/gpu: "1"
          limits:
            nvidia.com/gpu: "1"
  • minScale: 최소 파드 수
  • maxScale: 최대 파드 수
  • target: 동시성(Concurrency) 목표치(요청 기반 스케일링)

GPU 추론은 동시성을 무작정 올리면 지연이 급증할 수 있습니다. 모델이 배치 처리를 지원한다면 “동시성 증가”보다 “서버 내부 마이크로배칭”이 더 안정적일 때가 많습니다.

2) RawDeployment 모드로 HPA를 붙이는 선택지

Knative의 요청 기반 스케일링 대신, 일반 Deployment처럼 운영하고 싶다면 RawDeployment 모드를 고려합니다.

  • 장점: 안정적인 상주, 기존 HPA/PodDisruptionBudget 등 쿠버네티스 운영 패턴 활용
  • 단점: 스케일-투-제로의 이점을 잃음

KServe 버전과 설치 옵션에 따라 설정 방식이 다를 수 있지만, 핵심은 “Knative를 우회해 Deployment를 만들도록” 하는 것입니다. 이 경우 GPU 파드 수는 HPA 또는 수동으로 조절합니다.

HPA를 GPU 사용률로 직접 걸기는 까다롭습니다. 보통은 다음 중 하나를 씁니다.

  • 큐 길이(예: Kafka lag, Redis queue depth)
  • 요청 지연(p95/p99) 기반의 커스텀 메트릭
  • DCGM exporter로 GPU utilization 메트릭을 Prometheus로 수집 후 커스텀 메트릭화

3) 스케일링보다 중요한 “스케줄링 실패” 방지

GPU는 희소 자원이라, 확장하려고 해도 스케줄링이 실패하는 순간이 옵니다.

실무에서 자주 보는 원인:

  • GPU 노드가 부족함(당연하지만 가장 흔함)
  • 파드가 요청한 GPU 수가 노드 단위와 안 맞음
  • nodeSelector/tolerations 불일치
  • 이미지가 너무 커서 풀링이 느려 Pending이 길어짐

이때는 kubectl describe pod의 이벤트를 먼저 보고, 0/.. nodes are available 메시지를 기반으로 원인을 좁히는 게 빠릅니다.

카나리 배포: 새 모델을 안전하게 섞는 방법

모델 변경은 “정확도/지연/메모리”가 동시에 바뀌기 때문에, 애플리케이션 배포보다 카나리의 필요성이 큽니다. KServe는 canaryTrafficPercent로 간단히 트래픽을 분할할 수 있습니다.

1) 기본 카나리 설정

아래는 기존 predictor는 유지하고, canaryPredictor에 새 이미지를 올린 뒤 트래픽 10%만 보내는 예시입니다.

apiVersion: serving.kserve.io/v1beta1
kind: InferenceService
metadata:
  name: gpu-model
  namespace: ml
spec:
  predictor:
    containers:
      - name: predictor
        image: ghcr.io/acme/gpu-infer:1.0.0
        resources:
          requests:
            nvidia.com/gpu: "1"
          limits:
            nvidia.com/gpu: "1"
  canaryPredictor:
    containers:
      - name: predictor
        image: ghcr.io/acme/gpu-infer:1.1.0
        resources:
          requests:
            nvidia.com/gpu: "1"
          limits:
            nvidia.com/gpu: "1"
  canaryTrafficPercent: 10

이렇게 하면 동일한 URL로 들어오는 요청 중 일부가 새 리비전으로 라우팅됩니다.

2) 카나리에서 반드시 봐야 하는 지표

GPU 모델 카나리는 “정답률”만 보면 사고가 납니다. 다음을 함께 보세요.

  • 지연시간: p50/p95/p99
  • 오류율: 5xx, 타임아웃, OOM
  • GPU 메모리: steady-state와 peak
  • 워밍업 시간: 첫 요청 지연
  • 입력 분포: 특정 입력에서만 폭발하는지

특히 OOM은 카나리에서 가장 먼저 터지는 문제 중 하나입니다. 새 모델이 더 큰 KV cache를 쓰거나, 토크나이저/전처리가 바뀌면서 메모리가 증가하는 경우가 흔합니다.

3) 점진적 승격(10% -> 30% -> 50% -> 100%) 운영 팁

  • 트래픽 비율을 올리기 전에 최소 관측 윈도우를 잡습니다(예: 30분 또는 N 요청).
  • 지연/오류가 안정적이면 점진적으로 올립니다.
  • 문제가 있으면 즉시 canaryTrafficPercent0으로 내려 롤백합니다.
kubectl patch inferenceservice gpu-model -n ml --type merge \
  -p '{"spec":{"canaryTrafficPercent":0}}'

카나리 롤백은 “이미지 태그 롤백”보다 빠르고 안전한 경우가 많습니다.

운영에서 자주 겪는 장애 패턴과 해결 포인트

1) 이미지 풀링/모델 다운로드가 느려 cold start가 길다

대응:

  • 모델을 이미지에 bake-in(단, 이미지가 너무 커지면 역효과)
  • 노드 로컬 캐시/프리풀링(daemonset로 미리 pull)
  • 오브젝트 스토리지에서 모델을 받을 때는 네트워크 대역폭/동시성 제한

2) 카나리만 CrashLoopBackOff가 난다

대응:

  • 새 이미지에서 CUDA/cuDNN 버전이 달라졌는지 확인
  • GPU 노드 드라이버와 컨테이너 런타임 호환성 확인
  • kubectl logs와 이벤트를 먼저 모아 원인 분류

CrashLoopBackOff는 원인이 워낙 다양해서 “어디서부터 볼지”가 중요합니다. 빠른 트러블슈팅 흐름은 Kubernetes CrashLoopBackOff 원인별 로그·해결 9가지 글의 관점(이벤트 -> 로그 -> 프로브 -> 리소스 -> 볼륨/권한)을 그대로 적용하면 시간을 절약할 수 있습니다.

3) EKS에서 모델이 S3/외부 API를 못 때린다(인증 403)

GPU 서빙은 보통 모델 아티팩트를 S3에서 가져오거나, 토큰 검증/피처 스토어 등 AWS API 호출이 섞입니다. 이때 IRSA 설정이 조금만 틀어져도 403이 나면서 모델 로딩이 실패합니다.

EKS에서 AWS SDK 인증이 꼬일 때는 EKS에서 AWS SDK 403 MissingAuthenticationToken 해결 체크리스트처럼 ServiceAccount 어노테이션, 웹아이덴티티 토큰, 정책 연결을 순서대로 검증하는 방식이 효과적입니다.

실전 구성 예시: 워밍업 + 카나리 + 최소 스케일

GPU 모델은 첫 요청이 특히 느릴 수 있으니, 다음 조합이 자주 쓰입니다.

  • minScale1로 둬서 완전 0으로 내려가지 않게 함
  • 배포 직후 워밍업 요청을 1회 이상 수행
  • 카나리 10%로 시작

워밍업을 잡(job)으로 넣는 간단한 예시입니다.

apiVersion: batch/v1
kind: Job
metadata:
  name: gpu-model-warmup
  namespace: ml
spec:
  backoffLimit: 3
  template:
    spec:
      restartPolicy: Never
      containers:
        - name: warmup
          image: curlimages/curl:8.5.0
          command:
            - sh
            - -c
            - |
              set -e
              for i in 1 2 3; do
                curl -sS -H 'Content-Type: application/json' \
                  -d '{"instances": [[0.0, 0.0, 0.0]]}' \
                  http://YOUR_MODEL_URL/v1/models/gpu-model:predict \
                  && exit 0
                sleep 3
              done
              exit 1

여기서 set -e를 쓸 때 파이프라인이나 조건문에 따라 기대와 다르게 실패를 삼키는 경우가 있습니다. 셸 스크립트가 “실패해야 하는데 안 죽는” 상황을 겪었다면 Bash set -e가 안 멈출 때 - pipefail·|| true 함정 같은 패턴을 함께 점검하세요.

체크리스트: 배포 전후로 이것만은 확인

배포 전

  • GPU 노드에 nvidia.com/gpu가 allocatable로 잡히는가
  • 이미지의 CUDA 버전과 노드 드라이버 호환이 맞는가
  • 모델 로딩 경로(볼륨/오브젝트 스토리지)와 권한이 맞는가
  • requests/limits에 GPU가 정확히 명시됐는가

배포 후

  • InferenceServiceREADYTrue로 수렴하는가
  • 첫 요청 지연이 SLO를 만족하는가(워밍업/minScale 조정)
  • 카나리에서 오류율/OOM이 없는가
  • 확장 시 스케줄링 실패 이벤트가 없는가

마무리

KServe는 GPU 모델 서빙에서 반복되는 운영 문제를 InferenceService라는 단일 추상화로 묶어, 배포·스케일링·카나리를 한 흐름으로 관리하게 해줍니다. 다만 GPU 특성상 “스케일링 설정”만으로 해결되지 않는 영역(스케줄링 희소성, 모델 로딩 시간, CUDA 호환, OOM)이 크기 때문에, 카나리로 리스크를 분산하고 관측 가능한 지표를 먼저 정의하는 것이 핵심입니다.

다음 단계로는 (1) DCGM exporter로 GPU 메트릭을 수집해 카나리 승격 기준을 자동화하거나, (2) 모델별로 배치/동시성 튜닝을 표준화해 비용 대비 처리량을 끌어올리는 작업을 추천합니다.