本文主要分享一个 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. nginx配置2个不同端口的应用

    如何配置nginx,在同一台服务器上,部署2个不同端口的web应用? 1,利用Django框架搭建的web应用,默认端口是8000: 2,利用Flask框架搭建的web应用,默认端口是5000: 第一 ...

  2. VMware ESXi系统

    esxi全称"VMware ESXi",是可直接安装在物理服务器上的强大的裸机管理系统,是一款虚拟软件,不需安装其他操作系统,是VMware服务器虚拟化的基础.通过直接访问并控制底 ...

  3. 面试官:工作中优化MySQL的手段有哪些?

    MySQL 是面试中必问的模块,而 MySQL 中的优化内容又是常见的面试题,所以本文来看"工作中优化MySQL的手段有哪些?". 工作中常见的 MySQL 优化手段分为以下五大类 ...

  4. AI穿上身:苹果手表如何改变你的生活?

    楔子:一个普通理工男的科技启示录 我是张三,一个标准的90后理工男.在这个日新月异的科技时代,我习惯用精密的逻辑和近乎机械的效率来审视世界.每天早上6点45分准时起床,每一分钟都被精确地规划,生活就像 ...

  5. 主存的扩展及其与CPU的连接——字扩展

    一块芯片的容量为\(2^{18}B\),而该CPU需要的容量为:\(2^{地址总线位宽}\)=\(2^{21}B\),所以需要8片该芯片来扩展. 由于CPU由21个地址引脚,芯片只有18个地址引脚,C ...

  6. arthas安装和简单使用

    介绍 Arthas 是一款线上监控诊断产品,通过全局视角实时查看应用 load.内存.gc.线程的状态信息,并能在不修改应用代码的情况下,对业务问题进行诊断,包括查看方法调用的出入参.异常,监测方法执 ...

  7. .NET 原生驾驭 AI 新基建实战系列(三):Chroma ── 轻松构建智能应用的向量数据库

    在人工智能AI和机器学习ML迅猛发展的今天,数据的存储和检索需求发生了巨大变化.传统的数据库擅长处理结构化数据,但在面对高维向量数据时往往力不从心.向量数据库作为一种新兴技术,专为AI应用设计,能够高 ...

  8. 大模型流式调用规范(SSE)

    随着大语言模型的广泛应用,如何高效地与其进行接口调用成为一个关键问题.传统的请求-响应模式在面对大模型生成大量文本时存在响应延迟高.用户体验差等问题.流式输出(Streaming)是解决该问题的重要手 ...

  9. Java 中的强引用、软引用、弱引用和虚引用分别是什么?

    Java 中的引用类型:强引用.软引用.弱引用和虚引用 Java 中的引用类型主要分为 强引用.软引用.弱引用 和 虚引用,它们对对象的生命周期和垃圾回收(GC)行为产生不同的影响. 1. 强引用(S ...

  10. Asp.net core 少走弯路系列教程(七)WebApi 学习

    前言 新人学习成本很高,网络上太多的名词和框架,全部学习会浪费大量的时间和精力. 新手缺乏学习内容的辨别能力,本系列文章为新手过滤掉不适合的学习内容(比如多线程等等),让新手少走弯路直通罗马. 作者认 ...