KServe 源码拆解系列 · 第 1 篇
概述文讲的是 KServe"能干什么":一个 InferenceService YAML apply 下去,Deployment、Service、流量、扩缩容全自动搞定。但 apply 之后那几秒:谁读到了你的 YAML?谁决定建几个 Pod?谁把 vLLM 参数填进容器启动命令?------本系列拆"内部怎么干",共 10 篇:
- 源码导读(本篇):仓库地图 + 代码入口
- InferenceService API:一个 CRD 承载全部配置
- 调和循环:从事件到 Deployment
- 组件构建:组件如何变成 Pod
- 模型加载与节点级缓存:模型从对象存储到 GPU
- 流量路由:请求送到正确的 Pod
- 自动扩缩容:HPA/KEDA/Knative
- 多模型管理:TrainedModel 与 ModelMesh
- InferenceGraph 推理流水线:多模型编排
- LLMService 新 API:新一代接口
控制面/数据面:2 分钟回顾
arduino
kubectl apply ┌──────────────────────────────┐
───────────────▶ │ 控制面 Control Plane │
│ InferenceService Controller │
│ "期望状态" → 创建/改/删资源 │
└──────────────┬───────────────┘
│ 下发 Deployment / Service / VirtualService
▼
┌──────────────────────────────┐
│ 数据面 Data Plane │
│ Predictor/Transformer/ │
│ Explainer 真实推理 Pod │
└──────────────────────────────┘
对应到代码:控制面 = cmd/manager + pkg/controller + pkg/webhook(全在 KServe 仓库,本系列主战场);数据面 = cmd/agent(Pod 里的代理)+ 推理服务端,服务端主体在独立仓库 kserve/python,本系列不深入。
仓库地图:pkg/ 逐个看
仓库根目录(省略次要目录):
bash
kserve/
├── cmd/ # 可执行程序入口:manager / agent / router / llmisvc...
├── pkg/ # 核心库代码------本系列的主战场
├── config/ # Kustomize 部署清单:manager 部署、RBAC、Webhook、CRD
├── charts/ # Helm Chart:CRD 包、控制面包、运行时配置包
├── python/ # 推理服务端配套代码(主体在独立仓库 kserve/python)
├── hack/ # 代码生成脚本
└── test/ # e2e 测试
pkg/ 逐个看:
| 目录 | 职责 | 系列哪篇深入 |
|---|---|---|
pkg/apis |
CRD 的 Go 类型、默认值、校验、版本转换 | 第 2 篇 |
pkg/controller |
所有调和循环(InferenceService、TrainedModel、InferenceGraph、LLMService...) | 第 3、4 篇 |
pkg/agent |
数据面代理:模型下载、请求日志、批处理 | 第 4、5 篇 |
pkg/webhook |
Admission Webhook:改 Pod、校验 Runtime/模型类 CRD | 第 2 篇穿插 |
pkg/modelconfig |
ModelMesh 模型配置类型与 ConfigMap 更新 | 第 8 篇 |
pkg/credentials |
S3/GCS/Azure 存储凭据解析 | 第 5 篇穿插 |
pkg/logger |
请求/响应日志落盘(JSON/CSV/Parquet) | 第 4 篇穿插 |
pkg/batcher |
请求动态批处理:攒一批再转发 | 第 4 篇穿插 |
pkg/tls |
TLS 配置解析 | 不单独成篇 |
pkg/constants |
全局常量:ConfigMap 名、注解 key... | 各篇都会碰到 |
pkg/scheme |
把全部 CRD 类型注册进 controller-runtime | 本篇入口 |
pkg/utils |
工具函数:IsCrdAvailable 探测、env/volume 合并 | 各篇都会碰到 |
pkg/client |
生成的 clientset/informers/listers | 不单独成篇 |
pkg/validation |
EnvPolicy 屏蔽变量、Managed DRA 等校验 | 不单独成篇 |
pkg/types |
跨包共享配置结构体(StorageInitializerConfig) | 不单独成篇 |
pkg/openapi |
生成的 OpenAPI/Swagger 定义(给 Python SDK) | 不单独成篇 |
pkg/testing |
测试工具:matchers、cleaner | 不单独成篇 |
pkg/controller 按 API 版本再拆一层:
| 子目录 | 内容 | 系列哪篇 |
|---|---|---|
v1beta1 |
inferenceservice/ 主控制器(组件构建、ingress、autoscaler 全在它下面) |
第 3、4、6、7 篇 |
v1alpha1 |
inferencegraph、trainedmodel、localmodel、localmodelnode |
第 8、9 篇 |
v1alpha2 |
llmisvc(新一代 LLMService) |
第 10 篇 |
三个版本并列是分层不是替换:v1alpha1 住着 ServingRuntime、InferenceGraph 等"配角"CRD,v1beta1 是 InferenceService 主场,v1alpha2 是孵化中的 LLMService。
cmd/ 下每个目录一个二进制:manager 控制面大脑(一个进程跑三套控制器 + Webhook);agent 数据面代理,跑在每个推理 Pod 里干下载、日志、批处理;llmisvc LLMService 的独立控制面;router InferenceGraph 路由器;localmodel、localmodelnode 节点级模型缓存控制面(第 5 篇)。
config/ 是 Kustomize 清单(manager 部署、RBAC、CRD,config/default 组装成可直接 apply 的完整部署),charts/ 是 Helm 版同一套,二选一即可。
代码入口:cmd/manager/main.go
控制面的一切从 cmd/manager/main.go(约 300 行)开始。启动参数(Options 结构体):metricsAddr 默认 ":8080"、webhookPort 默认 9443、probeAddr 默认 ":8081"。
main() 主线:
markdown
1. config.GetConfig() + kubernetes.NewForConfig(cfg) → 拿 kubeconfig,建客户端
2. manager.New(cfg, manager.Options{...}) → controller-runtime 的 Manager 登场
3. kservescheme.AddAll(mgr.GetScheme()) → 注册全部 CRD 类型
4. 读 inferenceservice-config ConfigMap → Deploy/Ingress 配置 ← 集群级行为全靠它调
5. 注册三套控制器 + 一组 Webhook
6. mgr.Start(signals.SetupSignalHandler()) → 进入事件循环
第 5 步,主控制器挂载(真实代码):
go
if err = (&v1beta1controller.InferenceServiceReconciler{
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
Recorder: eventBroadcaster.NewRecorder(...),
}).SetupWithManager(mgr, deployConfig, ingressConfig); err != nil { ... }
同一段代码还挂了 TrainedModelReconciler、InferenceGraphReconciler。Webhook 侧注册了 /mutate-pods(处理器 pod.Mutator,每个推理 Pod 创建时被它"改一刀"注入 agent、storage-initializer、metrics-aggregator 等容器,第 4 篇),另有 ServingRuntime / ClusterServingRuntime / TrainedModel / InferenceGraph 四个 Validator,InferenceService 则是 Defaulter(漏填的字段它来补)+ Validator(填错的字段它来拦)一对。
注意:main.go 没直接用 ctrl.NewControllerManagedBy 建控制器,而是让每个 Reconciler 自己实现 SetupWithManager------KServe 的依赖是可选的。看 controller.go(真实代码):
go
ksvcFound, err := utils.IsCrdAvailable(r.ClientConfig,
knservingv1.SchemeGroupVersion.String(), constants.KnativeServiceKind)
if err != nil {
return err
} // ← 装没装 Knative?
...
ctrlBuilder := ctrl.NewControllerManagedBy(mgr).
For(&v1beta1.InferenceService{}). // ← 主对象
Owns(&appsv1.Deployment{}). // ← 我创建的子资源,它们变了也要调和
Owns(&corev1.Service{})
if ksvcFound {
ctrlBuilder = ctrlBuilder.Owns(&knservingv1.Service{}) // ← 装了 Knative 才 watch
}
同样的探测还出现在 KEDA ScaledObject、Istio VirtualService、Gateway API HTTPRoute 上------"运行时可插拔"的源码根基就在这。Reconcile 主循环骨架(真实代码,controller.go):
go
componentReconcilers := []components.Component{}
if deploymentMode != constants.ModelMeshDeployment {
componentReconcilers = append(componentReconcilers, components.NewPredictor(...))
} // ← ModelMesh 模式不建 Predictor
if isvc.Spec.Transformer != nil {
componentReconcilers = append(componentReconcilers, components.NewTransformer(...))
}
// Explainer 同理,写了才加
for _, reconciler := range componentReconcilers {
reconciler.Reconcile(ctx, isvc) // ← 逐个组件调和
}
YAML 里有什么组件,这里就实例化什么 reconciler------CRD 的 spec 和控制器代码在这接上头,完整流程第 3 篇逐行走。
源码阅读路线图
10 篇与目录的对应表(主线第 2、3、4 篇,其余是支线可跳读):
| 篇 | 对应目录 |
|---|---|
| 1(本篇) | cmd/manager、pkg/ 全景 |
| 2 | pkg/apis/serving/v1beta1 |
| 3 | pkg/controller/v1beta1/inferenceservice/controller.go |
| 4 | .../inferenceservice/components/、reconcilers/factory.go、pkg/agent、pkg/batcher、pkg/logger |
| 5 | pkg/agent、pkg/credentials、pkg/controller/v1alpha1/localmodel |
| 6 | .../inferenceservice/reconcilers/ingress/ |
| 7 | .../reconcilers/autoscaler/、knative/、keda/、hpa/ |
| 8 | pkg/controller/v1alpha1/trainedmodel、pkg/modelconfig |
| 9 | pkg/controller/v1alpha1/inferencegraph、cmd/router |
| 10 | pkg/controller/v1alpha2/llmisvc、pkg/apis/serving/v1alpha2/llm_inference_service_types.go |
一句话总结
KServe 仓库 = 控制面(cmd/manager + pkg/controller + pkg/webhook)+ 数据面(cmd/agent + kserve/python)。控制面一个进程跑三套控制器,启动时探测 CRD 动态装配 Knative/Istio/KEDA;agent 在数据面干下载、日志、批处理。读懂 KServe:从 cmd/manager/main.go 进,沿 pkg/apis → pkg/controller 下潜。
下一篇预告:第 2 篇 InferenceService API:一个 CRD 如何承载全部配置。predictor、transformer、explainer、canary、logger、batcher......这么多能力塞进一个 YAML,Go 结构体怎么组织的?默认值和校验谁在管?