【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 上的推理服务标准」。它的核心价值:
- InferenceService CRD:声明式定义推理服务,封装推理引擎、扩缩容、路由。
- 推理引擎无关:内置支持 vLLM、TGI、Triton、ONNX Runtime、PyTorch Serve 等。
- Scale-to-zero:基于 Knative,空闲自动缩零,请求来了自动拉起。
- 流量管理:原生 Canary、蓝绿、影子部署,通过 Istio/Kourier 实现。
- 多组件架构:Predictor(推理)、Transformer(前后处理)、Explainer(可解释性)可独立扩缩。
- 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
InferenceServiceCR,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 的核心能力:
- Scale-to-zero:Activator 组件拦截请求,无流量时 Pod 数缩为 0;请求来了,Activator 先 hold 住请求,同时触发 Pod 拉起。
- 流量拆分:Revision 机制,新版本和旧版本可按百分比拆分流量。
- 冷启动优化:支持 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(不加载模型),它负责:
- 解析图定义,按类型(SEQUENCE / SWITCH / ENSEMBLE)执行。
- 管理步骤间的数据传递(JSONPath 提取 / 注入)。
- 错误处理(某步失败时重试或降级)。
- 并行执行(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 的职责),而在于让推理服务的运维标准化和规模化。
下篇预告
到这里,模块五「GPU 集群与 K8s 编排」就全部讲完了------从 K8s GPU 调度、MIG 分区、Training Operator、Ray、弹性训练到 KServe 推理平台。接下来进入模块六「平台层服务」。下一篇我们讲 模型管理系统------为什么模型也需要一个注册中心,如何管理模型的元数据、版本、血缘,以及用 Argo Workflows 编排从训练到上架的完整模型流水线。
如果本篇内容对你有帮助,欢迎点赞收藏!有任何疑问,欢迎在评论区交流。