编程 Kubernetes 1.36 ImageVolume GA:当 OCI 镜像从容器运行时滑向数据分发层——从 KEP-4639 到生产落地的完全指南(2026)

2026-06-20 01:54:28 +0800 CST views 12

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 中的数据,通常要绕很多路:

  1. Init Container 拉取 + 共享 emptyDir:启动一个专门负责下载的容器,把数据放到 emptyDir 里,业务容器再挂载使用。问题是多一次容器调度、多一份网络流量、多一份临时存储开销。
  2. 自己实现 CSI Driver:比如用 ORAS 写一个自定义存储驱动,把 OCI 镜像当 Volume 挂载。问题是运维复杂度爆炸,需要维护镜像拉取、缓存、校验、更新等全链路。
  3. 把数据直接打进业务镜像:把模型权重、配置文件和业务代码打在一个镜像里。问题是镜像膨胀、版本耦合、每次数据更新都要重新打业务镜像。

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 里的类型从 emptyDirconfigMap 换成了 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:支持 AlwaysIfNotPresentNever,语义和容器镜像一致。

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 默认值关键变化
Alphav1.31false特性引入,需要手动开启
Beta(默认关)v1.33false支持 subPath / subPathExpr
Beta(默认关)v1.34false移除 noexec 限制
Beta(默认开)v1.35true首次默认启用
GAv1.36true(锁定)Feature Gate 锁定,E2E 提升为 Conformance

GA 之后,不再需要手动开启 Feature Gate,API 字段上的 +featureGate=ImageVolume 注解也被移除。按照 Kubernetes 的惯例,这个 Feature Gate 会在 GA 后 3 个版本(v1.39)彻底删除。

3.2 从 Pod YAML 到 CRI 挂载

当用户提交一个带有 ImageVolume 的 Pod 时,整个流程大致如下:

  1. API Server 接收 Pod 定义volumes.image 字段被写入 etcd;
  2. Scheduler 调度 Pod:和普通 Pod 一样,根据资源需求、亲和性等条件选择节点;
  3. Kubelet 准备 Volume
    • Kubelet 的 Volume Manager 发现这个 Pod 有一个 image 类型的 Volume;
    • 通过 CRI 调用容器运行时(containerd / CRI-O)的镜像挂载能力;
    • 容器运行时把 OCI 镜像的文件系统层挂载到节点上的一个目录;
    • Kubelet 把这个目录 bind mount 到容器的 mountPath
  4. 容器启动:业务容器看到 /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,经历了一条清晰的路径:

  1. 先把容器镜像格式标准化;
  2. 社区发现 OCI Registry 也适合分发非镜像内容,开始各种 hack;
  3. OCI 规范把 Artifacts 正式纳入,支持 artifactType 和 referrers API;
  4. 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 给生产实践者的建议

  1. 先确认容器运行时版本:containerd 必须 >= v2.1.0;
  2. 用 digest 引用镜像:避免 tag 漂移带来的版本不一致;
  3. 做好节点镜像缓存:大模型镜像冷启动不可接受;
  4. 监控 ImageVolume 挂载指标:及时发现挂载失败;
  5. 签名和扫描模型镜像:不要引入未经验证的数据镜像;
  6. 明确只读边界:运行时修改的数据放在 emptyDir 或 PVC 中。

ImageVolume 不是银弹,但它确实解决了一个长期存在的痛点:在 Kubernetes 中,如何原生、高效、可版本化地消费 OCI 格式的数据。对于正在落地 AI 推理、模型分发、配置即代码的团队来说,它值得认真评估。


参考与延伸阅读

复制全文 生成海报 Kubernetes ImageVolume OCI 云原生 容器化

推荐文章

rmux Test
2026-05-22 18:48:45 +0800 CST
PyMySQL - Python中非常有用的库
2024-11-18 14:43:28 +0800 CST
你可能不知道的 18 个前端技巧
2025-06-12 13:15:26 +0800 CST
Vue3中如何实现响应式数据?
2024-11-18 10:15:48 +0800 CST
总结出30个代码前端代码规范
2024-11-19 07:59:43 +0800 CST
liunx宝塔php7.3安装mongodb扩展
2024-11-17 11:56:14 +0800 CST
程序员茄子在线接单