本文主要分享一个 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 中引用镜像作为卷,并在容器中以挂载的形式重用这些镜像。

就像这样:

kind: Pod
spec:
containers:
- …
volumeMounts:
- name: my-volume
mountPath: /path/to/directory
volumes:
- name: my-volume
image:
reference: my-image:tag

这将为在 Kubernetes 中更灵活地管理镜像和卷的需求铺平道路,特别是对于 AI 和 ML 工作负载的复杂需求。

比如在大模型场景,有了 ImageVolume 支持,我们可以直接将大模型打包到镜像里,使用时直接挂载该镜像即可,而且是整个集群都可以使用。

之前通过 PVC 存储时由于 PVC 是 Namespace 范围的,导致同一个模型不同 namespace 下使用都需要重复下载。

大模型体检比较大,每次下载都会花费不少时间。

注意:目前 ImageVolume 仅作为 Alpha 特性引入,未来呈现方式可能有所变化,如果需要体验该功能需要配置 K8S 以启用该特性。

2.环境准备

ImageVolume 特性来源于KEP-4639, 由 SIG NodeSIG 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:

curl -sfL https://oss.kubeclipper.io/get-kubeclipper.sh | KC_REGION=cn KC_VERSION=master bash

确认版本

[root@imagevolume ~]# kcctl version
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:

# 指定下载 master 版本离线包
version=master
wget https://oss.kubeclipper.io/release/${version}/kc-amd64.tar.gz
# 然后使用刚下载的离线包进行部署
node=192.168.10.6
passwd=Thinkbig1
kcctl deploy --server $node --agent $node --passwd $passwd --pkg kc-amd64.tar.gz --v 5

部署完成再次查看版本:

[root@imagevolume ~]# kcctl version
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 包即可,命令如下:

mkdir -p k8s/v1.31.1/amd64

pushd k8s/v1.31.1/amd64

# 下载准备好的离线包
wget https://oss.kubeclipper.io/packages/k8s/v1.31.1/amd64/manifest.json
wget https://oss.kubeclipper.io/packages/k8s/v1.31.1/amd64/images.tar.gz
wget https://oss.kubeclipper.io/packages/k8s/v1.31.1/amd64/configs.tar.gz popd tar -zcvf k8s-v1.31.1-amd64.tar.gz k8s

然后使用 kcctl resource push 命令进行推送

kcctl resource push --pkg k8s-v1.31.1-amd64.tar.gz --type k8s

查看

[root@imagevolume ~]# kcctl resource list
+--------------+---------------+---------------+---------+-------+
| 192.168.10.6 | TYPE | NAME | VERSION | ARCH |
+--------------+---------------+---------------+---------+-------+
| 1. | cni | calico | v3.26.1 | amd64 |
| 2. | cri | containerd | 1.6.4 | amd64 |
| 3. | k8s | k8s | v1.27.4 | amd64 |
| 4. | k8s | k8s | v1.31.1 | amd64 |
| 5. | k8s-extension | k8s-extension | v1 | amd64 |
| 6. | kc-extension | kc-extension | latest | amd64 |
+--------------+---------------+---------------+---------+-------+

后续就可以创建 1.31.1 版本的 k8s 了,通过--k8s-version v1.31.1 指定安装 1.31 版本。

node=192.168.10.6
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 文件:

vi /etc/kubernetes/manifests/kube-apiserver.yaml

找到启动命令行,增加以下参数,保存后,等待 kube-apiserver 自动重启完成

--feature-gates=ImageVolume=true

kubelet

kubelet 开启 FeatureGates 则是修改配置文件:

vi /var/lib/kubelet/config.yaml

在末尾添加如下内容

featureGates:
ImageVolume: true

然后重启 kubelet

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 分支代码
git clone https://github.com/containerd/containerd.git
# 安装 gh:https://github.com/cli/cli
# 先登录:gh auth login # checkout 对应 pr
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 处理核心逻辑:

// kubernetes/pkg/kubelet/kubelet_pods.go makeMounts 方法
if imageVolumes != nil && utilfeature.DefaultFeatureGate.Enabled(features.ImageVolume) {
if image, ok := imageVolumes[mount.Name]; ok {
mounts = append(mounts, kubecontainer.Mount{
Name: mount.Name,
ContainerPath: mount.MountPath,
Image: image,
})
continue
}
}
...

containerd 中的校验

// containerd/internal/cri/server/container_image_mount.go 中mutateImageMount 方法
...
if !extraMount.GetReadonly() {
return fmt.Errorf("readonly must be true while mount image: %+v", extraMount)
}
...

为了让整个流程先能跑通,我们先暂时把 Containerd 中的这个校验注释掉。

  • 修改之后重新构建 containerd 二进制文件
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 make binaries

构建后的参数在 containerd/bin 目录下,会生成以下几个文件:

-rwxr-xr-x 1 x 40M  9 20 16:37 containerd
-rwxr-xr-x 1 x 14M 9 20 16:37 containerd-shim-runc-v2
-rwxr-xr-x 1 x 20M 9 20 16:37 containerd-stress
-rwxr-xr-x 1 x 21M 9 20 16:37 ctr

将其全部复制到 K8S 集群节点 /usr/local/bin/ 目录,做好旧二进制文件备份。

  • 替换现有环境的 containerd
# 停止 containerd
systemctl stop containerd
# cp 自己构建的二进制文件到 /usr/local/bin
cp containerd containerd-shim-runc-v2 containerd-stress ctr /usr/local/bin
systemctl start containerd

最后等待集群启动,查看节点当前的 cri 版本

kubectl get node -owide

至此,集群就可以使用 ImageVolume 功能了。

3. 使用

构建目标镜像

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

  • 下载 Qwen2-0.5B 大模型
mkdir models && cd models
git lfs install
git clone https://www.modelscope.cn/qwen/Qwen2-0.5B.git

模型内容如下:

[root@docker models]# ll Qwen2-0.5B/ -lhS
total 1.2G
-rw-r--r-- 1 root root 1.2G Oct 12 08:35 model.safetensors
-rw-r--r-- 1 root root 6.8M Oct 12 08:39 tokenizer.json
-rw-r--r-- 1 root root 2.7M Oct 12 08:39 vocab.json
-rw-r--r-- 1 root root 1.6M Oct 12 08:39 merges.txt
-rw-r--r-- 1 root root 12K Oct 12 08:39 LICENSE
-rw-r--r-- 1 root root 4.8K Oct 12 08:39 README.md
-rw-r--r-- 1 root root 1.3K Oct 12 08:39 tokenizer_config.json
-rw-r--r-- 1 root root 661 Oct 12 08:39 config.json
-rw-r--r-- 1 root root 138 Oct 12 08:39 generation_config.json
-rw-r--r-- 1 root root 48 Oct 12 08:39 configuration.json
  • 新建 Dockerfile,拷贝 models 目录到镜像指定目录

Dockerfile 如下:

FROM scratch
COPY ./models /models

目录结构如下所示:

[root@docker ~]# tree image-builder/
image-builder/
├── Dockerfile
└── models
└── Qwen2-0.5B
├── config.json
├── configuration.json
├── generation_config.json
├── LICENSE
├── merges.txt
├── model.safetensors
├── README.md
├── tokenizer_config.json
├── tokenizer.json
└── vocab.json
  • 构建镜像
cd image-builder
docker build -t lixd96/qwen2-0.5b:v1 .

后续将其作为 OCI 镜像挂载到 Pod 中使用。

创建 Pod 挂载 OCI 镜像

创建一个 Pod 挂载上述 OCI 镜像,完整 yaml 内容如下:

apiVersion: v1
kind: Pod
metadata:
name: oci-pod
spec:
containers:
- name: test
image: busybox:1.36
imagePullPolicy: IfNotPresent
command:
- sleep
- "3600"
volumeMounts:
- name: volume
mountPath: /volume
readOnly: true
volumes:
- name: volume
image:
reference: lixd96/qwen2-0.5b:v1
pullPolicy: IfNotPresent

应用到集群中

kubectl apply -f pod.yaml

等待 Pod 调度成功后,进入 Pod 中查看 /volume 目录就可以看到模型权重文件了

[root@imagevolume ~]# k exec -it oci-pod -- ls -al /volume/models/Qwen2-0.5B/
total 1221384
drwxr-xr-x 2 root root 247 Oct 12 08:40 .
drwxr-xr-x 3 root root 24 Oct 12 08:42 ..
-rw-r--r-- 1 root root 1519 Oct 12 08:39 .gitattributes
-rw-r--r-- 1 root root 11344 Oct 12 08:39 LICENSE
-rw-r--r-- 1 root root 4819 Oct 12 08:39 README.md
-rw-r--r-- 1 root root 661 Oct 12 08:39 config.json
-rw-r--r-- 1 root root 48 Oct 12 08:39 configuration.json
-rw-r--r-- 1 root root 138 Oct 12 08:39 generation_config.json
-rw-r--r-- 1 root root 1671839 Oct 12 08:39 merges.txt
-rw-r--r-- 1 root root 1239173352 Oct 12 08:35 model.safetensors
-rw-r--r-- 1 root root 7028015 Oct 12 08:39 tokenizer.json
-rw-r--r-- 1 root root 1289 Oct 12 08:39 tokenizer_config.json
-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 合并才行

目标镜像构建也和普通镜像一样:

FROM scratch
COPY ./models /models

挂载方式:

apiVersion: v1
kind: Pod
metadata:
name: oci-pod
spec:
containers:
- name: test
image: docker.io/library/busybox:latest
imagePullPolicy: IfNotPresent
command:
- sleep
- "3600"
volumeMounts:
- name: volume
mountPath: /volume
readOnly: true
volumes:
- name: volume
image:
reference: registry.cn-beijing.aliyuncs.com/kubeclipper/qwen1.5-0.5b-chat:latest
pullPolicy: IfNotPresent

体验下来感觉 ImageVolume 功能在 AI 相关场景应该是有较大的发挥空间的,可以让 artifact 分发更加方便。


【Kubernetes 系列】持续更新中,搜索公众号【探索云原生】订阅,阅读更多文章。


5.参考

Kubernetes 1.31: Read Only Volumes Based On OCI Artifacts (alpha)

Running Kubernetes with CRI-O

Kubeclipper

K8s v1.31 新特性:ImageVolume,允许将镜像作为 Volume 进行挂载的更多相关文章

  1. Atitit jquery  1.4--v1.11  v1.12  v2.0  3.0 的新特性

    Atitit jquery  1.4--v1.11  v1.12  v2.0  3.0 的新特性 1.1. Jquery1.12  jQuery 2.2 和 1.12 新版本发布 - OPEN资讯.h ...

  2. Atitit.android  jsbridge v1新特性

    Atitit.android  jsbridge v1新特性 1. Java代码调用js并传参其实是通过WebView的loadUrl方法去调用的.只是参数url的写法不一样而已1 2. 三.JAVA ...

  3. Atitit.cateService分类管理新特性与设计文档说明v1

    Atitit.cateService分类管理新特性与设计文档说明v1 1. V2 新特性规划1 2. 分类管理1 3. 分类增加与修改维护2 4. Js控件分类数据绑定2 1. V2 新特性规划 增加 ...

  4. Sql Server 2012新特性 Online添加非空栏位.

    我们都知道,Sql Server在一个数据量巨大的表中添加一个非空栏位是比较费心的,缺乏经验的DBA或是开发人员甚至可能鲁莽地直接添加导致阻塞相应业务,甚至可能因为资源欠缺造成实例的全局问题.当然这都 ...

  5. Atitit.编程语言新特性 通过类库框架模式增强 提升草案 v3 q27

    Atitit.编程语言新特性 通过类库框架模式增强 提升草案 v3 q27 1. 修改历史2 2. 适用语言::几乎所有编程语言.语言提升的三个渠道::语法,类库,框架,ide2 2.1. 单根继承  ...

  6. Centos7.6部署k8s v1.16.4高可用集群(主备模式)

    一.部署环境 主机列表: 主机名 Centos版本 ip docker version flannel version Keepalived version 主机配置 备注 master01 7.6. ...

  7. Atitit opencv版本新特性attilax总结

    Atitit opencv版本新特性attilax总结 1.1. :OpenCV 3.0 发布,史上功能最全,速度最快的版1 1.2. 应用领域2 1.3. OPENCV2.4.3改进 2.4.2就有 ...

  8. Atitit. Atiposter 发帖机 新特性 poster new feature   v7 q39

    Atitit. Atiposter 发帖机 新特性 poster new feature   v7 q39 V8   重构iocutilV4,use def iocFact...jettyUtil V ...

  9. Java 8新特性终极指南

    目录结构 介绍 Java语言的新特性 2.1 Lambdas表达式与Functional接口 2.2 接口的默认与静态方法 2.3 方法引用 2.4 重复注解 2.5 更好的类型推测机制 2.6 扩展 ...

  10. HTML5和CSS3新特性一览

    HTML5 1.HTML5 新元素 HTML5提供了新的元素来创建更好的页面结构: 标签 描述 <article> 定义页面独立的内容区域. <aside> 定义页面的侧边栏内 ...

随机推荐

  1. 使用unity构建射击小游戏

    博客地址:https://www.cnblogs.com/zylyehuo/ 成果图 参考例程 www.manning.com/hocking 问题汇总 1.renderer.material 方法过 ...

  2. 如何让tcxGrid左边显示序号

    第一步: 设置cxgrid的属性, OptionsView.Indicator = True 第二步: 写OnCustomDrawIndicatorCell方法 procedure TForm1.cx ...

  3. 独立博客与秘密基地,以及对UI设计中拟物态的怀念

    小时候的秘密基地 哪个人小的时候不想有一个"秘密基地"呢?后来人长大了,心里还有这个小欲望,想有一块属于自己的空间,可以自由装饰,可以藏喜欢的东西,不受社会道德约束,不受规则铁蹄践 ...

  4. 什么是MIME类型-基础知识补全

    MIME类型(Multipurpose Internet Mail Extensions,多用途互联网邮件扩展)是一种标准,用于标识互联网上传输的文件类型.它最初是为电子邮件设计的,后来被广泛应用于W ...

  5. WCHNET_SocketSend返回0x11原因及解决方法

    问题描述: TCPCLIENT模式使用WCHNET_SocketSend发送有概率会返回0x11 按wchnet.h定义为内存溢出错误. 异常分析: 通过WCHNET_QueryUnack查看,发现异 ...

  6. .net WorkFlow 流程传阅

    WikeFlow官网:www.wikesoft.com WikeFlow学习版演示地址:workflow.wikesoft.com WikeFlow学习版源代码下载:https://gitee.com ...

  7. 🎀chrome-截图录屏插件-Awesome Screenshot

    简介 Awesome Screenshot 截图录屏是一款浏览器扩展程序,它可以帮助用户进行网页截图.编辑图片以及录制屏幕视频 版本 4.4.22 功能 截图:可以截取整个网页(即使是需要滚动才能看到 ...

  8. Hystrix两种隔离方式对比

    ​在微服务架构中,我们不可避免的与Hystrix打交道,最近在面试过程中,也总是被问到Hystrix两种熔断方式的区别,今天,就给大家做个小结. 首先,Hystrix熔断方式主要有两种: 线程池隔离 ...

  9. Asp.net mvc基础(十四)Entity Framework

    一.EntityFramework介绍 1.ORM:Object Relation Mapping,用操作对象的方式来操作数据库 2.ORM工具有很多,其中Dapper.PetaPoco.NHiber ...

  10. 2025dsfz集训Day12: 斜率优化DP

    Day12:斜率优化DP 一次函数与斜率 斜率:表示一个直线倾斜程度.定义为和正方向水平轴的夹角的正切值. 经过两个点 \((x1, y1)\) 和 \((x2, y2)\) 的直线的斜率为 \(\f ...