找回密码
 立即注册
首页 业界区 业界 K8s v1.31 新特性:ImageVolume,允许将镜像作为 Volume ...

K8s v1.31 新特性:ImageVolume,允许将镜像作为 Volume 进行挂载

柴古香 2025-6-18 13:57:00
1.png

本文主要分享一个 K8s 1.31 增加的一个新 Feature:ImageVolume。允许直接将 OCI 镜像作为 Volume 进行挂载,加速 artifact 分发。
1.背景

Kubernetes 社区正在积极发展,以更好地支持未来的人工智能 (AI) 和机器学习 (ML) 场景。
为满足这些用例的需求之一,Kubernetes 正在增强对开放容器倡议 (OCI) 兼容镜像和工件(即 OCI 对象)作为原生卷源的支持
这样,用户可以专注于使用符合 OCI 标准的工具,并将 OCI 注册表作为存储和分发任何内容的手段。
基于这一愿景,Kubernetes v1.31 引入了一个新的 Alpha 特性:ImageVolume,允许在 Pod 中将 OCI 镜像作为卷使用。该功能可以将一个 OCI 镜像作为 volume 挂载到一个 Pod 中使用,从而可以在 Pod 中访问 OCI 镜像中存储的文件。
该功能是在当前 AI 技术大行其道背景下应运而生的,部署在 K8S 上的 AI 应用急需一种高效且通用的方式分发模型权重文件,此功能可以像分发镜像一样分发模型文件,用户只需要制作好包含了模型权重文件的 OCI 镜像,就可以在 POD 中挂载 OCI 镜像访问其内的模型文件,不再需要复制或者下载模型文件。
通过这一特性,用户可以在 Pod 中引用镜像作为卷,并在容器中以挂载的形式重用这些镜像。
就像这样:
  1. kind: Pod
  2. spec:
  3.   containers:
  4.     - …
  5.       volumeMounts:
  6.         - name: my-volume
  7.           mountPath: /path/to/directory
  8.   volumes:
  9.     - name: my-volume
  10.       image:
  11.         reference: my-image:tag
复制代码
这将为在 Kubernetes 中更灵活地管理镜像和卷的需求铺平道路,特别是对于 AI 和 ML 工作负载的复杂需求。
比如在大模型场景,有了 ImageVolume 支持,我们可以直接将大模型打包到镜像里,使用时直接挂载该镜像即可,而且是整个集群都可以使用。
之前通过 PVC 存储时由于 PVC 是 Namespace 范围的,导致同一个模型不同 namespace 下使用都需要重复下载。
大模型体检比较大,每次下载都会花费不少时间。
注意:目前 ImageVolume 仅作为 Alpha 特性引入,未来呈现方式可能有所变化,如果需要体验该功能需要配置 K8S 以启用该特性。
2.环境准备

ImageVolume 特性来源于KEP-4639, 由 SIG Node 和 SIG Storage 共同完成。
要使用该功能,我们需要先做一些准备工作:

  • 启用 ImageVolume  Feature Gates

    • k8s 1.31
    • kube-apiserver 启用
    • kubelet 启用 FeatureGate

  • Container Runtime 支持

    • 当前仅 CRI-O 1.31 版本支持(因为 CRI-O 是和 k8s 同时发版的)
    • containerd 需要等待 PR #10579 合并才行

部署 v1.31 版本集群

首先我们要准备一个 1.31 版本的 k8s 集群。
推荐使用 kubeclipper 来安装集群,这里我们直接使用 master 分支,先安装 kcctl:
  1. curl -sfL https://oss.kubeclipper.io/get-kubeclipper.sh | KC_REGION=cn KC_VERSION=master bash
复制代码
确认版本
  1. [root@imagevolume ~]# kcctl version
  2. kcctl version: version.Info{Major:"1", Minor:"4+", GitVersion:"v1.4.0-53+d78681d3b56134", GitCommit:"d78681d3b56134af88a0520c5f2892cdcefce82c", GitTreeState:"clean", BuildDate:"2024-10-18T01:20:12Z", GoVersion:"go1.22.5", Compiler:"gc", Platform:"linux/amd64"}
复制代码
使用 kcctl 部署 KubeClipper:
  1. # 指定下载 master 版本离线包
  2. version=master
  3. wget https://oss.kubeclipper.io/release/${version}/kc-amd64.tar.gz
  4. # 然后使用刚下载的离线包进行部署
  5. node=192.168.10.6
  6. passwd=Thinkbig1
  7. kcctl deploy --server $node --agent $node --passwd $passwd --pkg kc-amd64.tar.gz --v 5
复制代码
部署完成再次查看版本:
  1. [root@imagevolume ~]# kcctl version
  2. kcctl version: version.Info{Major:"1", Minor:"4+", GitVersion:"v1.4.0-53+d78681d3b56134", GitCommit:"d78681d3b56134af88a0520c5f2892cdcefce82c", GitTreeState:"clean", BuildDate:"2024-10-18T01:20:12Z", GoVersion:"go1.22.5", Compiler:"gc", Platform:"linux/amd64"}kubeclipper-server version: version.Info{Major:"1", Minor:"4+", GitVersion:"v1.4.0-53+d78681d3b56134", GitCommit:"d78681d3b56134af88a0520c5f2892cdcefce82c", GitTreeState:"clean", BuildDate:"2024-10-18T01:14:17Z", GoVersion:"go1.22.5", Compiler:"gc", Platform:"linux/amd64"}
复制代码
至此,KubeClipper 安装完成。
KubeClipper 默认离线包内置集群不是最新的 1.31版本,因此我们需要手动 push 一下离线包。
从 oss 下载然后打成 tar.gz 包即可,命令如下:
  1. mkdir -p k8s/v1.31.1/amd64
  2. pushd k8s/v1.31.1/amd64
  3. # 下载准备好的离线包
  4. wget https://oss.kubeclipper.io/packages/k8s/v1.31.1/amd64/manifest.json
  5. wget https://oss.kubeclipper.io/packages/k8s/v1.31.1/amd64/images.tar.gz
  6. wget https://oss.kubeclipper.io/packages/k8s/v1.31.1/amd64/configs.tar.gz
  7. popd
  8. tar -zcvf k8s-v1.31.1-amd64.tar.gz  k8s  
复制代码
然后使用 kcctl resource push 命令进行推送
  1. kcctl resource push --pkg k8s-v1.31.1-amd64.tar.gz --type k8s
复制代码
查看
  1. [root@imagevolume ~]# kcctl resource list
  2. +--------------+---------------+---------------+---------+-------+
  3. | 192.168.10.6 |     TYPE      |     NAME      | VERSION | ARCH  |
  4. +--------------+---------------+---------------+---------+-------+
  5. | 1.           | cni           | calico        | v3.26.1 | amd64 |
  6. | 2.           | cri           | containerd    | 1.6.4   | amd64 |
  7. | 3.           | k8s           | k8s           | v1.27.4 | amd64 |
  8. | 4.           | k8s           | k8s           | v1.31.1 | amd64 |
  9. | 5.           | k8s-extension | k8s-extension | v1      | amd64 |
  10. | 6.           | kc-extension  | kc-extension  | latest  | amd64 |
  11. +--------------+---------------+---------------+---------+-------+
复制代码
后续就可以创建 1.31.1 版本的 k8s 了,通过--k8s-version v1.31.1 指定安装 1.31 版本。
  1. node=192.168.10.6
  2. kcctl create cluster --name imagevolume --master $node --untaint-master --k8s-version v1.31.1
复制代码
等待几分钟之后,我们就得到了一个 1.31 版本的 k8s 集群。
k8s 启 Feature Gates

然后分别为 kube-apiserver 和 kubelet 启用对应的 Feature Gates。
kube-apiserver

配置 apiserver 启动参数 --feature-gates
kube-apiserver 以 static pod 方式启动,修改配置需要编辑 /etc/kubernetes/manifests/kube-apiserver.yaml 文件:
  1. vi /etc/kubernetes/manifests/kube-apiserver.yaml
复制代码
找到启动命令行,增加以下参数,保存后,等待 kube-apiserver 自动重启完成
  1. --feature-gates=ImageVolume=true
复制代码
kubelet

kubelet 开启 FeatureGates 则是修改配置文件:
  1. vi /var/lib/kubelet/config.yaml
复制代码
在末尾添加如下内容
  1. featureGates:
  2.   ImageVolume: true
复制代码
然后重启 kubelet
  1. systemctl restart kubelet
复制代码
以上操作完成后,当前集群就具备了 ImageVolume 特性,但是该功能还依赖了 CRI 的种类与版本,需要特别注意。
CRI 配置

目前支持 ImageVolume 特性的 CRI 包括 cri-o 以及 containerd:

  • cri-o 必须 >= v1.31 版本
  • containerd 目前的 patch 还没合并完成,目前的解决方法是手动 merge patch,然后构建二进制文件进行替换。
CRI-O

cri-o 基本上是与 k8s 同时发布新版本,所以一般来说,k8s 有什么新特性,只要依赖了 cri 版本的,那么 cri-o 大多都是最早支持的,ImageVolume 也不例外。
只需要在安装 K8S 集群时,使用 >= v1.31 版本的 cri-o 即可。
如何安装本文档不再赘述,有需求的可以参考相关文档 --> Running Kubernetes with CRI-O
Containerd

社区已经有人提交了 containerd 支持 ImageVolume PR,只是该 PR 目前还未合并,目前我们需要使用 containerd 作为 CRI 来体验 ImageVolume 特性的话,需要自己手动 checkout PR,然后构建二进制文件替换到集群中即可。
相关 patch:#10579 Add OCI/Image Volume Source support


  • clone containerd main 分支代码
  1. git clone https://github.com/containerd/containerd.git
复制代码

  • checkout #10579 PR
  1. # 安装 gh:https://github.com/cli/cli
  2. # 先登录:gh auth login
  3. # checkout 对应 pr
  4. gh pr checkout 10579
复制代码
注意:此 PR 代码有一处校验容器 mounts 是否为 readOnly,如果容器 mounts 配置里 readOnly 不为 true,那么会直接抛出错误。
经过查阅当前 k8s 实现 ImageVolume 时,kubelet 在调度创建涉及 ImageVolume 的容器中,并没有将 Pod 中 volumeMounts 配置的 readOnly 参数透传到 CRI mounts 配置中,这会导致 CRI mounts 中 readOnly 始终为 false,从而导致 containerd 创建容器一直报错。
kubelet 处理核心逻辑:
  1. // kubernetes/pkg/kubelet/kubelet_pods.go makeMounts 方法
  2. if imageVolumes != nil && utilfeature.DefaultFeatureGate.Enabled(features.ImageVolume) {
  3.                         if image, ok := imageVolumes[mount.Name]; ok {
  4.                                 mounts = append(mounts, kubecontainer.Mount{
  5.                                         Name:          mount.Name,
  6.                                         ContainerPath: mount.MountPath,
  7.                                         Image:         image,
  8.                                 })
  9.                                 continue
  10.                         }
  11.                 }
  12. ...
复制代码
containerd 中的校验
  1. // containerd/internal/cri/server/container_image_mount.go 中mutateImageMount 方法
  2. ...
  3.         if !extraMount.GetReadonly() {
  4.                 return fmt.Errorf("readonly must be true while mount image: %+v", extraMount)
  5.         }
  6. ...
复制代码
为了让整个流程先能跑通,我们先暂时把 Containerd 中的这个校验注释掉。

  • 修改之后重新构建 containerd 二进制文件
  1. GOOS=linux GOARCH=amd64 CGO_ENABLED=0 make binaries
复制代码
构建后的参数在 containerd/bin 目录下,会生成以下几个文件:
  1. -rwxr-xr-x 1 x 40M  9 20 16:37 containerd
  2. -rwxr-xr-x 1 x 14M  9 20 16:37 containerd-shim-runc-v2
  3. -rwxr-xr-x 1 x 20M  9 20 16:37 containerd-stress
  4. -rwxr-xr-x 1 x 21M  9 20 16:37 ctr
复制代码
将其全部复制到 K8S 集群节点 /usr/local/bin/ 目录,做好旧二进制文件备份。

  • 替换现有环境的 containerd
  1. # 停止 containerd
  2. systemctl stop containerd
  3. # cp 自己构建的二进制文件到 /usr/local/bin
  4. cp containerd containerd-shim-runc-v2 containerd-stress ctr /usr/local/bin
  5. systemctl start containerd
复制代码
最后等待集群启动,查看节点当前的 cri 版本
  1. kubectl get node -owide
复制代码
至此,集群就可以使用 ImageVolume 功能了。
3. 使用

构建目标镜像

这里就简单创建一个包含了 Qwen2-0.5B 大模型权重文件的 OCI 镜像。

  • 下载 Qwen2-0.5B 大模型
  1. mkdir models && cd models
  2. git lfs install
  3. git clone https://www.modelscope.cn/qwen/Qwen2-0.5B.git
复制代码
模型内容如下:
  1. [root@docker models]# ll Qwen2-0.5B/ -lhS
  2. total 1.2G
  3. -rw-r--r-- 1 root root 1.2G Oct 12 08:35 model.safetensors
  4. -rw-r--r-- 1 root root 6.8M Oct 12 08:39 tokenizer.json
  5. -rw-r--r-- 1 root root 2.7M Oct 12 08:39 vocab.json
  6. -rw-r--r-- 1 root root 1.6M Oct 12 08:39 merges.txt
  7. -rw-r--r-- 1 root root  12K Oct 12 08:39 LICENSE
  8. -rw-r--r-- 1 root root 4.8K Oct 12 08:39 README.md
  9. -rw-r--r-- 1 root root 1.3K Oct 12 08:39 tokenizer_config.json
  10. -rw-r--r-- 1 root root  661 Oct 12 08:39 config.json
  11. -rw-r--r-- 1 root root  138 Oct 12 08:39 generation_config.json
  12. -rw-r--r-- 1 root root   48 Oct 12 08:39 configuration.json
复制代码

  • 新建 Dockerfile,拷贝 models 目录到镜像指定目录
Dockerfile 如下:
  1. FROM scratch
  2. COPY ./models /models
复制代码
目录结构如下所示:
  1. [root@docker ~]# tree image-builder/
  2. image-builder/
  3. ├── Dockerfile
  4. └── models
  5.     └── Qwen2-0.5B
  6.         ├── config.json
  7.         ├── configuration.json
  8.         ├── generation_config.json
  9.         ├── LICENSE
  10.         ├── merges.txt
  11.         ├── model.safetensors
  12.         ├── README.md
  13.         ├── tokenizer_config.json
  14.         ├── tokenizer.json
  15.         └── vocab.json
复制代码

  • 构建镜像
  1. cd image-builder
  2. docker build  -t lixd96/qwen2-0.5b:v1 .
复制代码
后续将其作为 OCI 镜像挂载到 Pod 中使用。
创建 Pod 挂载 OCI 镜像

创建一个 Pod 挂载上述 OCI 镜像,完整 yaml 内容如下:
  1. apiVersion: v1
  2. kind: Pod
  3. metadata:
  4.   name: oci-pod
  5. spec:
  6.   containers:
  7.     - name: test
  8.       image: busybox:1.36
  9.       imagePullPolicy: IfNotPresent
  10.       command:
  11.         - sleep
  12.         - "3600"
  13.       volumeMounts:
  14.         - name: volume
  15.           mountPath: /volume
  16.           readOnly: true
  17.   volumes:
  18.     - name: volume
  19.       image:
  20.         reference: lixd96/qwen2-0.5b:v1
  21.         pullPolicy: IfNotPresent
复制代码
应用到集群中
  1. kubectl apply -f pod.yaml
复制代码
等待 Pod 调度成功后,进入 Pod 中查看 /volume 目录就可以看到模型权重文件了
  1. [root@imagevolume ~]# k exec -it oci-pod -- ls -al /volume/models/Qwen2-0.5B/
  2. total 1221384
  3. drwxr-xr-x    2 root     root           247 Oct 12 08:40 .
  4. drwxr-xr-x    3 root     root            24 Oct 12 08:42 ..
  5. -rw-r--r--    1 root     root          1519 Oct 12 08:39 .gitattributes
  6. -rw-r--r--    1 root     root         11344 Oct 12 08:39 LICENSE
  7. -rw-r--r--    1 root     root          4819 Oct 12 08:39 README.md
  8. -rw-r--r--    1 root     root           661 Oct 12 08:39 config.json
  9. -rw-r--r--    1 root     root            48 Oct 12 08:39 configuration.json
  10. -rw-r--r--    1 root     root           138 Oct 12 08:39 generation_config.json
  11. -rw-r--r--    1 root     root       1671839 Oct 12 08:39 merges.txt
  12. -rw-r--r--    1 root     root     1239173352 Oct 12 08:35 model.safetensors
  13. -rw-r--r--    1 root     root       7028015 Oct 12 08:39 tokenizer.json
  14. -rw-r--r--    1 root     root          1289 Oct 12 08:39 tokenizer_config.json
  15. -rw-r--r--    1 root     root       2776833 Oct 12 08:39 vocab.json
复制代码
4. 小结

本文主要分享了 k8s 1.31 新特性 ImageVolume,包括配置以及使用方式。
要使用该功能,我们需要先做一些准备工作:

  • 启用 ImageVolume  Feature Gates

    • k8s 1.31
    • kube-apiserver 启用
    • kubelet 启用 FeatureGate

  • Container Runtime 支持

    • 当前仅 CRI-O 1.31 版本支持(因为 CRI-O 是和 k8s 同时发版的)
    • containerd 需要等待 PR #10579 合并才行

目标镜像构建也和普通镜像一样:
  1. FROM scratch
  2. COPY ./models /models
复制代码
挂载方式:
  1. apiVersion: v1
  2. kind: Pod
  3. metadata:
  4.   name: oci-pod
  5. spec:
  6.   containers:
  7.     - name: test
  8.       image: docker.io/library/busybox:latest
  9.       imagePullPolicy: IfNotPresent
  10.       command:
  11.         - sleep
  12.         - "3600"
  13.       volumeMounts:
  14.         - name: volume
  15.           mountPath: /volume
  16.           readOnly: true
  17.   volumes:
  18.     - name: volume
  19.       image:
  20.         reference: registry.cn-beijing.aliyuncs.com/kubeclipper/qwen1.5-0.5b-chat:latest
  21.         pullPolicy: IfNotPresent
复制代码
体验下来感觉 ImageVolume 功能在 AI 相关场景应该是有较大的发挥空间的,可以让 artifact 分发更加方便。
【Kubernetes 系列】持续更新中,搜索公众号【探索云原生】订阅,阅读更多文章。
2.png

5.参考

Kubernetes 1.31: Read Only Volumes Based On OCI Artifacts (alpha)
Running Kubernetes with CRI-O
Kubeclipper

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
您需要登录后才可以回帖 登录 | 立即注册