- 模型推理服务
- 云原生
- 后端
- 微服务
- MLOps
- 人工智能
【免费下载链接】kserve
Standardized Distributed Generative and Predictive AI Inference Platform for Scalable, Multi-Framework Deployment on Kubernetes
导读
本文基于 KServe 仓库中的 OCI Image Volume 示例,系统讲解如何用oci+native://这一显式 URI scheme,让 Kubernetes 以原生 ImageVolume(KEP-4639,即spec.volumes[].image)方式直接挂载模型容器镜像,替代传统 modelcar 边车(sidecar)方案。读完本文,你将掌握:oci+native://与oci://(modelcar)、oci+fetch://三类模式的区别与选型依据;如何配置集群级默认ociModelMode或按服务显式覆盖;如何验证准入 webhook 注入的 ImageVolume 与subPath: "models"挂载;以及 native 模式在不同规模模型下的冷/热启动实测数据与版本兼容性(含OciImageVolumeCompatible咨询条件)的完整知识。
背景:OCI 模型交付的三种路径
KServe 在 OCI(Open Container Initiative)模型交付上规划了三条并存路径,对应 KServe issue #4083 中定义了三种模式常量:
| 模式 | URI scheme | 工作方式 | 适用场景 |
|---|---|---|---|
native | oci+native://,或oci://(当集群默认配置为 native) | Kubernetes ImageVolume 直接挂载,无 sidecar | K8s ≥ 1.33,模型镜像已是标准 OCI 格式 |
modelcar | oci://(默认) | 边车容器与主容器共享/mnt/models | 任意 K8s 版本、已有 modelcar 镜像 |
fetch(规划中) | oci+fetch:// | storage initializer 拉取镜像层 | 离线(air-gapped)集群、遗留运行时 |
oci+native://方案最大的收益是省掉了 modelcar 边车及其生命周期开销,直接复用容器运行时的镜像拉取与缓存能力。
前置条件
| 要求 | 最低版本 |
|---|---|
| Kubernetes | 1.35+(ImageVolume beta 默认开启;完整支持,含 subPath) |
| Kubernetes(手动开启 feature gate,完整支持) | 1.33–1.34 + 在 apiserver 与 kubelet 上设置--feature-gates=ImageVolume=true |
| Kubernetes(手动开启 feature gate,无 subPath) | 1.31–1.32 +--feature-gates=ImageVolume=true——ImageVolume 挂载上的 subPath 被禁止;KServe 会给出OciImageVolumeCompatible咨询条件 |
| 容器运行时 | containerd ≥ 2.0 或 CRI-O ≥ 1.31 |
| KServe | 本分支(当前仓库)或更新版本 |
K8s 1.31–1.32 注意:这两个 alpha 版本不支持 ImageVolume VolumeMount 上的
subPath。KServe 在检测到该组合时,会在 InferenceService 上设置咨询性条件OciImageVolumeCompatible=False。要获得完整的oci+native://支持,请升级到 K8s 1.33+。
兼容性检测的源码实现
这条咨询条件的判定逻辑位于 pkg/controller/v1beta1/inferenceservice/controller.go:当解析出的存储模式为native时,控制器调用共享辅助函数utils.CheckImageVolumeCompatibility检查集群版本,并按结果分别设置OciImageVolumeUnsupported、OciImageVolumeSubPathUnsupported、OciImageVolumeAlpha三种OciImageVolumeCompatible=False条件;当模式不是native(或版本 ≥ 1.35 / 未知)时清除该条件。重要细节:该条件从不影响 Ready 状态(不在 conditionSet 中),属于纯咨询性提示,且测试覆盖了“从 native 切回其他模式时陈旧条件会被清除”的行为(见 controller_oci_warn_test.go)。
OCI 镜像布局约定
KServe 以subPath: "models"挂载 ImageVolume,也就是说容器运行时会把你镜像内部的/models/目录暴露到配置的modelPath(默认/mnt/models)。这与 modelcar 的 OCI 镜像布局约定一致——model 文件存放在镜像内的/models/目录下。
因此,你不需要为了 native 模式重新构建镜像:只要现有 modelcar 镜像内部有/models/目录,就能直接以oci+native://引用,保持零成本迁移。默认挂载路径常量DefaultModelLocalMountPath = "/mnt/models"定义在 pkg/constants/constants.go。
如何部署:替换 storageUri 并应用 manifest
示例清单位于 docs/samples/storage/oci-image-volume/inference_service.yaml,完整内容如下:
apiVersion: serving.kserve.io/v1beta1 kind: InferenceService metadata: name: sklearn-oci-native namespace: kserve-test spec: predictor: model: modelFormat: name: sklearn # oci+native:// forces Kubernetes-native ImageVolume mounting regardless # of the cluster-wide ociModelMode setting in inferenceservice-config. # Replace with your OCI model image reference. storageUri: "oci+native://ghcr.io/my-org/my-sklearn-model:v1" resources: requests: cpu: "100m" memory: "256Mi" limits: cpu: "500m" memory: "512Mi"操作步骤:
- 把
inference_service.yaml中的storageUri替换为你的 OCI 模型镜像引用。镜像内必须包含/models/目录下的模型文件(通过subPath: "models"暴露在/mnt/models)。 - 应用清单:
kubectl apply -f inference_service.yamlURI scheme 的解析逻辑
oci+native://之所以能“绕过全局默认配置强制 native”,是因为 webhook 在注入前会先解析 URI scheme。核心函数ParseOciScheme位于 pkg/utils/storage.go,解析规则如下:
"oci+native://reg/img:tag"→ 模式"native",归一化为oci://reg/img:tag"oci+modelcar://reg/img:tag"→ 模式"modelcar""oci+fetch://reg/img:tag"→ 模式"fetch""oci://reg/img:tag"→ 模式为空,稍后由配置解析"s3://bucket/key"→ 非 OCI URI,原样返回
在 storage_initializer_injector.go 中,注入器先取显式后缀模式;若为空(裸oci://),则回退到types.ResolveOciModelMode(config)决定有效模式,再分发到对应的 materializer:
native→utils.ConfigureOciNativeToContainerfetch→ConfigureOciFetchToContainer- 默认 →
utils.ConfigureModelcarToContainer
如何验证
检查 InferenceService 是否已创建:
kubectl get inferenceservice sklearn-oci-native -n kserve-test查看已被准入 webhook 物化的 Pod spec,确认注入了image类型 volume 以及kserve-container上的只读挂载:
kubectl describe pod -n kserve-test -l serving.kserve.io/inferenceservice=sklearn-oci-native你应该能看到类似如下的 volume 段:
Volumes: mnt-models: Type: Image (an OCI container image) Reference: ghcr.io/my-org/my-sklearn-model:v1 ...以及带subPath: "models"的容器挂载:
Mounts: /mnt/models from mnt-models (ro, subPath=models)注入器到底做了什么
从源码看,ConfigureOciNativeToContainer(pkg/utils/storage.go)完成了四件事:
- 从
oci://前缀后截取镜像引用(strings.TrimPrefix(modelUri, "oci://")); - 根据 modelPath 派生唯一 volume 名(
GetVolumeNameFromPath,空时回退为oci-model),保证同一 Pod 内多个不同挂载路径的 adapter 互不冲突; - 向 PodSpec 追加
corev1.Volume,类型为corev1.ImageVolumeSource,其中Reference为镜像引用、PullPolicy: PullIfNotPresent(镜像已在节点时不再重复拉取); - 为目标容器追加只读 VolumeMount:
AddVolumeMountIfNotPresentWithSubPath(targetContainer, volName, modelPath, "models", true)。
同时它做了两处防呆校验:若 modelPath 已被其他 volume 占用则拒绝注入(避免静默的挂载遮蔽);若 Pod 级同名 Volume 已存在则不再重复追加(API server 会拒绝重复条目)。由于注入逻辑是幂等的(webhook 可能以reinvocationPolicy: IfNeeded多次调用),重复调用不会产生重复的 volume 或 mount。
全局默认配置 vs 显式 scheme
你可以在inferenceservice-configConfigMap 中配置ociModelMode: "native",让集群内所有oci://URI 默认走 native 挂载;而oci+native://是显式覆盖,绕过全局设置——适合“单个服务用 native、集群默认仍保持 modelcar”的场景。
示例清单文件顶部有一段被注释掉的 ConfigMap 片段(inference_service.yaml):
apiVersion: v1 kind: ConfigMap metadata: name: inferenceservice-config namespace: kserve data: storageInitializer: |- { "enableOciModelSupport": true, "ociModelMode": "native" }对应StorageInitializerConfig结构体(pkg/types/config.go)中的字段:
enableOciModelSupport:总开关,开启任意 OCI 模型存储路径;兼容旧的enableModelcar字段;ociModelMode:"modelcar"(默认)、"native"、"fetch"三选一,空值解析为"modelcar";OciInsecureRegistry:仅供oci+fetch://路径关闭 TLS 校验(默认 false,必须显式开启)。
模式解析优先级(ResolveOciModelMode,pkg/types/config.go):显式OciModelMode> 旧的EnableOciImageSource/EnableOciModelSupport(向后兼容)> 空(禁用/默认 modelcar)。
native 模式下多模型挂载的冲突规则
如果你在单个 InferenceService 中挂载多个 OCI 源,native 与 modelcar 的冲突规则不同(ValidateOCIMountPaths,pkg/utils/storage.go):
- native 模式:只禁止完全相同的挂载路径重复出现;同一父目录下的兄弟路径完全合法(因为每个 ImageVolume 独立挂载到自己的精确路径);
- modelcar 模式:每个 sidecar 把 emptyDir 挂在 modelPath 的父目录上,因此共享父目录的两个 URI 会在目标容器上造成挂载遮蔽,必须使用父目录不同的挂载路径。
实测启动行为
仓库文档给出了一份单节点基准数据(kind,Kubernetes v1.36.1,containerd 2.3.1,KServe master 构建;单云虚拟机 AWS m6i.2xlarge:8 vCPU / 32 GB RAM,1 TB gp3 EBS 默认预置 3,000 IOPS / 125 MiB/s,控制平面、registry、MinIO 与 predictor Pod 同机部署,140 GB 档受默认磁盘吞吐限制)。测试产物为 2 / 14 / 140 GB(对应 fp16 权重下的 1B / 7B / 70B 级模型)。数据为从InferenceServiceapply 到首次成功预测的中位秒数:
| 路径 | 2 GB 冷 | 2 GB 热 | 14 GB 冷 | 14 GB 热 | 140 GB 冷 | 140 GB 热 |
|---|---|---|---|---|---|---|
oci+native:// | 38.6 | 11.5 | 443.4 | 11.4 | 4,585.5 | 11.7 |
modelcar(oci://) | 59.6 | 15.4 | 363.3 | 15.2 | 4,603.0 | 15.8 |
s3:// | 39.6 | 30.0 | 137.8 | 128.4 | 2,318.8 | 2,441.6 |
“热”= 模型镜像已存在于节点 containerd 存储中;对s3://而言节点层没有缓存,因此每次 Pod 启动都会重新下载产物。
关键结论:
- OCI 路径的热启动与模型大小无关:镜像缓存在节点后,无论 2 GB 还是 140 GB,
oci+native://都在约 12 秒内完成首次预测;在热节点上为 140 GB 模型加一个副本只需约 11.7 秒,而s3://要重新下载约 41 分钟。 - 首次冷拉取的成本高于纯下载(140 GB 时约 2×):containerd 先写 layer blob 再解包进 snapshot,需要两遍磁盘;而 storage initializer 只流式写一遍。如果节点多次服务同一模型,缓存拉取很快就能摊薄这笔开销。
- modelcar 与 native 表现相同的拉取行为,外加每 Pod 约 4 秒的 sidecar 生命周期开销(这与文档基准中 15.4s vs 11.5s 的差异吻合,也解释了 modelcar 热启动普遍比 native 慢 3–4 秒的原因)。
绝对数值随磁盘/网络吞吐伸缩,路径间的相对行为才是稳定结论。完整方法学、脚本与原始数据见文档末尾引用的外部基准仓库(oci-model-delivery-bench),该 harness 可在任意集群上运行以获取你环境的数据。
何时选择 native 而非替代方案
- 选
native:集群 K8s ≥ 1.33、模型镜像已是 OCI 格式时,直接用oci+native://(或集群默认配置ociModelMode: "native")。它避开了 modelcar 边车开销,直接复用容器运行时的镜像拉取与缓存。 - 选
modelcar:任何 K8s 版本都能用,且已存在 modelcar 镜像时保持默认oci://即可;代价是每 Pod 多出约 4 秒的 sidecar 生命周期开销。 fetch(规划中):面向离线集群与遗留运行时,由 storage initializer 拉取镜像层,属于路线图中的未来能力(oci+fetch://前缀已由ParseOciScheme预留解析)。
参考链接
- OCI Image Volume 示例 README
- 示例 InferenceService 清单
- URI scheme 解析与 native 注入实现
- 存储初始化器注入 webhook
- StorageInitializerConfig 与模式常量
- OciImageVolumeCompatible 咨询条件实现
- KServe issue #4083:OCI 存储统一路线图
- KEP-4639:OCI Volume Source
- 模型推理服务
- 云原生
- 后端
- 微服务
- MLOps
- 人工智能
【免费下载链接】kserve
Standardized Distributed Generative and Predictive AI Inference Platform for Scalable, Multi-Framework Deployment on Kubernetes
相关推荐
Kubernetes实战:在Pod中使用镜像卷(Image Volume)挂载OCI镜像内容
Kubernetes实战:在Pod中使用镜像卷 Image Volume 挂载OCI镜像内容 概述 在Kubernetes中,Image Volume(镜像卷)
文档教程云原生OCI镜像转换指南:将现有镜像迁移到OCI标准格式
OCI镜像转换指南:将现有镜像迁移到OCI标准格式 OCI镜像格式作为容器技术的行业标准,为容器镜像提供了统一、可互操作的规范。本指南将详细介绍如何将现有镜像迁
云原生存储MediaPipe 迁移指南:从 Legacy Solutions 到 Tasks API,这 4 处改动就够
MediaPipe 迁移指南:从 Legacy Solutions 到 Tasks API,这 4 处改动就够 一个再熟悉不过的开头 你项目里是不是还留着 im
人工智能机器学习计算机视觉多模态本地部署
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考