Published on

Seldon Core v2로 K8s LLM 카나리 배포·롤백

Authors

프로덕션 LLM 서빙에서 제일 무서운 순간은 “모델은 좋아졌는데 트래픽을 태우자마자 지연이 튀고, 에러가 늘고, 비용이 폭발하는” 상황입니다. 특히 LLM은 일반 분류/회귀 모델보다 지연(latency) 분포가 두껍고, 토큰 생성 속도 같은 새로운 성능 지표가 있으며, 프롬프트 길이/동시성에 따라 동작이 급격히 바뀝니다. 그래서 블루/그린처럼 한 번에 갈아타기보다 카나리 배포로 소량 트래픽에서 안전하게 검증하고, 즉시 롤백 가능한 구조가 사실상 필수입니다.

이 글에서는 Seldon Core v2(이하 Seldon v2)로 Kubernetes에서 LLM을 서빙할 때, 카나리 배포·승격·롤백을 어떻게 설계/적용하는지 운영 관점으로 정리합니다. 예시는 vLLM 또는 Hugging Face TGI 같은 “LLM 서버 컨테이너”를 Seldon v2 워크로드로 붙인다는 전제로 설명합니다.

참고: 클러스터 네트워크/DNS 문제는 카나리 중에도 자주 발목을 잡습니다. 특히 EKS에서 DNS 타임아웃이 간헐적으로 생기면 카나리가 “모델 문제인지 인프라 문제인지” 구분이 어려워집니다. 관련 진단은 EKS CoreDNS DNS timeout·SERVFAIL 10분 진단을 함께 참고하세요.

Seldon Core v2에서 카나리의 핵심 개념

Seldon v2는 “모델 서빙을 쿠버네티스 네이티브 리소스로 선언하고, 라우팅/관측/스케일링을 붙이는” 방향으로 진화했습니다. 카나리 관점에서 중요한 포인트는 아래 3가지입니다.

  1. 버전(Revision) 단위로 워크로드를 분리할 수 있어야 함
  2. 트래픽 분할(가중치 라우팅) 을 통해 1%~10%처럼 작은 비율로 검증 가능해야 함
  3. 메트릭 기반으로 승격/롤백을 빠르게 수행할 수 있어야 함

LLM은 “정답률”만으로는 판단이 어렵고, 실시간 서빙에서는 p95/p99 지연, 5xx 비율, 토큰/초, 큐잉 지연, GPU 메모리 OOM 같은 운영 지표가 더 중요합니다. 따라서 카나리 정책도 이 지표들을 중심으로 잡는 게 현실적입니다.

아키텍처: LLM 서버 + Seldon v2 라우팅

일반적인 구성은 다음과 같습니다.

  • LLM 서버: vllm 또는 text-generation-inference 컨테이너
  • Seldon v2:
    • 모델/서버 워크로드를 선언
    • Ingress(또는 Gateway)에서 트래픽을 받아
    • Stable/Canary로 가중치 라우팅
  • 관측: Prometheus + Grafana(또는 OTEL)
  • 배포: GitOps(Argo CD/Flux) 또는 CI/CD 파이프라인

핵심은 “LLM 서버 컨테이너는 그대로 두고, Seldon이 라우팅과 운영 제어면을 제공”하게 만드는 것입니다.

사전 준비 체크리스트(LLM 카나리 특화)

카나리 전에 다음을 먼저 고정해두면, 롤백 판단이 빨라집니다.

1) 요청 스키마/토크나이저 호환성

  • 같은 API 스키마를 유지하는지(예: OpenAI 호환 엔드포인트)
  • 토크나이저 변경으로 프롬프트 길이/비용이 달라지지 않는지
  • 시스템 프롬프트/템플릿이 버전별로 달라지지 않는지

2) 성능 SLO를 “토큰 기반”으로 정의

LLM은 단순 latency_ms만 보면 오판하기 쉽습니다.

  • time_to_first_token_ms (TTFT)
  • tokens_per_second (TPS)
  • completion_tokens / prompt_tokens 분포
  • p95/p99 지연
  • 5xx 비율, 429 비율(큐/레이트리밋)

3) 인프라 변수 고정

카나리 중에는 “모델 차이”만 보고 싶습니다.

  • GPU 타입/수량 동일
  • max_num_seqs, max_model_len, 배치/큐 설정 동일
  • 노드 오토스케일 정책 동일

예시: Stable/Canary 두 개의 LLM 워크로드 만들기

아래는 “stable(v1)”, “canary(v2)” 두 워크로드를 분리해 두고, 라우팅에서 가중치만 바꾸는 패턴입니다. 실제 CRD 이름/필드는 설치한 Seldon v2 버전과 배포 방식(Envoy/Gateway/Service Mesh)에 따라 다를 수 있으니, 핵심 아이디어(두 리비전, 동일 인터페이스, 라우팅 가중치) 를 중심으로 보시면 됩니다.

1) LLM 서버 Deployment (stable)

apiVersion: apps/v1
kind: Deployment
metadata:
  name: llm-stable
  labels:
    app: llm
    track: stable
spec:
  replicas: 2
  selector:
    matchLabels:
      app: llm
      track: stable
  template:
    metadata:
      labels:
        app: llm
        track: stable
    spec:
      containers:
        - name: vllm
          image: vllm/vllm-openai:latest
          args:
            - "--model"
            - "mistralai/Mistral-7B-Instruct-v0.2"
            - "--port"
            - "8000"
          ports:
            - containerPort: 8000
          resources:
            limits:
              nvidia.com/gpu: "1"
              cpu: "4"
              memory: "16Gi"

2) LLM 서버 Deployment (canary)

apiVersion: apps/v1
kind: Deployment
metadata:
  name: llm-canary
  labels:
    app: llm
    track: canary
spec:
  replicas: 1
  selector:
    matchLabels:
      app: llm
      track: canary
  template:
    metadata:
      labels:
        app: llm
        track: canary
    spec:
      containers:
        - name: vllm
          image: vllm/vllm-openai:latest
          args:
            - "--model"
            - "mistralai/Mistral-7B-Instruct-v0.3"
            - "--port"
            - "8000"
          ports:
            - containerPort: 8000
          resources:
            limits:
              nvidia.com/gpu: "1"
              cpu: "4"
              memory: "16Gi"

여기까지는 “그냥 두 개의 LLM 서버”입니다. Seldon v2는 이 위에 트래픽 라우팅과 관측을 붙이는 계층으로 생각하면 이해가 쉽습니다.

트래픽 가중치 라우팅(카나리 1%부터)

Seldon v2에서 트래픽 분할은 보통 Gateway/Ingress 계층에서 수행합니다. 아래 예시는 “stable 99, canary 1”로 시작하는 전형적인 카나리입니다.

주의: 아래 YAML은 개념 예시이며, 실제 환경에서는 Istio VirtualService 또는 Gateway API HTTPRoute 등으로 구현하는 경우가 많습니다. 핵심은 weight 기반 분기입니다.

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: llm-route
spec:
  parentRefs:
    - name: llm-gateway
  rules:
    - matches:
        - path:
            type: PathPrefix
            value: /v1/chat/completions
      backendRefs:
        - name: llm-stable-svc
          port: 8000
          weight: 99
        - name: llm-canary-svc
          port: 8000
          weight: 1

이렇게 하면 애플리케이션 수정 없이도 “동일 엔드포인트”로 들어온 요청이 1%만 canary로 흘러갑니다.

LLM 카나리에서 가중치 라우팅만으로 부족한 경우

가중치만으로는 다음 문제를 못 잡을 때가 있습니다.

  • 특정 테넌트/프롬프트 유형에서만 장애가 나는 경우
  • 캐시/세션/대화 컨텍스트가 섞이면 비교가 어려운 경우

이때는 “헤더 기반 라우팅”을 같이 쓰면 좋습니다. 예를 들어 내부 QA 트래픽만 canary로 강제 라우팅합니다.

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: llm-route-header-canary
spec:
  parentRefs:
    - name: llm-gateway
  rules:
    - matches:
        - headers:
            - name: x-llm-track
              value: canary
      backendRefs:
        - name: llm-canary-svc
          port: 8000
          weight: 100

운영에서는 보통 “헤더 강제 라우팅(내부 검증)”으로 1차 검증 후, “가중치 카나리”로 확대합니다.

관측 지표 설계: 승격/롤백을 결정하는 신호

LLM 카나리의 승격/롤백은 감으로 하면 실패합니다. 최소한 아래 4개는 대시보드에 고정하세요.

1) 에러율

  • HTTP 5xx 비율
  • 업스트림 타임아웃 비율
  • 모델 서버 OOM/프로세스 재시작 횟수

2) 지연 분포

  • p50, p95, p99 latency
  • time_to_first_token_ms (가능하면)

3) 처리량과 큐 상태

  • RPS, 동시 요청 수
  • 큐잉 지연, 429 발생

4) 비용/자원

  • GPU utilization, GPU memory
  • 노드 스케줄 실패(리소스 부족)

카나리에서 지표가 튀는 원인이 모델이 아니라 네트워크/DNS인 경우도 많습니다. 특히 클러스터 내부 DNS가 불안하면 업스트림 호출(예: 토크나이저 서비스, 프롬프트 템플릿 서비스, 정책 서비스)에서 지연이 생깁니다. 이런 유형은 EKS Pod STS AssumeRole 타임아웃 - NAT·PrivateLink·DNS 같은 네트워크 진단 글이 실제로 도움이 됩니다.

카나리 승격/롤백 운영 절차(현실적인 Runbook)

아래는 “1%로 시작해서 100%까지” 올리는 전형적인 절차입니다.

1) 0단계: canary 워밍업

  • canary Pod가 뜨자마자 트래픽을 주면 콜드 스타트/모델 로딩이 지표를 망칩니다.
  • readinessProbe가 “서버 포트 오픈”만 보지 말고, 가능하면 “모델 로딩 완료”를 확인해야 합니다.

예시(개념):

readinessProbe:
  httpGet:
    path: /health
    port: 8000
  initialDelaySeconds: 30
  periodSeconds: 5
  failureThreshold: 12

2) 1단계: 내부 헤더 트래픽으로 기능 검증

  • x-llm-track: canary 헤더를 붙이는 QA/스테이징 트래픽만 canary로
  • 응답 포맷, 안전 필터, 프롬프트 템플릿, 로그/트레이싱을 확인

3) 2단계: 1% 카나리(10~30분)

  • 5xx가 stable 대비 유의미하게 증가하면 즉시 롤백
  • p95가 stable 대비 특정 임계치(예: 20%) 이상 증가하면 롤백
  • 429가 증가하면 canary의 동시성/큐 설정 문제일 수 있음

4) 3단계: 5% → 10% → 25% 점진 확대

  • 단계마다 최소 관측 시간 확보
  • 트래픽 확대 시점에만 튀는 지표는 “오토스케일/스케줄링” 문제일 가능성이 큼

5) 4단계: 50% 이후에는 롤백 비용이 커진다

  • 50%를 넘기면 사용자 영향이 커지므로, 롤백 기준을 더 보수적으로
  • 이 구간에서 “DB 락/외부 API 병목”이 같이 드러나기도 함

즉시 롤백: 라우팅 weight를 0으로

카나리 롤백의 가장 큰 장점은 “이미 stable이 살아있다”는 점입니다. 롤백은 보통 canary weight를 0으로 내리면 끝입니다.

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: llm-route
spec:
  rules:
    - matches:
        - path:
            type: PathPrefix
            value: /v1/chat/completions
      backendRefs:
        - name: llm-stable-svc
          port: 8000
          weight: 100
        - name: llm-canary-svc
          port: 8000
          weight: 0

여기서 중요한 운영 팁:

  • canary를 바로 내리지 말고(Deployment 삭제), 잠시 유지하면서 로그/메트릭/프로파일을 수집하세요.
  • 실패 원인을 “프롬프트 길이 증가”, “KV cache 메모리”, “배치 파라미터”, “GPU OOM” 중 무엇으로 분류할지 결정해야 다음 카나리가 빨라집니다.

자동화: GitOps/CI에서 카나리 단계를 관리하기

수동으로 YAML을 고치면 실수가 납니다. 보통은 아래 중 하나로 자동화합니다.

  • Argo Rollouts 같은 전용 카나리 컨트롤러
  • GitOps에서 PR 머지로 weight를 단계적으로 변경
  • CI가 메트릭을 보고 weight를 변경(권장하진 않지만 가능)

GitOps로 운영한다면 “weight 변경만 하는 PR”을 자동 생성하는 패턴이 깔끔합니다. 대규모 매트릭스 테스트나 병렬 파이프라인이 필요하면 GitHub Actions 매트릭스 빌드로 CI 50% 줄이기처럼 CI 시간을 줄이는 최적화도 같이 고려할 만합니다.

LLM 카나리에서 자주 겪는 실패 패턴 6가지

1) p95는 괜찮은데 p99만 폭발

  • 긴 프롬프트/긴 응답이 canary에서 더 자주 발생
  • max_tokens 상한, 스트리밍 설정, 배치 정책 차이 확인

2) TTFT만 느려짐

  • 프리필(prefill) 경로 최적화 차이
  • 토크나이저/프롬프트 템플릿 처리 비용 증가

3) 429 증가

  • canary replica 수가 작아 큐가 먼저 찬다
  • canary만 리소스가 부족하거나 GPU 스케줄링이 불안정

4) 특정 테넌트만 장애

  • 헤더 기반 라우팅으로 “문제 테넌트”를 분리해 재현
  • 프롬프트 정책/세이프티 필터 버전 차이 확인

5) 비용 폭증(토큰 사용량 증가)

  • 모델 변경으로 답변이 장황해짐
  • 시스템 프롬프트 변경으로 토큰이 늘어남

6) canary만 간헐적 타임아웃

  • 노드/파드 네트워크, DNS, NAT 경로 문제
  • 특히 외부 의존성이 있는 경우(권한 토큰 발급, 정책 서버 호출)에서 두드러짐

정리: Seldon v2 카나리의 운영 포인트

  • LLM 카나리는 “정확도”보다 지연 분포, 에러율, 토큰 처리량, 비용이 핵심 지표입니다.
  • stable/canary 워크로드를 분리하고, Gateway에서 가중치 라우팅으로 점진 확대하세요.
  • 롤백은 canary weight를 0으로 내리는 방식이 가장 빠르고 안전합니다.
  • 헤더 기반 라우팅을 섞으면 “내부 검증”과 “실사용자 카나리”를 깔끔하게 분리할 수 있습니다.
  • 카나리 실패의 상당수는 모델 자체가 아니라 인프라/DNS/오토스케일/큐 설정에서 터집니다. 모델 변경과 무관한 변수를 최대한 고정하고, 관측 지표로 빠르게 판별하는 Runbook을 만들어 두는 것이 장기적으로 가장 큰 비용 절감입니다.