本文主要分享一个 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. Wireshark 的过滤器类型

    Wireshark 是一个强大的网络协议分析工具,支持多种类型的过滤器来帮助用户捕获和分析网络流量. 根据使用场景和功能,Wireshark 的过滤器可以分为以下两类: 1. 捕获过滤器(Captur ...

  2. VulnHub2018_DeRPnStiNK靶机渗透练习

    据说该靶机有四个flag 扫描 扫描附近主机arp-scan -l 扫主目录 扫端口 nmap -sS -sV -n -T4 -p- 192.168.xx.xx 结果如下 Starting Nmap ...

  3. Windows 提权-服务_弱注册表权限

    本文通过 Google 翻译 Weak Registry Key Permissions – Windows Privilege Escalation 这篇文章所产生,本人仅是对机器翻译中部分表达别扭 ...

  4. Delphi 执行一个外部程序,当外部程序结束后言主程序立即响应

    delphi 执行一个外部程序,当外部程序结束后言主程序立即响应 我们经常能看到360安全卫士进行windows系统升级时,执行windows升级程序,当升级程序执行完成后,360马上弹出提示框.这样 ...

  5. 【Java】枚举类和注解

    一.枚举类的使用 1. 枚举类的说明: 枚举类的理解:类的对象只有有限个,确定的.我们称此类为枚举类 当需要定义一组常量时,强烈建议使用枚举类 枚举类的实现: JDK 5.0以前需要自定义 JDK 5 ...

  6. nginx中的路径匹配规则详解(location规则)

    Nginx的路径匹配规则 Nginx的匹配规则用location指令来实现,Nginx 的location指令用于匹配请求的 URI(请求路径),并根据匹配结果执行特定的处理指令.location是实 ...

  7. centos7防火墙启动关闭

    1. 查看防火墙状态 systemctl status firewalld.service 2. 查看对外开放的端口号 firewall-cmd --list-ports 3. 添加端口号 firew ...

  8. windows 环境下vs code配置go mod 包管理进行开发,终于解决go mod 模式下可以编译运行,但引入包"github.com/gin-gonic/gin"的飘红黄波浪警告

    最近在积极的转入go后端开发,学习gin的时候,能够编译运行,但是在improt github.com/gin-gonic/gin 波浪警告 当时忘记截图了,类似于这样的波浪警告 , 内容大概是&qu ...

  9. app自动化的三大等待

    app自动化的三大等待与web自动化的三大等待的代码脚本一样 一.硬性等待 硬性等待无论元素是否出现,都必须等待设置的时间再继续执行后面的代码. 使用简单,但是容易浪费时间.所以一般和隐式等待或显式等 ...

  10. PhpStorm - 本地动态调试-下载配置xdebug扩展

    PhpStorm - 本地动态调试-下载配置xdebug扩展 00x01 查看phpinfo <?php phpinfo(); 00x02下载扩展前查看Architecture 如果Archit ...