Kubernetes에서 LLM 추론 트래픽, “그냥 라우팅”으로는 안 되는 이유와 Gateway API Inference Extension의 해법

도입

LLM 추론 트래픽을 쿠버네티스 위에 올리려다 보면, 기존 L7 로드밸런싱이 비효율적이라는 벽을 금방 만나게 됩니다. 이 글에서는 Gateway API Inference ExtensionIstio, 그리고 llm-d가 제안하는 “inference-aware 라우팅/스케줄링”이 왜 필요한지와, 제가 정리한 표준화할 메트릭(신호) 초안을 공유합니다.


기존 L7 로드밸런싱이 LLM 추론에서 깨지는 지점

일반적인 HTTP 트래픽은 “요청이 짧고, 처리 시간이 비슷하며, 서버는 대체 가능”하다는 전제가 깔려 있습니다. 하지만 LLM 추론은 전제가 다릅니다.

  • 장시간 처리 + 대형 페이로드: 한 요청이 오래 물고 늘어질 수 있습니다.
  • GPU 독점/경합: 같은 QPS라도 어떤 파드가 “실제로” 더 바쁜지 단순 커넥션 수로 알기 어렵습니다.
  • KV cache, LoRA adapter 같은 상태(state): “아무 파드나” 보내면 손해입니다.
    • KV cache가 이미 있는 파드로 보내면 TTFT(Time To First Token) 가 유리할 수 있고,
    • 특정 LoRA adapter가 로드된 파드로 보내야 “불필요한 로드/스왑”을 피할 수 있습니다.

Istio 쪽에서도 이런 이유로, 기존 L7 로드밸런싱만으로는 LLM 추론 효율이 떨어지며 모델/LoRA/실시간 메트릭 기반 동적 라우팅이 필요하다고 정리합니다.


Gateway API Inference Extension: “쿠버네티스 표준”으로 추론 라우팅을 다루기

Kubernetes 블로그에서 소개된 Gateway API Inference Extension의 핵심 메시지는 간단합니다.

  • “추론 트래픽 관리”를 각 벤더/서비스 메시의 사설 기능으로 두지 말고,
  • Gateway API를 확장해 쿠버네티스 네이티브 방식으로 표준화하자는 접근입니다.

GitHub 구현체(gateway-api-inference-extension)를 보면, 이 확장은 대체로 다음 방향으로 읽힙니다.

  • Gateway API 지원 게이트웨이를 Inference Gateway로 확장
  • Envoy의 ext-proc(External Processing) 기반으로 요청 처리 파이프라인을 열어
  • 스케줄러/Endpoint Picker가 실시간 메트릭·역량(capability)을 반영해 목적지 엔드포인트를 고르거나
  • 요청 본문 기반 라우팅(BBR; Body-Based Routing) 같은 “추론 친화 기능”을 붙이는 방식입니다.

즉, “HTTP 라우팅”이 아니라 **추론 워크로드의 특성(상태/캐시/가속기 혼잡)**을 이해하는 라우팅을, 쿠버네티스 표준 레이어에서 하겠다는 그림입니다.


llm-d: 라우팅을 넘어 “분산 추론 서빙 스택”으로 확장

라우팅은 시작점이고, 실제 비용/지연 최적화는 서빙 런타임과 스케줄링까지 내려가야 하는 경우가 많습니다. llm-d는 vLLM/SGLang 기반으로 쿠버네티스에서 분산 추론을 운영하기 위한 스택을 제공하며, 다음 같은 키워드를 전면에 둡니다.

  • Inference Scheduler 기반 지능형 스케줄링
  • Prefill / Decode 분리
  • MoE 병렬화
  • 계층형 KV cache
  • SLO/비용 고려 오토스케일링

제가 흥미롭게 본 지점은, 이 흐름이 Gateway API Inference Extension과 경쟁이라기보다 “레이어가 다르다”는 점입니다.

  • Gateway/mesh 레벨에서 inference-aware 라우팅을 하고,
  • 서빙 스택 레벨에서 프리필/디코드, 캐시, 병렬화, 오토스케일링을 최적화하는 식으로 맞물릴 수 있습니다.

“모델/LoRA/캐시 인지 라우팅”을 도입할 때, 무엇부터 표준화할까?

제가 대화 중에 정리한 결론은 이거였습니다. 감이 안 잡히는 이유는 지표가 많아서가 아니라, 처음부터 모든 최적화를 동시에 하려 해서입니다. 그래서 저는 신호를 3층으로 나눠 “먼저 표준화할 최소 세트”를 잡는 접근이 현실적이라고 봅니다.

1) 요청 수준(Request-level): 들어오는 순간 계산 가능한 신호

요청이 들어온 순간, 게이트웨이/프록시가 계산하거나 헤더로 전달받아 쓸 수 있는 신호입니다.

  • input_token_count (또는 근사치)
  • prefix_hash (KV cache 재사용 가능성을 추정하기 위한 키)
  • criticality (interactive / batch 같은 클래스)

여기서 주의점도 있습니다. prefix_hash는 “KV 재사용 가능성”의 필요조건일 수 있지만, 충분조건이 아닐 수 있습니다(시스템 프롬프트 변형, tool 호출, 샘플링 파라미터 차이, RAG retrieval 결과 변화 등). 그럼에도 불구하고 “표준 키”를 하나 정해두면, 라우팅 실험을 시작할 수 있는 발판이 됩니다.

2) 파드 수준(Pod-level): 실시간 폴링/스크랩으로 얻는 혼잡·상태

파드/런타임이 현재 얼마나 바쁜지, 어떤 상태를 들고 있는지에 대한 신호입니다.

  • gpu_kv_cache_usage_perc
  • num_requests_waiting
  • num_requests_running
  • lora_adapter_loaded{adapter_id=...} (또는 이를 나타내는 capability)

다만 폴링 기반은 신선도 vs 오버헤드 트레이드오프가 있어서, “몇 초마다 볼 것인가”가 정책 품질에 직접 영향을 줍니다.

3) 캐시 수준(Cache-level): 이벤트 기반 신호(정확하지만 전달 신뢰성이 관건)

예: llm-d가 ZMQ 이벤트로 다루는 것처럼 캐시 블록 단위 이벤트를 받는 형태를 상정할 수 있습니다.

  • BlockStored
  • BlockRemoved

이벤트 기반은 정확하지만, 운영에서는 유실/지연이 생기면 라우팅이 흔들릴 수 있어 “관측 신뢰성”을 먼저 확보해야 합니다.


인사이트: “Criticality 1순위”는 맞지만, 혼잡 신호를 버리면 SLO를 깨기 쉽습니다

대화에서 저는 우선순위를 이렇게 두고 싶었습니다.

  1. criticality: SLO breach가 고객 경험에 직격
  2. cache-hit 가능성: TTFT를 “추가 비용 없이” 줄일 수 있는 가장 싼 방법
  3. 큐 혼잡: 인프라가 감당할 문제(…라고 생각하고 싶음)

그런데 실제로는, 인터랙티브를 무조건 1순위로 밀어 넣는 라우팅이 hotspot을 만들어 tail latency(p95/p99)를 더 악화시킬 수 있습니다. 결과적으로 “혼잡은 인프라가 해결”이 아니라, 어떤 순간엔 라우팅이 혼잡을 만들기도/완화하기도 하는 레버가 됩니다.

그래서 제가 지금 단계에서 제안하고 싶은 운영적인 결론은 이렇습니다.

  • “criticality 1순위” 원칙은 유지하되,
  • 예외(가드레일) 조건 1~2개만 먼저 둬도 효과가 큽니다.
    예: gpu_kv_cache_usage_perc > 85% 이거나 num_requests_waiting이 특정 임계치를 넘으면
    → 캐시 적중보다 덜 바쁜 파드로 우회.

플랫폼형(다양한 워크로드가 올라오는) 환경이라면 더더욱, “완벽한 목적함수”를 만들기 전에 워크로드 클래스를 최소 개수로 분류하고(예: 대고객 RAG vs 내부 분석 배치), 클래스별로 가드레일을 다르게 가져가는 게 첫 단추가 되기 쉽습니다.


간단 예시: 라우팅 점수(스케치)

아래는 “표준화된 신호”를 받았다는 가정 하에, Endpoint Picker가 쓸 법한 점수 계산의 스케치입니다(개념 예시입니다).

if request.criticality == "interactive":
  # 가드레일: 너무 혼잡하면 캐시/LoRA 이득을 포기하고 우회
  if pod.num_requests_waiting > W || pod.gpu_kv_cache_usage_perc > 85:
    score = -INF
  else:
    score = cache_hit_score(prefix_hash, pod) * 0.6
          + lora_match_score(adapter_id, pod) * 0.3
          - congestion_score(pod) * 0.1
else: # batch/analysis
  score = throughput_score(pod) * 0.6
        - cost_score(pod) * 0.3
        - congestion_score(pod) * 0.1
pick max(score)

핵심은 “정교한 수식”이 아니라,

  • 요청/파드/캐시 신호의 표준화
  • criticality 우선 + 단순 가드레일
    로 첫 버전을 만들고, SLO/비용 데이터가 쌓이면 점진적으로 고도화하는 흐름입니다.

마무리

Gateway API Inference Extension과 Istio, llm-d가 공통으로 말하는 방향은 명확합니다. 쿠버네티스 표준(Gateway API) 위에서, 메트릭·캐시·어댑터 상태를 이해하는 inference-aware 라우팅/스케줄링으로 LLM 추론의 지연과 비용을 줄이자는 것입니다. 제 경험상 첫 단계는 “완벽한 정책”이 아니라, 표준화된 최소 신호 세트와 가드레일을 합의하는 데서 시작하는 편이 성공 확률이 높습니다.


참고 자료

1 Like