第33篇-KServe推理平台-标准化的K8s推理服务管理

【AIaaS 全栈架构师】第 33 篇:KServe 推理平台------标准化的 K8s 推理服务管理

系列定位:AIaaS 全栈架构师教程,技术栈以 Go 为主。本篇聚焦 KServe------K8s 上的推理服务标准,拆解 InferenceService CRD、推理服务生命周期、自动扩缩容(HPA/KEDA)、Canary/蓝绿/影子部署,以及 InferenceGraph 多模型编排。


本篇你将学到

  • 为什么裸 Deployment + Service 管理推理服务极其痛苦,需要专门的 CRD
  • KServe 的 InferenceService CRD 设计:Predictor / Transformer / Explainer 三组件
  • 推理服务的完整生命周期:创建、路由、扩缩容、缩零、更新
  • Knative Serving 与 KServe 的关系:Scale-to-zero 与流量管理
  • 基于 HPA 和 KEDA 的自动扩缩容差异与选型
  • Canary、蓝绿、影子(Shadow)部署的实现原理
  • InferenceGraph 多模型编排:级联、路由、集成
  • KServe 与 vLLM / TGI / Triton 等推理引擎的集成方式

前面几篇我们解决了训练的方方面面------从 K8s 调度到 Training Operator,从 Ray 到弹性训练。训练产出的模型最终要变成线上服务。如果你直接用 K8s 原生的 Deployment + Service 来管理推理服务,很快就会发现:每个模型都要手写扩缩容规则、手写灰度发布逻辑、手写健康检查、手写流量路由。几十个模型下来,运维代码比业务代码还多。KServe 就是为解决这个问题而生的「推理服务专用 Operator」。这一篇我们彻底拆解它。


一、为什么需要 KServe

1.1 裸 Deployment 管理推理服务的痛点

先看用原生 K8s 管理一个 LLM 推理服务需要做什么:

yaml 复制代码
# 你需要写的 YAML(简化版,实际更复杂)
apiVersion: apps/v1
kind: Deployment
metadata:
  name: llama-serve
spec:
  replicas: 4
  selector:
    matchLabels:
      app: llama-serve
  template:
    spec:
      containers:
        - name: vllm
          image: vllm/vllm-openai:latest
          args: [--model, /models/llama-8b, --tensor-parallel-size, 4]
          resources:
            limits:
              nvidia.com/gpu: 4
          readinessProbe: ...   # 手写
          livenessProbe: ...    # 手写
---
apiVersion: v1
kind: Service
metadata:
  name: llama-serve
spec:
  selector:
    app: llama-serve
  ports: [...]
---
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: llama-serve
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: llama-serve
  minReplicas: 1               # 无法缩到 0
  maxReplicas: 16
  metrics:
    - type: Pods               # 自定义 QPS 指标
      pods:
        metric:
          name: inference_requests_per_second
---
# 灰度发布?再写一套 Deployment + Service + Istio VirtualService...
# 影子部署?再写一套...
# 多模型级联?自己写编排逻辑...

这还只是一个模型。当你有几十上百个模型,每个都要这么搞,痛点成倍放大:

痛点 裸 K8s 期望
扩缩容 HPA 基于 CPU,推理是 GPU 密集 基于推理 QPS / 并发
缩零 HPA 无法 scale to 0 空闲时自动缩零,省钱
灰度 手写多 Deployment + 流量权重 声明式 canaryTrafficPercent: 10
模型加载 容器启动时挂载 框架自动拉取(从 Model Registry)
推理引擎适配 每个引擎不同的启动参数 统一抽象(Predictor spec)
前后处理 全塞进容器 Transformer 组件独立扩缩
多模型编排 自己写路由 InferenceGraph CRD

KServe 的目标就是把这些推理服务特有的需求封装成一个 CRD,让运维像写 Deployment 一样简单。

1.2 KServe 的定位

KServe(原名 KFServing)是 CNCF 项目,定位是「K8s 上的推理服务标准」。它的核心价值:

  1. InferenceService CRD:声明式定义推理服务,封装推理引擎、扩缩容、路由。
  2. 推理引擎无关:内置支持 vLLM、TGI、Triton、ONNX Runtime、PyTorch Serve 等。
  3. Scale-to-zero:基于 Knative,空闲自动缩零,请求来了自动拉起。
  4. 流量管理:原生 Canary、蓝绿、影子部署,通过 Istio/Kourier 实现。
  5. 多组件架构:Predictor(推理)、Transformer(前后处理)、Explainer(可解释性)可独立扩缩。
  6. InferenceGraph:多模型级联、路由、集成的声明式编排。

1.3 KServe 在 AIaaS 平台中的位置

#mermaid-svg-w4CZDOaJHAe0qtzF{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-w4CZDOaJHAe0qtzF .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-w4CZDOaJHAe0qtzF .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-w4CZDOaJHAe0qtzF .error-icon{fill:#552222;}#mermaid-svg-w4CZDOaJHAe0qtzF .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-w4CZDOaJHAe0qtzF .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-w4CZDOaJHAe0qtzF .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-w4CZDOaJHAe0qtzF .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-w4CZDOaJHAe0qtzF .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-w4CZDOaJHAe0qtzF .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-w4CZDOaJHAe0qtzF .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-w4CZDOaJHAe0qtzF .marker{fill:#333333;stroke:#333333;}#mermaid-svg-w4CZDOaJHAe0qtzF .marker.cross{stroke:#333333;}#mermaid-svg-w4CZDOaJHAe0qtzF svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-w4CZDOaJHAe0qtzF p{margin:0;}#mermaid-svg-w4CZDOaJHAe0qtzF .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-w4CZDOaJHAe0qtzF .cluster-label text{fill:#333;}#mermaid-svg-w4CZDOaJHAe0qtzF .cluster-label span{color:#333;}#mermaid-svg-w4CZDOaJHAe0qtzF .cluster-label span p{background-color:transparent;}#mermaid-svg-w4CZDOaJHAe0qtzF .label text,#mermaid-svg-w4CZDOaJHAe0qtzF span{fill:#333;color:#333;}#mermaid-svg-w4CZDOaJHAe0qtzF .node rect,#mermaid-svg-w4CZDOaJHAe0qtzF .node circle,#mermaid-svg-w4CZDOaJHAe0qtzF .node ellipse,#mermaid-svg-w4CZDOaJHAe0qtzF .node polygon,#mermaid-svg-w4CZDOaJHAe0qtzF .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-w4CZDOaJHAe0qtzF .rough-node .label text,#mermaid-svg-w4CZDOaJHAe0qtzF .node .label text,#mermaid-svg-w4CZDOaJHAe0qtzF .image-shape .label,#mermaid-svg-w4CZDOaJHAe0qtzF .icon-shape .label{text-anchor:middle;}#mermaid-svg-w4CZDOaJHAe0qtzF .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-w4CZDOaJHAe0qtzF .rough-node .label,#mermaid-svg-w4CZDOaJHAe0qtzF .node .label,#mermaid-svg-w4CZDOaJHAe0qtzF .image-shape .label,#mermaid-svg-w4CZDOaJHAe0qtzF .icon-shape .label{text-align:center;}#mermaid-svg-w4CZDOaJHAe0qtzF .node.clickable{cursor:pointer;}#mermaid-svg-w4CZDOaJHAe0qtzF .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-w4CZDOaJHAe0qtzF .arrowheadPath{fill:#333333;}#mermaid-svg-w4CZDOaJHAe0qtzF .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-w4CZDOaJHAe0qtzF .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-w4CZDOaJHAe0qtzF .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-w4CZDOaJHAe0qtzF .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-w4CZDOaJHAe0qtzF .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-w4CZDOaJHAe0qtzF .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-w4CZDOaJHAe0qtzF .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-w4CZDOaJHAe0qtzF .cluster text{fill:#333;}#mermaid-svg-w4CZDOaJHAe0qtzF .cluster span{color:#333;}#mermaid-svg-w4CZDOaJHAe0qtzF div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-w4CZDOaJHAe0qtzF .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-w4CZDOaJHAe0qtzF rect.text{fill:none;stroke-width:0;}#mermaid-svg-w4CZDOaJHAe0qtzF .icon-shape,#mermaid-svg-w4CZDOaJHAe0qtzF .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-w4CZDOaJHAe0qtzF .icon-shape p,#mermaid-svg-w4CZDOaJHAe0qtzF .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-w4CZDOaJHAe0qtzF .icon-shape .label rect,#mermaid-svg-w4CZDOaJHAe0qtzF .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-w4CZDOaJHAe0qtzF .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-w4CZDOaJHAe0qtzF .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-w4CZDOaJHAe0qtzF :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 推理引擎
数据面
KServe 控制面
创建/管理
创建/管理
终端用户 / 应用
LLM API 网关

(第 22-24 篇)
InferenceService CRD
KServe Controller
Ingress / Istio Gateway
InferenceService: llama-v2
InferenceService: whisper
InferenceGraph: 多模型编排
vLLM Pod
TGI Pod
Triton Pod

KServe 处于 LLM API 网关(认证、限流、计量)和底层推理引擎(vLLM、TGI)之间。网关负责平台层能力,KServe 负责推理服务生命周期管理,推理引擎负责实际计算。三者分层清晰。


二、KServe 架构

2.1 整体架构

#mermaid-svg-Q4ihuZEbGNC8Rfpj{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-Q4ihuZEbGNC8Rfpj .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-Q4ihuZEbGNC8Rfpj .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-Q4ihuZEbGNC8Rfpj .error-icon{fill:#552222;}#mermaid-svg-Q4ihuZEbGNC8Rfpj .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-Q4ihuZEbGNC8Rfpj .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-Q4ihuZEbGNC8Rfpj .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Q4ihuZEbGNC8Rfpj .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Q4ihuZEbGNC8Rfpj .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-Q4ihuZEbGNC8Rfpj .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Q4ihuZEbGNC8Rfpj .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Q4ihuZEbGNC8Rfpj .marker{fill:#333333;stroke:#333333;}#mermaid-svg-Q4ihuZEbGNC8Rfpj .marker.cross{stroke:#333333;}#mermaid-svg-Q4ihuZEbGNC8Rfpj svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Q4ihuZEbGNC8Rfpj p{margin:0;}#mermaid-svg-Q4ihuZEbGNC8Rfpj .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-Q4ihuZEbGNC8Rfpj .cluster-label text{fill:#333;}#mermaid-svg-Q4ihuZEbGNC8Rfpj .cluster-label span{color:#333;}#mermaid-svg-Q4ihuZEbGNC8Rfpj .cluster-label span p{background-color:transparent;}#mermaid-svg-Q4ihuZEbGNC8Rfpj .label text,#mermaid-svg-Q4ihuZEbGNC8Rfpj span{fill:#333;color:#333;}#mermaid-svg-Q4ihuZEbGNC8Rfpj .node rect,#mermaid-svg-Q4ihuZEbGNC8Rfpj .node circle,#mermaid-svg-Q4ihuZEbGNC8Rfpj .node ellipse,#mermaid-svg-Q4ihuZEbGNC8Rfpj .node polygon,#mermaid-svg-Q4ihuZEbGNC8Rfpj .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-Q4ihuZEbGNC8Rfpj .rough-node .label text,#mermaid-svg-Q4ihuZEbGNC8Rfpj .node .label text,#mermaid-svg-Q4ihuZEbGNC8Rfpj .image-shape .label,#mermaid-svg-Q4ihuZEbGNC8Rfpj .icon-shape .label{text-anchor:middle;}#mermaid-svg-Q4ihuZEbGNC8Rfpj .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-Q4ihuZEbGNC8Rfpj .rough-node .label,#mermaid-svg-Q4ihuZEbGNC8Rfpj .node .label,#mermaid-svg-Q4ihuZEbGNC8Rfpj .image-shape .label,#mermaid-svg-Q4ihuZEbGNC8Rfpj .icon-shape .label{text-align:center;}#mermaid-svg-Q4ihuZEbGNC8Rfpj .node.clickable{cursor:pointer;}#mermaid-svg-Q4ihuZEbGNC8Rfpj .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-Q4ihuZEbGNC8Rfpj .arrowheadPath{fill:#333333;}#mermaid-svg-Q4ihuZEbGNC8Rfpj .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-Q4ihuZEbGNC8Rfpj .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-Q4ihuZEbGNC8Rfpj .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Q4ihuZEbGNC8Rfpj .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-Q4ihuZEbGNC8Rfpj .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Q4ihuZEbGNC8Rfpj .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-Q4ihuZEbGNC8Rfpj .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-Q4ihuZEbGNC8Rfpj .cluster text{fill:#333;}#mermaid-svg-Q4ihuZEbGNC8Rfpj .cluster span{color:#333;}#mermaid-svg-Q4ihuZEbGNC8Rfpj div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-Q4ihuZEbGNC8Rfpj .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-Q4ihuZEbGNC8Rfpj rect.text{fill:none;stroke-width:0;}#mermaid-svg-Q4ihuZEbGNC8Rfpj .icon-shape,#mermaid-svg-Q4ihuZEbGNC8Rfpj .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Q4ihuZEbGNC8Rfpj .icon-shape p,#mermaid-svg-Q4ihuZEbGNC8Rfpj .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-Q4ihuZEbGNC8Rfpj .icon-shape .label rect,#mermaid-svg-Q4ihuZEbGNC8Rfpj .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Q4ihuZEbGNC8Rfpj .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-Q4ihuZEbGNC8Rfpj .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-Q4ihuZEbGNC8Rfpj :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} InferenceService: llama-serve
网络层
KServe 控制面
Explainer (解释)
Transformer (前后处理)
Predictor (推理)
reconcile
scale 0⇄N
流量拆分
可选
KServe Controller Manager
Admission Webhook

(默认值注入)
Knative Controller

(Scale-to-zero)
Istio / Kourier

(Ingress Gateway)
Knative Service
Pod: vLLM

(2 replica, 4 GPU)
Knative Service
Pod: tokenizer
Knative Service
Pod: SHAP
客户端

KServe 的架构有三个关键层次:

控制面(KServe Controller)

  • Watch InferenceService CR,reconcile 到期望状态。
  • 为每个组件创建 Knative Service、Deployment、HPA。
  • Admission Webhook 注入默认值(容器镜像、资源配额、存储凭证)。

网络层(Istio / Kourier)

  • 所有推理流量经过 Ingress Gateway。
  • 实现 Canary 流量拆分、影子流量镜像。
  • 提供 /v1/models/<model>:predict 等标准 URL。

数据面(InferenceService 实例)

  • 每个组件(Predictor / Transformer / Explainer)是一个 Knative Service。
  • Predictor 跑推理引擎(vLLM / TGI / Triton)。
  • Transformer 做前后处理(分词、后处理、脱敏)。
  • Explainer 做模型解释(SHAP / LIME)。

2.2 三组件模型

InferenceService 的精妙之处在于把推理服务拆成三个可独立扩缩的组件:

组件 职责 扩缩依据 典型实现
Predictor 模型推理 GPU 利用率 / 并发 vLLM, TGI, Triton, ONNX
Transformer 请求预处理 + 响应后处理 CPU QPS 自定义 Python / Go 服务
Explainer 模型可解释性 按需(不常调) SHAP, LIME, AIX360

为什么拆开?因为它们的资源特征和扩缩需求完全不同:

  • Predictor 是 GPU 密集,扩缩看并发数,成本高。
  • Transformer 是 CPU 密集,扩缩看 QPS,成本低。
  • Explainer 偶尔调用,可以 scale-to-zero。

如果全塞进一个容器,GPU Pod 为了处理 CPU 密集的分词而扩容,浪费 GPU。拆开后各自独立扩缩,成本最优。

2.3 Knative Serving 的角色

KServe 的 Scale-to-zero 能力来自 Knative Serving。Knative 在 K8s 之上增加了一层「Serverless」抽象:

复制代码
InferenceService (KServe CRD)
    → Knative Service (Knative CRD)
        → Deployment + Service + HPA (K8s 原生)

Knative 的核心能力:

  1. Scale-to-zero:Activator 组件拦截请求,无流量时 Pod 数缩为 0;请求来了,Activator 先 hold 住请求,同时触发 Pod 拉起。
  2. 流量拆分:Revision 机制,新版本和旧版本可按百分比拆分流量。
  3. 冷启动优化:支持 predownload 镜像、并发控制。

这是 KServe 能做到「空闲模型不占 GPU」的关键。


三、InferenceService CRD 详解

3.1 最简 InferenceService

部署一个 vLLM 推理服务:

yaml 复制代码
apiVersion: serving.kserve.io/v1beta1
kind: InferenceService
metadata:
  name: llama-8b
  namespace: production
spec:
  predictor:
    model:
      modelFormat:
        name: vLLM
      storageUri: pvc://model-pvc/models/llama-8b   # 模型存储位置
      resources:
        limits:
          cpu: "8"
          memory: 32Gi
          nvidia.com/gpu: 1
        requests:
          cpu: "4"
          memory: 16Gi
          nvidia.com/gpu: 1
    scaleTarget: 1            # 期望并发
    scaleMetric: concurrency  # 扩缩容指标

部署后:

bash 复制代码
$ kubectl get inferenceservice -n production
NAME        URL                                    READY   AGE
llama-8b    http://llama-8b.production.example.com  True    2m

注意 URL:KServe 自动为每个 InferenceService 创建一个外部可访问的域名(基于 Istio/Kourier Ingress)。客户端直接 POST 到这个 URL 的 /v1/completions 即可。

3.2 字段详解

yaml 复制代码
apiVersion: serving.kserve.io/v1beta1
kind: InferenceService
metadata:
  name: llama-8b
  annotations:
    serving.kserve.io/enable-prometheus-scraping: "true"  # 开启指标采集
spec:
  predictor:
    # --- 模型定义 ---
    model:
      modelFormat:
        name: vLLM                    # 推理引擎 / 框架名
      runtime: kserve-vllm            # KServe 预置 runtime
      storageUri: pvc://model-pvc     # 或 s3://bucket/path, hdfs://...
      protocolVersion: v2             # 推理协议: v1(KServe) / v2(OpenAI)
      # --- 引擎特定参数 ---
      env:
        - name: VLLM_TENSOR_PARALLEL_SIZE
          value: "1"
        - name: VLLM_GPU_MEMORY_UTILIZATION
          value: "0.9"
    # --- 扩缩容 ---
    minReplicas: 0                    # 最小 0 (scale-to-zero)
    maxReplicas: 8                    # 最大 8
    scaleTarget: 10                   # 目标并发
    scaleMetric: concurrency
    # --- 超时与优雅终止 ---
    timeoutSeconds: 300               # 推理超时 (LLM 流式可能长)
    terminationGracePeriodSeconds: 60
    # --- 节点选择 ---
    nodeSelector:
      nvidia.com/gpu.product: A100-SXM4-80GB
    tolerations:
      - key: nvidia.com/gpu
        operator: Exists
  # --- Transformer (可选) ---
  transformer:
    containers:
      - image: registry.internal/llm-transformer:latest
        name: transformer
        resources:
          limits:
            cpu: "2"
            memory: 4Gi

关键字段说明:

字段 含义 关键值
modelFormat.name 模型格式 / 引擎 vLLM, TGI, pytorch, onnx, triton
runtime KServe 预置运行时 kserve-vllm, kserve-tgi, kserve-triton
storageUri 模型存储位置 pvc://, s3://, hdfs://, gs://
protocolVersion 推理协议 v1(KServe 原生), v2(OpenAI 兼容)
minReplicas 最小副本 0 = scale-to-zero
scaleMetric 扩缩指标 concurrency, rps, cpu, memory
scaleTarget 扩缩目标值 每副本期望并发/RPS

3.3 推理协议

KServe 支持两种推理协议:

v1 协议(KServe 原生)

http 复制代码
POST /v1/models/llama-8b:predict
{
  "instances": [
    {"prompt": "Hello"},
    {"prompt": "World"}
  ]
}

v2 协议(OpenAI 兼容)

http 复制代码
POST /v1/completions
{
  "model": "llama-8b",
  "prompt": "Hello",
  "max_tokens": 100
}

v2 协议与 OpenAI API 完全兼容,客户端无需修改即可切换底层模型。这是 KServe 对接 LLM API 网关(第 22 篇)的推荐协议。

3.4 支持的推理引擎

KServe 内置了多种推理引擎的 Runtime(ServingRuntime CRD):

Runtime 适用场景 特点
kserve-vllm LLM 推理 PagedAttention, Continuous Batching
kserve-tgi LLM 推理 HuggingFace 原生, 流式输出
kserve-triton 多框架 支持 PyTorch/TF/ONNX/TensorRT
kserve-lgb LightGBM 传统 ML
kserve-sklearn scikit-learn 传统 ML
kserve-paddleserving PaddlePaddle PaddlePaddle 模型
自定义 Runtime 特殊需求 实现 KServe 协议即可

自定义 Runtime 的例子(用 Go 写一个自定义推理服务):

yaml 复制代码
apiVersion: serving.kserve.io/v1alpha1
kind: ServingRuntime
metadata:
  name: my-custom-runtime
spec:
  supportedModelFormats:
    - name: my-format
      version: "1"
  containers:
    - name: kserve-container
      image: registry.internal/my-inference:latest
      args:
        - --port=8080
        - --model-dir=/mnt/models
      resources:
        limits:
          nvidia.com/gpu: 1

四、推理服务生命周期

4.1 完整生命周期流程

#mermaid-svg-6LqY8L5mijsJq2mS{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-6LqY8L5mijsJq2mS .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-6LqY8L5mijsJq2mS .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-6LqY8L5mijsJq2mS .error-icon{fill:#552222;}#mermaid-svg-6LqY8L5mijsJq2mS .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-6LqY8L5mijsJq2mS .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-6LqY8L5mijsJq2mS .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-6LqY8L5mijsJq2mS .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-6LqY8L5mijsJq2mS .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-6LqY8L5mijsJq2mS .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-6LqY8L5mijsJq2mS .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-6LqY8L5mijsJq2mS .marker{fill:#333333;stroke:#333333;}#mermaid-svg-6LqY8L5mijsJq2mS .marker.cross{stroke:#333333;}#mermaid-svg-6LqY8L5mijsJq2mS svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-6LqY8L5mijsJq2mS p{margin:0;}#mermaid-svg-6LqY8L5mijsJq2mS .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-6LqY8L5mijsJq2mS .cluster-label text{fill:#333;}#mermaid-svg-6LqY8L5mijsJq2mS .cluster-label span{color:#333;}#mermaid-svg-6LqY8L5mijsJq2mS .cluster-label span p{background-color:transparent;}#mermaid-svg-6LqY8L5mijsJq2mS .label text,#mermaid-svg-6LqY8L5mijsJq2mS span{fill:#333;color:#333;}#mermaid-svg-6LqY8L5mijsJq2mS .node rect,#mermaid-svg-6LqY8L5mijsJq2mS .node circle,#mermaid-svg-6LqY8L5mijsJq2mS .node ellipse,#mermaid-svg-6LqY8L5mijsJq2mS .node polygon,#mermaid-svg-6LqY8L5mijsJq2mS .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-6LqY8L5mijsJq2mS .rough-node .label text,#mermaid-svg-6LqY8L5mijsJq2mS .node .label text,#mermaid-svg-6LqY8L5mijsJq2mS .image-shape .label,#mermaid-svg-6LqY8L5mijsJq2mS .icon-shape .label{text-anchor:middle;}#mermaid-svg-6LqY8L5mijsJq2mS .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-6LqY8L5mijsJq2mS .rough-node .label,#mermaid-svg-6LqY8L5mijsJq2mS .node .label,#mermaid-svg-6LqY8L5mijsJq2mS .image-shape .label,#mermaid-svg-6LqY8L5mijsJq2mS .icon-shape .label{text-align:center;}#mermaid-svg-6LqY8L5mijsJq2mS .node.clickable{cursor:pointer;}#mermaid-svg-6LqY8L5mijsJq2mS .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-6LqY8L5mijsJq2mS .arrowheadPath{fill:#333333;}#mermaid-svg-6LqY8L5mijsJq2mS .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-6LqY8L5mijsJq2mS .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-6LqY8L5mijsJq2mS .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-6LqY8L5mijsJq2mS .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-6LqY8L5mijsJq2mS .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-6LqY8L5mijsJq2mS .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-6LqY8L5mijsJq2mS .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-6LqY8L5mijsJq2mS .cluster text{fill:#333;}#mermaid-svg-6LqY8L5mijsJq2mS .cluster span{color:#333;}#mermaid-svg-6LqY8L5mijsJq2mS div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-6LqY8L5mijsJq2mS .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-6LqY8L5mijsJq2mS rect.text{fill:none;stroke-width:0;}#mermaid-svg-6LqY8L5mijsJq2mS .icon-shape,#mermaid-svg-6LqY8L5mijsJq2mS .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-6LqY8L5mijsJq2mS .icon-shape p,#mermaid-svg-6LqY8L5mijsJq2mS .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-6LqY8L5mijsJq2mS .icon-shape .label rect,#mermaid-svg-6LqY8L5mijsJq2mS .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-6LqY8L5mijsJq2mS .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-6LqY8L5mijsJq2mS .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-6LqY8L5mijsJq2mS :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是



用户提交

InferenceService YAML
KServe Controller

reconcile
创建 Knative Service

(Predictor/Transformer)
创建 Deployment + Service

  • HPA + VirtualService
    minReplicas

== 0?
Activator 接管

初始 Pod 数 = 0
拉起 minReplicas 个 Pod
等待第一个请求
Pod Ready

路由生效
请求到达 Activator
Activator 触发

Pod 拉起
Pod Ready

转发请求
流量持续

HPA 扩容
流量消失

持续 idle?
Scale to zero

Pod 数 → 0

4.2 Scale-to-zero 的冷启动

Scale-to-zero 是 KServe 的杀手特性,但有冷启动代价。一个被缩零的 LLM 推理服务,请求到来时的恢复流程:

复制代码
1. 请求到达 Ingress Gateway → 路由到 Activator
2. Activator 持有请求(不返回),触发 Knative 拉起 Pod
3. Pod 调度 → 拉镜像 → 启动 vLLM → 加载模型权重
4. Pod Ready → Activator 转发请求
5. 后续请求直接路由到 Pod(绕过 Activator)

冷启动时间构成:

阶段 耗时 优化手段
Pod 调度 1-5s 节点有 GPU 时秒级
拉镜像 10-60s 预拉镜像(DaemonSet preload)
启动引擎 2-5s -
加载模型权重 10-120s NVMe SSD, 模型分片并行加载
总计 30-200s -

对于 LLM(几十 GB 权重),冷启动可能高达 2-3 分钟。这就是为什么 LLM 推理服务通常设置 minReplicas: 1(保持至少一个热实例),而不是 scale-to-zero。

实践建议

  • 高频 / SLA 敏感模型:minReplicas: 1,永不缩零。
  • 低频 / 批处理模型:minReplicas: 0,省钱但接受冷启动。
  • 中频模型:minReplicas: 0 + scaleToZeroGracePeriod: 300(5 分钟无流量才缩零)。

4.3 自动扩缩容

KServe 的扩缩容有两种模式:

模式一:Knative Pod Autoscaler(KPA)

Knative 内置的扩缩容,基于并发(concurrency)或 RPS:

yaml 复制代码
spec:
  predictor:
    autoscaling.knative.dev/class: kpa.autoscaling.knative.dev
    autoscaling.knative.dev/metric: concurrency
    autoscaling.knative.dev/target: "10"       # 每副本目标并发 10
    autoscaling.knative.dev/min-scale: "1"
    autoscaling.knative.dev/max-scale: "16"

KPA 的优势是原生支持 scale-to-zero,扩缩容速度快(秒级),不依赖外部指标系统。

模式二:HPA(Horizontal Pod Autoscaler)

基于 CPU / 内存 / 自定义指标:

yaml 复制代码
spec:
  predictor:
    autoscaling.knative.dev/class: hpa.autoscaling.knative.dev
    autoscaling.knative.dev/metric: cpu
    autoscaling.knative.dev/target: "80"       # CPU 80%

HPA 不支持 scale-to-zero,但可以对接 Prometheus 自定义指标。

模式三:KEDA(Kubernetes Event-Driven Autoscaling)

KEDA 是更灵活的事件驱动扩缩容,支持基于 Kafka 队列长度、Redis、外部指标等扩缩:

yaml 复制代码
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
  name: llama-keda
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: llama-serve-predictor
  minReplicaCount: 0
  maxReplicaCount: 16
  triggers:
    - type: prometheus
      metadata:
        serverAddress: http://prometheus:9090
        metricName: inference_requests
        threshold: "100"                   # 超过 100 并发就扩容
        query: sum(rate(inference_requests_total{model="llama-8b"}[1m]))

三种扩缩容的对比:

维度 KPA HPA KEDA
Scale-to-zero 原生支持 不支持 支持
扩缩速度 秒级 分钟级 秒级
指标来源 并发 / RPS CPU / Prometheus 50+ 数据源
复杂度
LLM 推理推荐 是(concurrency) 是(队列驱动)

LLM 推理服务推荐用 KPA + concurrency,因为它直接反映「每个推理副本能处理多少并发请求」,是推理 SLA 的核心指标。

4.4 推理并发模型

理解 KServe 扩缩容的关键是推理的并发模型。与 Web 服务不同,LLM 推理是长连接、流式的:

复制代码
传统 Web API:
  请求 → 处理(10ms) → 响应
  单副本可扛 1000 QPS

LLM 推理:
  请求 → 生成 500 tokens (10-30s) → 流式响应
  单副本(vLLM, A100) 并发上限约 32-64 (Continuous Batching)

因此 LLM 推理的扩缩容指标应该是并发请求数,而非 QPS:

复制代码
需要的副本数 = ceil(当前并发请求数 / 每副本并发上限)

例如: 当前 200 并发, vLLM 单副本支持 40 并发
  副本数 = ceil(200/40) = 5
yaml 复制代码
spec:
  predictor:
    autoscaling.knative.dev/class: kpa.autoscaling.knative.dev
    autoscaling.knative.dev/metric: concurrency
    autoscaling.knative.dev/target: "40"       # vLLM 的安全并发
    autoscaling.knative.dev/max-scale: "16"

五、Canary / 蓝绿 / 影子部署

5.1 Canary(金丝雀)部署

KServe 的 Canary 部署通过 canaryTrafficPercent 字段声明式实现:

yaml 复制代码
apiVersion: serving.kserve.io/v1beta1
kind: InferenceService
metadata:
  name: llama-8b
spec:
  predictor:
    model:
      modelFormat:
        name: vLLM
      storageUri: pvc://model-pvc/models/llama-8b-v1
  # --- Canary: 新版本 ---
  transformer:
    containers:
      - image: transformer:v2

当 InferenceService 更新(如换模型版本)时,KServe 自动创建新 Revision 并按流量百分比拆分:

yaml 复制代码
# 通过 annotation 控制流量
metadata:
  annotations:
    serving.knative.dev/traffic: |
      [
        {"revisionName": "llama-8b-v1", "percent": 90},
        {"revisionName": "llama-8b-v2", "percent": 10, "tag": "canary"}
      ]

这意味着 90% 流量走旧版本,10% 走新版本。带 tag: canary 的版本可以通过独立 URL 访问(http://llama-8b-canary.production.example.com),便于定向测试。

流量拆分的实现:
#mermaid-svg-UP3MZcVWMnQzFCyF{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-UP3MZcVWMnQzFCyF .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-UP3MZcVWMnQzFCyF .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-UP3MZcVWMnQzFCyF .error-icon{fill:#552222;}#mermaid-svg-UP3MZcVWMnQzFCyF .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-UP3MZcVWMnQzFCyF .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-UP3MZcVWMnQzFCyF .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-UP3MZcVWMnQzFCyF .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-UP3MZcVWMnQzFCyF .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-UP3MZcVWMnQzFCyF .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-UP3MZcVWMnQzFCyF .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-UP3MZcVWMnQzFCyF .marker{fill:#333333;stroke:#333333;}#mermaid-svg-UP3MZcVWMnQzFCyF .marker.cross{stroke:#333333;}#mermaid-svg-UP3MZcVWMnQzFCyF svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-UP3MZcVWMnQzFCyF p{margin:0;}#mermaid-svg-UP3MZcVWMnQzFCyF .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-UP3MZcVWMnQzFCyF .cluster-label text{fill:#333;}#mermaid-svg-UP3MZcVWMnQzFCyF .cluster-label span{color:#333;}#mermaid-svg-UP3MZcVWMnQzFCyF .cluster-label span p{background-color:transparent;}#mermaid-svg-UP3MZcVWMnQzFCyF .label text,#mermaid-svg-UP3MZcVWMnQzFCyF span{fill:#333;color:#333;}#mermaid-svg-UP3MZcVWMnQzFCyF .node rect,#mermaid-svg-UP3MZcVWMnQzFCyF .node circle,#mermaid-svg-UP3MZcVWMnQzFCyF .node ellipse,#mermaid-svg-UP3MZcVWMnQzFCyF .node polygon,#mermaid-svg-UP3MZcVWMnQzFCyF .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-UP3MZcVWMnQzFCyF .rough-node .label text,#mermaid-svg-UP3MZcVWMnQzFCyF .node .label text,#mermaid-svg-UP3MZcVWMnQzFCyF .image-shape .label,#mermaid-svg-UP3MZcVWMnQzFCyF .icon-shape .label{text-anchor:middle;}#mermaid-svg-UP3MZcVWMnQzFCyF .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-UP3MZcVWMnQzFCyF .rough-node .label,#mermaid-svg-UP3MZcVWMnQzFCyF .node .label,#mermaid-svg-UP3MZcVWMnQzFCyF .image-shape .label,#mermaid-svg-UP3MZcVWMnQzFCyF .icon-shape .label{text-align:center;}#mermaid-svg-UP3MZcVWMnQzFCyF .node.clickable{cursor:pointer;}#mermaid-svg-UP3MZcVWMnQzFCyF .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-UP3MZcVWMnQzFCyF .arrowheadPath{fill:#333333;}#mermaid-svg-UP3MZcVWMnQzFCyF .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-UP3MZcVWMnQzFCyF .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-UP3MZcVWMnQzFCyF .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-UP3MZcVWMnQzFCyF .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-UP3MZcVWMnQzFCyF .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-UP3MZcVWMnQzFCyF .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-UP3MZcVWMnQzFCyF .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-UP3MZcVWMnQzFCyF .cluster text{fill:#333;}#mermaid-svg-UP3MZcVWMnQzFCyF .cluster span{color:#333;}#mermaid-svg-UP3MZcVWMnQzFCyF div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-UP3MZcVWMnQzFCyF .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-UP3MZcVWMnQzFCyF rect.text{fill:none;stroke-width:0;}#mermaid-svg-UP3MZcVWMnQzFCyF .icon-shape,#mermaid-svg-UP3MZcVWMnQzFCyF .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-UP3MZcVWMnQzFCyF .icon-shape p,#mermaid-svg-UP3MZcVWMnQzFCyF .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-UP3MZcVWMnQzFCyF .icon-shape .label rect,#mermaid-svg-UP3MZcVWMnQzFCyF .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-UP3MZcVWMnQzFCyF .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-UP3MZcVWMnQzFCyF .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-UP3MZcVWMnQzFCyF :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 90%
10%
客户端
Ingress Gateway
VirtualService

(Istio)
Revision v1

(旧模型)
Revision v2

(新模型)
Pod × 4
Pod × 1

Istio VirtualService 在 Gateway 层做流量拆分,客户端无感知。

5.2 蓝绿部署

蓝绿部署 = 100% 流量切换。KServe 里通过 traffic 字段一步切换:

yaml 复制代码
metadata:
  annotations:
    serving.knative.dev/traffic: |
      [
        {"revisionName": "llama-8b-v2", "percent": 100, "tag": "current"},
        {"revisionName": "llama-8b-v1", "percent": 0, "tag": "rollback"}
      ]
  • 切换前:v2 是 canary(10%),v1 是 current(90%)。
  • 切换后:v2 变 current(100%),v1 保留为 rollback(0%)。
  • 如果发现问题,把 percent 改回去即可秒级回滚。

蓝绿部署的关键是 v1 和 v2 同时存在(不立即销毁旧版本),直到确认 v2 稳定。

5.3 影子部署(Shadow / Mirror)

影子部署 = 把生产流量复制一份给新版本,但不影响用户响应。用于线上效果验证:

yaml 复制代码
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: llama-shadow
spec:
  http:
    - route:
        - destination:
            host: llama-8b-predictor-default.production.svc.cluster.local
            weight: 100
      mirror:
        host: llama-8b-v2-predictor-default.production.svc.cluster.local
      mirrorPercentage:
        value: 100.0     # 100% 流量镜像给 v2

效果:

复制代码
用户请求 → 主版本(v1) 处理 → 返回响应给用户
         ↘ 镜像流量 → v2 处理 → 响应丢弃(不影响用户)

影子部署的用途:

  • 验证新模型的输出质量(对比 v1 和 v2 的输出)。
  • 压测新版本的性能(承受真实流量模式)。
  • 检查新版本的稳定性(OOM / 延迟)。

注意:镜像请求的响应被 Istio 丢弃,客户端只收到主版本的响应。镜像请求的延迟和错误不会影响用户。

5.4 三种策略对比

策略 流量分配 回滚速度 资源开销 适用场景
Canary 渐进(10%→50%→100%) 快(调百分比) 两版本并行 常规迭代
蓝绿 0%→100% 一步切 极快(切回旧版) 两版本并行 大版本升级
影子 100% 主 + N% 镜像 无需回滚(镜像不影响) 镜像版本额外消耗 上线前验证

LLM 推理服务推荐组合:先影子验证 → 再 Canary 10% → 逐步 50% → 蓝绿 100%


六、InferenceGraph:多模型编排

6.1 为什么需要多模型编排

很多 AI 应用的推理不是单模型,而是多个模型协作:

复制代码
场景1: RAG(检索增强生成)
  检索模型 → 排序模型 → 生成模型 → 审查模型

场景2: 多模态
  图片描述模型 → 文本生成模型

场景3: 路由
  分类模型 → 路由到(A 专家模型 / B 专家模型)

场景4: 集成
  模型A + 模型B + 模型C → 投票/平均

如果在业务代码里硬编码这些编排,逻辑复杂且难维护。KServe 的 InferenceGraph CRD 把多模型编排声明式化。

6.2 InferenceGraph CRD

四种图类型:SEQUENCE(顺序)、SWITCH(路由)、ENSEMBLE(集成)、SPLIT(分流)。

顺序(SEQUENCE)------ RAG 场景

yaml 复制代码
apiVersion: serving.kserve.io/v1alpha1
kind: InferenceGraph
metadata:
  name: rag-pipeline
spec:
  nodes:
    root:
      routerType: SEQUENCE
      steps:
        - name: retriever          # 第一步: 检索
          serviceName: retriever.default.svc.cluster.local
        - name: reranker           # 第二步: 排序
          serviceName: reranker.default.svc.cluster.local
        - name: llm                # 第三步: 生成
          serviceName: llama-8b.default.svc.cluster.local

请求依次经过 retriever → reranker → llm,前一步的输出作为后一步的输入。

路由(SWITCH)------ 领域分发

yaml 复制代码
spec:
  nodes:
    root:
      routerType: SWITCH
      routerParams:
        type: "router"             # 用一个路由模型决定走哪个分支
      steps:
        - name: classifier
          serviceName: classifier.default.svc.cluster.local
        - name: math-expert
          serviceName: math-llm.default.svc.cluster.local
          data: "$response.prediction == 'math'"
        - name: code-expert
          serviceName: code-llm.default.svc.cluster.local
          data: "$response.prediction == 'code'"

分类模型先判断请求类别,然后路由到对应的专家模型。

集成(ENSEMBLE)------ 投票

yaml 复制代码
spec:
  nodes:
    root:
      routerType: ENSEMBLE
      steps:
        - name: model-a
          serviceName: model-a.default.svc.cluster.local
        - name: model-b
          serviceName: model-b.default.svc.cluster.local
        - name: model-c
          serviceName: model-c.default.svc.cluster.local

三个模型并行推理,结果聚合(默认是拼接,可自定义聚合逻辑)。

6.3 InferenceGraph 的执行模型

Model D Model B Model A InferenceGraph (Router Pod) 客户端 Model D Model B Model A InferenceGraph (Router Pod) 客户端 #mermaid-svg-2nvOrIndPGQdGpyt{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-2nvOrIndPGQdGpyt .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-2nvOrIndPGQdGpyt .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-2nvOrIndPGQdGpyt .error-icon{fill:#552222;}#mermaid-svg-2nvOrIndPGQdGpyt .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-2nvOrIndPGQdGpyt .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-2nvOrIndPGQdGpyt .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-2nvOrIndPGQdGpyt .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-2nvOrIndPGQdGpyt .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-2nvOrIndPGQdGpyt .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-2nvOrIndPGQdGpyt .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-2nvOrIndPGQdGpyt .marker{fill:#333333;stroke:#333333;}#mermaid-svg-2nvOrIndPGQdGpyt .marker.cross{stroke:#333333;}#mermaid-svg-2nvOrIndPGQdGpyt svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-2nvOrIndPGQdGpyt p{margin:0;}#mermaid-svg-2nvOrIndPGQdGpyt .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-2nvOrIndPGQdGpyt text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-2nvOrIndPGQdGpyt .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-2nvOrIndPGQdGpyt .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-2nvOrIndPGQdGpyt .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-2nvOrIndPGQdGpyt .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-2nvOrIndPGQdGpyt #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-2nvOrIndPGQdGpyt .sequenceNumber{fill:white;}#mermaid-svg-2nvOrIndPGQdGpyt #sequencenumber{fill:#333;}#mermaid-svg-2nvOrIndPGQdGpyt #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-2nvOrIndPGQdGpyt .messageText{fill:#333;stroke:none;}#mermaid-svg-2nvOrIndPGQdGpyt .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-2nvOrIndPGQdGpyt .labelText,#mermaid-svg-2nvOrIndPGQdGpyt .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-2nvOrIndPGQdGpyt .loopText,#mermaid-svg-2nvOrIndPGQdGpyt .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-2nvOrIndPGQdGpyt .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-2nvOrIndPGQdGpyt .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-2nvOrIndPGQdGpyt .noteText,#mermaid-svg-2nvOrIndPGQdGpyt .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-2nvOrIndPGQdGpyt .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-2nvOrIndPGQdGpyt .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-2nvOrIndPGQdGpyt .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-2nvOrIndPGQdGpyt .actorPopupMenu{position:absolute;}#mermaid-svg-2nvOrIndPGQdGpyt .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-2nvOrIndPGQdGpyt .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-2nvOrIndPGQdGpyt .actor-man circle,#mermaid-svg-2nvOrIndPGQdGpyt line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-2nvOrIndPGQdGpyt :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Router Pod 是无状态的 只做编排和转发 POST /v2/models/rag:predict 解析图定义 (SEQUENCE) 调用 retriever 返回候选文档 注入文档到下一步输入 调用 reranker (带文档) 返回排序结果 注入排序结果 调用 llm (带上下文) 返回生成文本 返回最终结果

InferenceGraph 本身是一个轻量 Router Pod(不加载模型),它负责:

  1. 解析图定义,按类型(SEQUENCE / SWITCH / ENSEMBLE)执行。
  2. 管理步骤间的数据传递(JSONPath 提取 / 注入)。
  3. 错误处理(某步失败时重试或降级)。
  4. 并行执行(ENSEMBLE 类型的步骤自动并行)。

Router Pod 是 CPU-only 的,成本极低。真正的推理负载在下游的 InferenceService 上,它们各自独立扩缩。


七、生产落地实践

7.1 安装 KServe

KServe 依赖 Knative Serving。推荐用 KServe 官方 Chart 一键安装(包含 Knative + Istio/Kourier):

bash 复制代码
# 安装 Knative Serving + KServe
kubectl apply -f https://github.com/knative/serving/releases/download/knative-v1.15.0/serving-crds.yaml
kubectl apply -f https://github.com/knative/serving/releases/download/knative-v1.15.0/serving-core.yaml

# 安装网络层 (Kourier, 比 Istio 轻量)
kubectl apply -f https://github.com/knative/net-kourier/releases/download/knative-v1.15.0/kourier.yaml

# 安装 KServe
helm repo add kserve https://kserve.github.io/charts/
helm install kserve kserve/kserve --namespace kserve --create-namespace

验证:

bash 复制代码
$ kubectl get crd | grep kserve
clusterservingruntimes.serving.kserve.io
inferenceservices.serving.kserve.io
inferencegraphs.serving.kserve.io
servingruntimes.serving.kserve.io

7.2 模型存储配置

KServe 支持多种存储后端,通过 Storage Spec 注入凭证:

yaml 复制代码
apiVersion: v1
kind: Secret
metadata:
  name: s3-creds
  annotations:
    serving.kserve.io/s3-endpoint: minio.internal:9000
    serving.kserve.io/s3-usehttps: "0"
    serving.kserve.io/s3-region: "us-east-1"
type: Opaque
stringData:
  AWS_ACCESS_KEY_ID: minioadmin
  AWS_SECRET_ACCESS_KEY: minioadmin
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: sa
secrets:
  - name: s3-creds

InferenceService 引用:

yaml 复制代码
spec:
  predictor:
    model:
      storageUri: s3://models/llama-8b
      serviceAccountName: sa          # 自动挂载 S3 凭证

KServe 的 storage-initializer 会启动一个 init container,从 S3 拉取模型到本地 /mnt/models,然后主容器(vLLM/TGI)从本地加载。

7.3 与 vLLM 深度集成

vLLM 是 LLM 推理的首选引擎。KServe + vLLM 的最佳实践:

yaml 复制代码
apiVersion: serving.kserve.io/v1beta1
kind: InferenceService
metadata:
  name: llama-70b
  annotations:
    serving.kserve.io/enable-prometheus-scraping: "true"
    huggingface.co/repository: "meta-llama/Llama-3-70B"
spec:
  predictor:
    model:
      modelFormat:
        name: vLLM
      runtime: kserve-vllm
      storageUri: pvc://llama-pvc
      protocolVersion: v2
      env:
        - name: VLLM_TENSOR_PARALLEL_SIZE
          value: "4"                    # 4 卡张量并行
        - name: VLLM_GPU_MEMORY_UTILIZATION
          value: "0.92"
        - name: VLLM_MAX_MODEL_LEN
          value: "8192"
        - name: VLLM_TRUST_REMOTE_CODE
          value: "true"
      resources:
        limits:
          nvidia.com/gpu: 4
          memory: 128Gi
        requests:
          nvidia.com/gpu: 4
    minReplicas: 1                      # LLM 不建议缩零
    maxReplicas: 4
    autoscaling.knative.dev/class: kpa.autoscaling.knative.dev
    autoscaling.knative.dev/metric: concurrency
    autoscaling.knative.dev/target: "32"     # vLLM 单副本并发上限
    timeoutSeconds: 300                 # 流式生成可能长
    nodeSelector:
      nvidia.com/gpu.product: A100-SXM4-80GB

7.4 指标采集与监控

KServe 自动暴露 Prometheus 指标:

指标 含义
kserve_inference_request_total 推理请求总数
kserve_inference_request_duration_seconds 推理延迟
kserve_inference_request_errors_total 推理错误数
kserve_data_plane_components_active 活跃组件数
kserve_model_load_time_seconds 模型加载时间

Grafana 关键面板:

promql 复制代码
# 按模型查 QPS
sum(rate(kserve_inference_request_total{model_name="llama-8b"}[1m])) by (model_name)

# P99 延迟
histogram_quantile(0.99, sum(rate(
  kserve_inference_request_duration_seconds_bucket{model_name="llama-8b"}[5m]
)) by (le))

# 错误率
sum(rate(kserve_inference_request_errors_total{model_name="llama-8b"}[1m])) /
sum(rate(kserve_inference_request_total{model_name="llama-8b"}[1m]))

# 活跃副本数(从 KPA 指标)
sum(kpa_pod) by (inferenceservice)

7.5 常见故障排查

现象 原因 排查
InferenceService 一直 Not Ready 模型拉取失败 / 镜像拉取失败 kubectl describe isvc,看 Pod events
请求超时 模型加载慢 / 冷启动 看 Pod 日志,调整 timeoutSeconds
HPA 不扩容 指标未暴露 / target 设置错 kubectl get hpa,看 metrics
Scale-to-zero 后请求丢失 Activator 配置问题 检查 Knative Activator 是否正常
Canary 流量不切 VirtualService 配置错 kubectl get virtualService
模型加载 OOM GPU 显存不够 VLLM_GPU_MEMORY_UTILIZATION
流式响应中断 Ingress 超时 调 Ingress 的 proxy-buffering / timeout

7.6 KServe vs 裸 vLLM Deployment

维度 KServe 裸 Deployment + vLLM
部署复杂度 一个 CRD Deployment+Service+HPA+Ingress
Scale-to-zero 支持 不支持
扩缩容指标 并发(推理专用) CPU(不适合推理)
灰度发布 声明式 traffic 手写多套资源
多模型编排 InferenceGraph 自己写
学习成本
灵活性 受 CRD 约束 完全自由
LLM 场景 需配合 vLLM runtime 直接用

实践建议:

  • 少量模型、简单需求:裸 vLLM Deployment 更简单直接。
  • 多模型、需灰度 / 编排:KServe 的声明式优势明显。
  • LLM 流式:KServe v2 协议支持,但需注意 Ingress 超时配置。

本篇小结

主题 核心结论
KServe 定位 K8s 上的推理服务标准 CRD,封装扩缩容/路由/灰度
InferenceService Predictor(推理)+Transformer(前后处理)+Explainer(解释) 三组件
Knative Serving 提供 Scale-to-zero 和流量拆分的底层能力
推理协议 v1(KServe 原生) / v2(OpenAI 兼容)
推理引擎 vLLM/TGI/Triton/ONNX 等,通过 ServingRuntime CRD 适配
Scale-to-zero 空闲缩零省 GPU,但 LLM 冷启动慢(30-200s)
自动扩缩容 KPA(并发,推荐) > HPA(CPU) > KEDA(事件驱动)
并发模型 LLM 扩缩看并发而非 QPS,target = 单副本并发上限
Canary traffic annotation 声明式流量拆分
蓝绿 100% 切换 + 保留旧版秒级回滚
影子部署 Istio mirror 流量复制,不影响用户
InferenceGraph SEQUENCE/SWITCH/ENSEMBLE 多模型编排
选型 少量简单模型用裸 Deployment,多模型/灰度/编排用 KServe

核心心智模型:KServe 把推理服务的「扩缩容 + 路由 + 发布策略」从手写 YAML 变成声明式 CRD。它的价值不在于提供更强的推理能力(那是 vLLM 的职责),而在于让推理服务的运维标准化和规模化。


下篇预告

第 34 篇:模型管理系统-注册中心与版本管理

到这里,模块五「GPU 集群与 K8s 编排」就全部讲完了------从 K8s GPU 调度、MIG 分区、Training Operator、Ray、弹性训练到 KServe 推理平台。接下来进入模块六「平台层服务」。下一篇我们讲 模型管理系统------为什么模型也需要一个注册中心,如何管理模型的元数据、版本、血缘,以及用 Argo Workflows 编排从训练到上架的完整模型流水线。


如果本篇内容对你有帮助,欢迎点赞收藏!有任何疑问,欢迎在评论区交流。

相关推荐
数据智研1 小时前
【数据分享】陕西区域统计年鉴(2012-2018)
大数据·人工智能·信息可视化·数据分析
ZJU_统一阿萨姆1 小时前
【推理优化进阶】通信关键路径:NCCL、RDMA 与计算通信重叠
开发语言·人工智能·语言模型·架构·系统架构
AI大法师1 小时前
品牌焕新不只换 Logo:捷豹案例里的触点重构方法
大数据·人工智能·设计模式·新媒体运营
名字还没想好☜1 小时前
Kubernetes StatefulSet 实战:为什么数据库不能用 Deployment 部署
数据库·云原生·容器·kubernetes·statefulset
欧阳天羲1 小时前
全屋智能家居一体化方案‑配套资料文档合集
云原生·eureka·智能家居
tuanxiang2 小时前
在线AI生成率检测:平台投稿内容合规排查踩坑实录
人工智能
machnerrn2 小时前
智慧交通系列(一)-十字路口车辆闯红灯检测告警抓拍系统(附含数据+源码+模型)
人工智能·python·深度学习
AKAMAI2 小时前
超大规模的新定义
人工智能·云计算
未来和明天2 小时前
领嵌4G/5GAI边缘计算盒子多路视频算力高达10.4Tops兼容Modbus、DLT645、OPC UA等多种行业协议
人工智能·5g·边缘计算