Kubernetes 1.36 ImageVolume GA:当 OCI 镜像从容器运行时滑向数据分发层——从 KEP-4639 到生产落地的完全指南(2026)
2026 年 6 月,Kubernetes v1.36 把 ImageVolume 特性推到了 GA。这个看起来只是"多了一种 Volume 类型"的更新,实际上正在悄悄改写我们对 OCI 镜像的认知:它不再只是容器的载体,而是正在成为云原生场景下通用、可寻址、可校验、可分层去重的只读数据分发层。模型权重、配置包、安全签名、CI/CD 工件,只要能打包成 OCI Artifact,就能原生挂载到 Pod 里。
这篇文章会从 OCI 规范的演进讲起,拆解 ImageVolume 的架构原理,再用完整的代码实战带你走通"构建 OCI 数据镜像 → 挂载到 Pod → 配合 AI 推理服务 → 生产级优化"的完整链路。
一、背景:OCI 镜像为什么能用来分发数据?
1.1 从容器镜像到通用内容仓库
OCI(Open Container Initiative)是 2015 年在 Linux 基金会支持下成立的开放项目。Docker、CoreOS 和容器行业的主要厂商一起,围绕容器格式和运行时制定了开放标准。Docker 捐出了自己的镜像格式作为基础,社区在此基础上逐步形成了 Runtime、Image 和 Distribution 三大规范。
2017 年,image-spec v1.0 发布,容器镜像格式算是正式定型。从那以后,运行一个容器的标准流程变成:
Registry → 下载 OCI 镜像 → 解压成 OCI Bundle → OCI Runtime 运行 Bundle
这套流程标准化之后,不同 Runtime、不同 Registry 之间可以互操作,Kubernetes 也摆脱了必须绑定 Docker 的尴尬。
但 OCI 的野心不止于容器。
OCI 镜像的本质是什么?就是一堆只读的层(layer),加上一个 manifest 描述这些层的组织方式,再通过 Registry 的 API 完成分发。这套机制提供了一整套"可寻址、可校验、可去重、可控访问"的分发原语,而且并不绑定"容器"这个概念。
所以社区很早就开始在 OCI Registry 里存非镜像内容:
- Helm 3.0 开始支持把 Chart 推到 OCI Registry;
- Cosign 把容器签名、SBOM 也存进 OCI Registry,用镜像层来承载签名数据;
- ORAS(OCI Registry As Storage) 更直接,WASM 模块、OPA 策略、Falco 规则都能往里塞,相当于把 OCI Registry 当成通用对象存储来用。
这些实践推动了 OCI 规范本身的演进。2024 年,image-spec v1.1.0 正式加入了 artifactType 字段,允许 Manifest 声明:"我不是容器镜像,我是一个签名 / 一个 Helm Chart / 一个模型权重包"。OCI 对非镜像内容的支持从社区 hack 变成了规范的一部分,OCI Registry 正式成为了一个通用的内容仓库。
1.2 Kubernetes 的缺失拼图
虽然 OCI Registry 已经变成了通用内容仓库,但在 Kubernetes 这边,OCI 镜像仍然只能做一件事:跑容器。Helm、Cosign、ORAS 都在往里存东西,但 Kubernetes 缺少一个原生的消费方式。
过去想在 Pod 里使用 OCI Artifact 中的数据,通常要绕很多路:
- Init Container 拉取 + 共享 emptyDir:启动一个专门负责下载的容器,把数据放到 emptyDir 里,业务容器再挂载使用。问题是多一次容器调度、多一份网络流量、多一份临时存储开销。
- 自己实现 CSI Driver:比如用 ORAS 写一个自定义存储驱动,把 OCI 镜像当 Volume 挂载。问题是运维复杂度爆炸,需要维护镜像拉取、缓存、校验、更新等全链路。
- 把数据直接打进业务镜像:把模型权重、配置文件和业务代码打在一个镜像里。问题是镜像膨胀、版本耦合、每次数据更新都要重新打业务镜像。
ImageVolume 就是来补上这块拼图的。它允许在 Pod 中将 OCI 镜像直接作为 Volume 挂载,让 OCI Artifacts 在 Kubernetes 里也能被原生消费,不再只是跑容器。
1.3 为什么这件事在 2026 年特别重要?
2026 年,AI 推理服务的部署方式正在发生变化。大模型权重动辄几个 GB 甚至几十 GB,传统的配置管理方式(ConfigMap、Secret、PVC)都显得力不从心:
- ConfigMap/Secret:有大小限制,而且不适合二进制大文件;
- PVC:需要动态 provision 或者提前准备存储,跨集群分发模型权重困难;
- Init Container 下载:每次 Pod 重建都要重新下载,没有分层去重。
OCI 镜像天然适合解决这个问题:模型权重可以分层存储、版本化、通过 Registry 分发、多 Pod 共享相同的层(containerd 只存一份)。ImageVolume GA 之后,Kubernetes 对 AI 工作负载的原生支持又向前迈了一大步。
二、核心概念:ImageVolume 是什么?
2.1 最简单的用法
ImageVolume 的用法和普通 Volume 几乎一样,只是 volumes 里的类型从 emptyDir 或 configMap 换成了 image:
apiVersion: v1
kind: Pod
metadata:
name: image-volume-demo
spec:
containers:
- name: app
image: busybox:1.36
command: ["sleep", "3600"]
volumeMounts:
- name: model-volume
mountPath: /models
readOnly: true
volumes:
- name: model-volume
image:
reference: registry.example.com/models/qwen2-0.5b:v1
pullPolicy: IfNotPresent
关键点:
volumes.image.reference:OCI 镜像的引用,可以是 tag 或 digest;volumeMounts.readOnly:必须设置为true,ImageVolume 目前只支持只读挂载;pullPolicy:支持Always、IfNotPresent、Never,语义和容器镜像一致。
2.2 OCI 镜像结构回顾
要理解 ImageVolume,需要先理解 OCI 镜像的结构。一个 OCI 镜像主要包含:
manifest.json
├── config.json (容器镜像的配置,ImageVolume 场景下通常是一个很小的占位配置)
└── layers/
├── layer-1.tar.gz (文件系统层)
├── layer-2.tar.gz
└── ...
对于容器来说,OCI Runtime 会把这些层按顺序叠加成一个 rootfs,然后在这个 rootfs 里启动进程。
对于 ImageVolume 来说,kubelet 会通过 CRI 让容器运行时(containerd / CRI-O)把这些层挂载到 Pod 的指定路径。本质上,ImageVolume 就是把 OCI 镜像的文件系统层作为一个只读目录暴露给容器。
2.3 与 OCI Artifacts 的关系
严格来说,ImageVolume 挂载的是 OCI 镜像。但得益于 OCI Artifacts 的演进,很多非容器内容现在也以 OCI 镜像的形式存在(只是 manifest 里标注了 artifactType)。
所以你可以把 ImageVolume 理解为:
把任意 OCI 兼容的内容包(包括传统容器镜像和 OCI Artifacts)作为只读文件系统挂载到 Pod 中。
这包括:
- 模型权重包(ML model weights)
- 配置数据包(configuration bundles)
- 安全签名与 SBOM
- 静态资源包(字体、图标、文档)
- CI/CD 工件(二进制、证书、脚本)
三、架构分析:ImageVolume 是怎么工作的?
3.1 从 KEP-4639 说起
ImageVolume 这个特性来源于 KEP-4639:Image Volume Source,由 Kubernetes SIG Node 和 SIG Storage 共同推动。从 2024 年 v1.31 的 Alpha 阶段到 2026 年 v1.36 的 GA,走了将近两年。
| 阶段 | K8s 版本 | Feature Gate 默认值 | 关键变化 |
|---|---|---|---|
| Alpha | v1.31 | false | 特性引入,需要手动开启 |
| Beta(默认关) | v1.33 | false | 支持 subPath / subPathExpr |
| Beta(默认关) | v1.34 | false | 移除 noexec 限制 |
| Beta(默认开) | v1.35 | true | 首次默认启用 |
| GA | v1.36 | true(锁定) | Feature Gate 锁定,E2E 提升为 Conformance |
GA 之后,不再需要手动开启 Feature Gate,API 字段上的 +featureGate=ImageVolume 注解也被移除。按照 Kubernetes 的惯例,这个 Feature Gate 会在 GA 后 3 个版本(v1.39)彻底删除。
3.2 从 Pod YAML 到 CRI 挂载
当用户提交一个带有 ImageVolume 的 Pod 时,整个流程大致如下:
- API Server 接收 Pod 定义:
volumes.image字段被写入 etcd; - Scheduler 调度 Pod:和普通 Pod 一样,根据资源需求、亲和性等条件选择节点;
- Kubelet 准备 Volume:
- Kubelet 的 Volume Manager 发现这个 Pod 有一个
image类型的 Volume; - 通过 CRI 调用容器运行时(containerd / CRI-O)的镜像挂载能力;
- 容器运行时把 OCI 镜像的文件系统层挂载到节点上的一个目录;
- Kubelet 把这个目录 bind mount 到容器的
mountPath;
- Kubelet 的 Volume Manager 发现这个 Pod 有一个
- 容器启动:业务容器看到
/models下就是镜像里的文件。
这个过程中,containerd 扮演了关键角色。它负责:
- 解析镜像引用(tag 或 digest);
- 从 Registry 拉取镜像(如果本地没有);
- 把镜像层解压并组合成一个可挂载的 rootfs;
- 以只读方式挂载到指定路径。
3.3 containerd 支持的时间线
ImageVolume 的落地速度和 containerd 的支持密切相关:
- Alpha 阶段(v1.31):containerd 不支持 ImageVolume,需要手动编译特定 PR 才能体验;
- containerd v2.1.0:正式原生支持 ImageVolume;
- CRI-O:从 v1.31 就支持了,v1.34 还增加了 subPath 支持,一直走在前面。
所以生产环境使用 ImageVolume 的基本要求很简单:
- Kubernetes >= v1.36
- containerd >= v2.1.0(或 CRI-O >= v1.31)
不需要额外配置任何 Feature Gate。
3.4 关键 API 字段解析
volumes:
- name: model-volume
image:
reference: registry.example.com/models/qwen2-0.5b:v1
pullPolicy: IfNotPresent
volumeAttributesClassName: fast-ssd # 可选,用于 DRA 场景
reference:镜像引用,支持 tag 或 digest。生产环境强烈建议使用 digest;pullPolicy:Always:每次 Pod 创建都重新拉取;IfNotPresent:本地没有才拉取(默认);Never:只使用本地镜像,不拉取。
注意:ImageVolume 挂载的是只读的。如果业务需要在运行时修改数据,还是得用 PVC 或 emptyDir。
四、代码实战:从构建 OCI 数据镜像到生产落地
4.1 构建一个 OCI 数据镜像
假设我们有一个 AI 推理服务,需要加载 Qwen2-0.5B 和 Llama2-7B 两个模型。我们可以把这两个模型打包成一个 OCI 镜像,然后让 ImageVolume 挂载到 Pod 中。
4.1.1 准备模型文件
mkdir -p image-builder/models/Qwen2-0.5B
mkdir -p image-builder/models/Llama2-7B
echo "qwen2 model weights (placeholder)" > image-builder/models/Qwen2-0.5B/model.bin
echo '{"model_type": "qwen2", "vocab_size": 151936}' > image-builder/models/Qwen2-0.5B/config.json
echo "llama2 model weights (placeholder)" > image-builder/models/Llama2-7B/model.bin
echo '{"model_type": "llama2", "vocab_size": 32000}' > image-builder/models/Llama2-7B/config.json
echo "app config v1" > image-builder/app.conf
目录结构:
image-builder/
├── Dockerfile
├── app.conf
└── models/
├── Qwen2-0.5B/
│ ├── config.json
│ └── model.bin
└── Llama2-7B/
├── config.json
└── model.bin
4.1.2 编写 Dockerfile
关键点:数据镜像不需要基础镜像,直接用 FROM scratch。
FROM scratch
COPY ./models /models
COPY ./app.conf /app.conf
4.1.3 构建并推送
docker build -t registry.example.com/demo/image-volume:v1 image-builder/
docker push registry.example.com/demo/image-volume:v1
4.2 基本挂载:把整个镜像挂进 Pod
apiVersion: v1
kind: Pod
metadata:
name: image-volume-demo
spec:
containers:
- name: app
image: busybox:1.36
command: ["sleep", "3600"]
volumeMounts:
- name: model-volume
mountPath: /models
readOnly: true
volumes:
- name: model-volume
image:
reference: registry.example.com/demo/image-volume:v1
pullPolicy: IfNotPresent
验证:
kubectl apply -f pod.yaml
kubectl wait --for=condition=Ready pod/image-volume-demo --timeout=60s
kubectl exec image-volume-demo -- ls -la /models/
# drwxr-xr-x 1 root root 4096 Jun 20 00:00 .
# drwxr-xr-x 1 root root 4096 Jun 20 00:00 ..
# -rw-r--r-- 1 root root 14 Jun 20 00:00 app.conf
# drwxr-xr-x 2 root root 4096 Jun 20 00:00 Llama2-7B
# drwxr-xr-x 2 root root 4096 Jun 20 00:00 Qwen2-0.5B
kubectl exec image-volume-demo -- cat /models/Qwen2-0.5B/config.json
# {"model_type": "qwen2", "vocab_size": 151936}
4.3 只挂载子目录:subPath 实战
很多时候一个镜像里会放多个模型目录,如果 Pod 只需要其中一个,用 subPath 可以避免把整个镜像都挂载进来。
apiVersion: v1
kind: Pod
metadata:
name: image-volume-subpath
spec:
containers:
- name: app
image: busybox:1.36
command: ["sleep", "3600"]
volumeMounts:
- name: model-volume
mountPath: /models/qwen2
subPath: Qwen2-0.5B
readOnly: true
volumes:
- name: model-volume
image:
reference: registry.example.com/demo/image-volume:v1
pullPolicy: IfNotPresent
验证:
kubectl exec image-volume-subpath -- ls -la /models/qwen2/
# -rw-r--r-- 1 root root 55 Jun 20 00:00 config.json
# -rw-r--r-- 1 root root 37 Jun 20 00:00 model.bin
kubectl exec image-volume-subpath -- ls -la /models/
# total 12
# drwxr-xr-x 3 root root 4096 Jun 20 00:00 .
# drwxr-xr-x 1 root root 4096 Jun 20 00:00 ..
# drwxr-xr-xr-x 2 root root 4096 Jun 20 00:00 qwen2
注意:如果 subPath 指定的路径在镜像中不存在,容器创建会直接报错:
failed to mount image volume: ImageVolumeMountFailed: failed to ensure image subpath "not-exist-dir" in "...": openat not-exist-dir: no such file or directory
4.4 只读性验证
ImageVolume 挂载是只读的,尝试写入会报错:
kubectl exec image-volume-demo -- sh -c 'echo test > /models/test.txt'
# sh: can't create /models/test.txt: Read-only file system
4.5 与 AI 推理服务结合:KServe 场景示例
ImageVolume 最适合的场景之一就是 AI 模型推理。下面是一个简化的示例,展示如何用 ImageVolume 给推理服务提供模型权重。
apiVersion: apps/v1
kind: Deployment
metadata:
name: llm-inference
spec:
replicas: 2
selector:
matchLabels:
app: llm-inference
template:
metadata:
labels:
app: llm-inference
spec:
containers:
- name: vllm
image: vllm/vllm-openai:v0.8.0
args:
- --model
- /models/Qwen2-0.5B
- --served-model-name
- qwen2-0.5b
ports:
- containerPort: 8000
volumeMounts:
- name: model-volume
mountPath: /models
readOnly: true
resources:
limits:
nvidia.com/gpu: "1"
volumes:
- name: model-volume
image:
reference: registry.example.com/demo/image-volume:v1
pullPolicy: IfNotPresent
---
apiVersion: v1
kind: Service
metadata:
name: llm-inference
spec:
selector:
app: llm-inference
ports:
- port: 8000
targetPort: 8000
这个例子的好处:
- 模型权重和业务镜像解耦;
- 多个推理副本共享同一个模型镜像的层,节省磁盘;
- 模型版本独立演进,不需要重新打 vLLM 镜像。
4.6 使用 digest 引用,避免 tag 漂移
生产环境永远不要用 tag 引用 ImageVolume。tag 可以被覆盖,Pod 重建时可能拿到非预期版本。
volumes:
- name: model-volume
image:
reference: registry.example.com/demo/image-volume@sha256:abcd1234...
pullPolicy: IfNotPresent
v1.36 还 GA 了一个子特性 ImageVolumeWithDigest:Pod Status 里可以直接看到挂载镜像的 digest,方便版本追溯。
五、性能优化与生产注意事项
5.1 镜像层共享与磁盘压力
containerd 对镜像层的管理是全局的。如果两个 ImageVolume 引用的镜像有相同的层,containerd 只存一份。这意味着:
- 多个模型包共享基础层时,磁盘占用会显著减少;
- 但大模型镜像本身很大,节点磁盘容量需要提前规划;
- 建议为模型镜像设置合理的节点缓存策略,避免磁盘被无限占满。
5.2 拉取策略与冷启动时间
pullPolicy: IfNotPresent 是默认行为。第一次启动 Pod 时需要拉取镜像,会有冷启动时间。如果模型镜像很大(比如 10GB),第一次拉取可能耗时数分钟。
优化方案:
- Node 预热:在节点加入集群后,通过 DaemonSet 或节点初始化脚本提前拉取常用模型镜像;
- 镜像缓存:使用 kube-image-keeper 或 Spegel 等工具在集群内缓存镜像;
- 本地 Registry:在集群内部署本地 Registry,减少跨可用区/跨地域的拉取延迟。
5.3 只读限制与读写需求的妥协
ImageVolume 目前是只读的。如果业务需要在运行时修改数据(比如写入缓存、日志、临时文件),需要额外处理:
apiVersion: v1
kind: Pod
metadata:
name: llm-inference-with-cache
spec:
containers:
- name: vllm
image: vllm/vllm-openai:v0.8.0
volumeMounts:
- name: model-volume
mountPath: /models
readOnly: true
- name: cache
mountPath: /cache
volumes:
- name: model-volume
image:
reference: registry.example.com/demo/image-volume@sha256:abcd1234...
- name: cache
emptyDir:
sizeLimit: 10Gi
模型只读挂载,运行时缓存放在 emptyDir 或 PVC 中。
5.4 与 Kubelet 监控指标结合
v1.36 把 ImageVolume 相关的 Kubelet 指标提升到了 BETA 稳定性级别,可以在 Prometheus 里配置告警:
kubelet_image_volume_requested_total:请求的 ImageVolume 数量;kubelet_image_volume_mounted_succeed_total:挂载成功的数量;kubelet_image_volume_mounted_errors_total:挂载失败的数量。
Prometheus 告警示例:
- alert: ImageVolumeMountFailure
expr: rate(kubelet_image_volume_mounted_errors_total[5m]) > 0
for: 1m
labels:
severity: warning
annotations:
summary: "ImageVolume 挂载失败"
description: "节点 {{ $labels.instance }} 上的 ImageVolume 挂载出现错误"
5.5 安全考虑
- 镜像签名:模型镜像最好通过 Cosign 签名,确保来源可信;
- Registry 访问控制:ImageVolume 需要 Kubelet 能从节点访问 Registry,确保网络策略和凭证配置正确;
- 只读挂载:虽然不能写入,但恶意镜像仍可能包含危险文件,建议扫描镜像内容;
- Pod Security:使用 ImageVolume 的 Pod 通常需要允许挂载未知来源的镜像,需要在 PSP / PSA 策略中评估风险。
六、ImageVolume 的边界与替代方案
6.1 适合的场景
- 只读数据分发(模型权重、配置包、静态资源);
- 数据版本化和审计追溯(配合 digest);
- 多 Pod 共享数据,希望利用 OCI Registry 的分层去重;
- CI/CD 工件的原生消费。
6.2 不适合的场景
- 需要运行时修改的数据(必须用 PVC / emptyDir);
- 超大型单文件(OCI 镜像层的管理有开销);
- 对挂载延迟极其敏感的场景(首次拉取镜像有冷启动);
- 需要跨 Pod 实时同步的数据(ImageVolume 是本地挂载,不是共享存储)。
6.3 替代方案对比
| 方案 | 优点 | 缺点 |
|---|---|---|
| Init Container + emptyDir | 简单,不需要新特性 | 多一次容器调度,无分层去重 |
| 自定义 CSI Driver(ORAS) | 灵活,可定制 | 运维复杂,自己维护全链路 |
| 把数据打进业务镜像 | 简单 | 镜像膨胀,版本耦合 |
| PVC + 对象存储挂载 | 可读写,容量大 | 需要 provision,跨集群分发复杂 |
| ImageVolume | 原生支持,分层去重,版本化 | 只读,冷启动 |
七、总结与展望
7.1 从容器到数据分发层的范式转移
OCI 从 2017 年的 image-spec v1.0 走到今天的 image-spec v1.1.0,经历了一条清晰的路径:
- 先把容器镜像格式标准化;
- 社区发现 OCI Registry 也适合分发非镜像内容,开始各种 hack;
- OCI 规范把 Artifacts 正式纳入,支持
artifactType和 referrers API; - Kubernetes 通过 ImageVolume 提供了第一个原生的消费方式。
ImageVolume GA 标志着一个重要的范式转移:OCI 镜像不再只是容器的载体,而是正在成为云原生场景下的通用只读数据分发层。
7.2 对 AI 工作负载的意义
2026 年,AI 推理服务的部署正在从"把模型打进业务镜像"转向"把模型作为独立 Artifact 分发"。ImageVolume 让这个转变在 Kubernetes 上变得自然:
- 模型版本可以独立管理;
- 多个推理副本可以共享模型层;
- 模型分发可以复用成熟的 OCI Registry 生态(镜像仓库、签名、扫描、缓存)。
7.3 未来的可能性
目前 ImageVolume 只支持只读挂载,社区已经在讨论读写支持的可能性。如果未来 ImageVolume 能支持可写层,那么 OCI 镜像将可以直接替代很多 PVC 场景,进一步统一数据分发模型。
此外,随着 DRA(Dynamic Resource Allocation)的成熟,ImageVolume 可能会和 GPU、NPU 等硬件资源更紧密地结合,形成"模型数据 + 算力资源"的一体化声明式调度。
7.4 给生产实践者的建议
- 先确认容器运行时版本:containerd 必须 >= v2.1.0;
- 用 digest 引用镜像:避免 tag 漂移带来的版本不一致;
- 做好节点镜像缓存:大模型镜像冷启动不可接受;
- 监控 ImageVolume 挂载指标:及时发现挂载失败;
- 签名和扫描模型镜像:不要引入未经验证的数据镜像;
- 明确只读边界:运行时修改的数据放在 emptyDir 或 PVC 中。
ImageVolume 不是银弹,但它确实解决了一个长期存在的痛点:在 Kubernetes 中,如何原生、高效、可版本化地消费 OCI 格式的数据。对于正在落地 AI 推理、模型分发、配置即代码的团队来说,它值得认真评估。
参考与延伸阅读
- KEP-4639: Image Volume Source — https://github.com/kubernetes/enhancements/issues/4639
- OCI image-spec v1.1.0 — https://github.com/opencontainers/image-spec/releases
- containerd v2.1.0 Release Notes — https://github.com/containerd/containerd/releases
- ORAS: OCI Registry As Storage — https://oras.land/
- Kubernetes v1.36 Feature Gates — https://kubernetes.io/docs/reference/command-line-tools-reference/feature-gates/