如何实现一个K8S DevicePlugin?
什么是device plugin
k8s允许限制容器对资源的使用,比如CPU和内存,并以此作为调度的依据。
当其他非官方支持的设备类型需要参与到k8s的工作流程中时,就需要实现一个device plugin。
Kubernetes提供了一个设备插件框架,你可以用它来将系统硬件资源发布到Kubelet。供应商可以实现设备插件,由你手动部署或作为 DaemonSet 来部署,而不必定制 Kubernetes 本身的代码。
目标设备包括 GPU、高性能 NIC、FPGA、 InfiniBand 适配器以及其他类似的、可能需要特定于供应商的初始化和设置的计算资源。
更多云原生、K8S相关文章请点击【专栏】查看!
发现插件
一个新的device plugin是如何被kubelet发现的?
device plugin通过gRPC的方式与kubelet通信,kubelet实现了Register接口,用于注册插件。
service Registration {rpc Register(RegisterRequest) returns (Empty) {}
}
通过这个接口, 向kubelet提交当前插件的信息,包括插件的名称、版本、socket路径等。
已注册的插件信息并不会被持久化下来, 也就是说当kubelet重启后,插件需要重新调用
Register方法。kuelet重启时会删除插件的socket文件, 插件通过监听socket文件的方式来感知kubelet的重启并重新注册。
成功注册后,设备插件就向 kubelet 发送它所管理的设备列表,然后 kubelet 负责将这些资源发布到 API 服务器,作为 kubelet 节点状态更新的一部分。
当插件注册成功后, 根据插件中的配置与定义, 可能会有类似下面的pod配置以使用插件中的资源。
apiVersion: v1
kind: Pod
metadata:name: demo-pod
spec:containers:- name: demo-container-1image: registry.k8s.io/pause:2.0resources:limits:hardware-vendor.example/foo: 2
#
# 这个 pod 需要两个 hardware-vendor.example/foo 设备
# 而且只能够调度到满足需求的节点上
#
# 如果该节点中有 2 个以上的设备可用,其余的可供其他 Pod 使用

AMD GPU插件源码解析
插件的实现并不复杂, 只需要实现几个接口函数即可。
service DevicePlugin {// GetDevicePluginOptions 返回与设备管理器沟通的选项。// kuelet 在每次方法调用前都会调用这个方法,来获取可用的设备插件选项。rpc GetDevicePluginOptions(Empty) returns (DevicePluginOptions) {}// ListAndWatch 返回 Device 列表构成的数据流。// 当 Device 状态发生变化或者 Device 消失时,ListAndWatch会返回新的列表。rpc ListAndWatch(Empty) returns (stream ListAndWatchResponse) {}// Allocate 在容器创建期间调用,这样设备插件可以运行一些特定于设备的操作,// 并告诉 kubelet 如何令 Device 可在容器中访问的所需执行的具体步骤rpc Allocate(AllocateRequest) returns (AllocateResponse) {}// GetPreferredAllocation 从一组可用的设备中返回一些优选的设备用来分配,// 所返回的优选分配结果不一定会是设备管理器的最终分配方案。// 此接口的设计仅是为了让设备管理器能够在可能的情况下做出更有意义的决定。rpc GetPreferredAllocation(PreferredAllocationRequest) returns (PreferredAllocationResponse) {}// PreStartContainer 在设备插件注册阶段根据需要被调用,调用发生在容器启动之前。// 在将设备提供给容器使用之前,设备插件可以运行一些诸如重置设备之类的特定于具体设备的操作,rpc PreStartContainer(PreStartContainerRequest) returns (PreStartContainerResponse) {}
}
以下源码解析以AMD GPU插件为例。
代码版本 0.12.0
仓库地址 https://github.com/ROCm/k8s-device-plugin
源码解析
插件启动流程
AMD GPU插件的框架,是使用的"github.com/kubevirt/device-plugin-manager/pkg/dpm"这个包。
AMD的插件确实实现的很粗糙, 这里我们只用它分析实现一个插件需要做什么。
程序启动时实例化Manager对象, 并调用Run方法。
func main() {// ...// Lister用于传递心跳与资源更新l := Lister{ResUpdateChan: make(chan dpm.PluginNameList),Heartbeat: make(chan bool),}manager := dpm.NewManager(&l)// ...// 启动管理器manager.Run()
}
Run方法中启动了gRPC服务, 并注册了AMD GPU插件。
func (dpm *Manager) Run() {// ...// 监听socket文件变化(kubelet会在重启时删除)fsWatcher, _ := fsnotify.NewWatcher()defer fsWatcher.Close()// DevicePluginPath = "/var/lib/kubelet/device-plugins/"fsWatcher.Add(pluginapi.DevicePluginPath)// 启动插件监听方法, // 实际是将上面传入Liste.ResUpdateChan的数据转发到这个chan中pluginsCh := make(chan PluginNameList)defer close(pluginsCh)go dpm.lister.Discover(pluginsCh)
HandleSignals:for {select {case newPluginsList := <-pluginsCh:// 创建新的插件服务, 并启动服务dpm.handleNewPlugins(pluginMap, newPluginsList)case event := <-fsWatcher.Events:if event.Name == pluginapi.KubeletSocket {// kubelet重启时, 重新注册插件if event.Op&fsnotify.Create == fsnotify.Create {dpm.startPluginServers(pluginMap)}if event.Op&fsnotify.Remove == fsnotify.Remove {dpm.stopPluginServers(pluginMap)}}case s := <-signalCh:switch s {case syscall.SIGTERM, syscall.SIGQUIT, syscall.SIGINT:// 优雅退出dpm.stopPlugins(pluginMap)break HandleSignals}}}
}
创建插件服务会返回一个devicePlugin对象:
// dpm.handleNewPlugins(pluginMap, newPluginsList) 最终会调用这个方法
func newDevicePlugin(resourceNamespace string, pluginName string, devicePluginImpl PluginInterface) devicePlugin {return devicePlugin{DevicePluginImpl: devicePluginImpl,// DevicePluginPath = "/var/lib/kubelet/device-plugins/"// resourceNamespace = "amd.com"Socket: pluginapi.DevicePluginPath + resourceNamespace + "_" + pluginName,ResourceName: resourceNamespace + "/" + pluginName,Name: pluginName,Starting: &sync.Mutex{},}
}
type devicePlugin struct {// 实现的deviceplugin serverDevicePluginImpl PluginInterfaceResourceName stringName string// socket文件路径Socket stringServer *grpc.ServerRunning boolStarting *sync.Mutex
}
启动服务最终会由StartServer这个方法来完成。
func (dpi *devicePlugin) StartServer() error {// ...if dpi.Running {return nil}// 启动grpc服务err := dpi.serve()if err != nil {return err}// 调用Register方法向kubelet注册插件err = dpi.register()if err != nil {dpi.StopServer()return err}dpi.Running = truereturn nil
}
func (dpi *devicePlugin) serve() error {// ...// 可以看见是以socket文件启动的grpc服务sock, err := net.Listen("unix", dpi.Socket)if err != nil {glog.Errorf("%s: Failed to setup a DPI gRPC server: %s", dpi.Name, err)return err}dpi.Server = grpc.NewServer([]grpc.ServerOption{}...)pluginapi.RegisterDevicePluginServer(dpi.Server, dpi.DevicePluginImpl)go dpi.Server.Serve(sock)// ...return nil
}
func (dpi *devicePlugin) register() error {// KubeletSocket = DevicePluginPath + "kubelet.sock"// "/var/lib/kubelet/device-plugins/kubelet.sock"// 与kubelet通信conn, err := grpc.Dial(pluginapi.KubeletSocket, grpc.WithInsecure(),grpc.WithDialer(func(addr string, timeout time.Duration) (net.Conn, error) {return net.DialTimeout("unix", addr, timeout)}))defer conn.Close()client := pluginapi.NewRegistrationClient(conn)// 向kubelet注册插件reqt := &pluginapi.RegisterRequest{Version: pluginapi.Version,Endpoint: path.Base(dpi.Socket),ResourceName: dpi.ResourceName,Options: options,}_, err = client.Register(context.Background(), reqt)// ...return nil
}
socket文件默认会放在/var/lib/kubelet/device-plugins目录下, 所以当以daemonset的方式部署插件时,需要将这个目录挂载到容器中。
服务实现
AMD GPU插件只实现了两个关键方法(因为不同设备插件的实现都不一样,所以这里不展开):
- ListAndWatch
- Allocate
所以它的GetDevicePluginOptions方法返回的是一个空结构体
func (p *Plugin) GetDevicePluginOptions(ctx context.Context, e *pluginapi.Empty) (*pluginapi.DevicePluginOptions, error) {return &pluginapi.DevicePluginOptions{}, nil
}
type DevicePluginOptions struct {// 是否需要调用 PreStartContainer 方法PreStartRequired bool `protobuf:"varint,1,opt,name=pre_start_required,json=preStartRequired,proto3" json:"pre_start_required,omitempty"`// 是否需要调用 GetPreferredAllocation 方法GetPreferredAllocationAvailable bool `protobuf:"varint,2,opt,name=get_preferred_allocation_available,json=getPreferredAllocationAvailable,proto3" json:"get_preferred_allocation_available,omitempty"`
}
服务部署
设备插件可以作为节点操作系统的软件包来部署、作为 DaemonSet 来部署或者手动部署。
如果你将设备插件部署为 DaemonSet, /var/lib/kubelet/device-plugins 目录必须要在插件的 PodSpec 中声明作为 卷(Volume)被挂载到插件中。
实现一个设备插件
- 实现一个虚假设备, 用于测试插件。(可选)
- 实现
DevicePlugin接口。 我们可以仅实现ListAndWatch和Allocate两个关键方法。 - 注册
gRPC服务, 并向kubelet注册插件。 - 监听
kubelet的socket文件变化, 重新注册插件。
代码实现
待补充…
相关文章:
如何实现一个K8S DevicePlugin?
什么是device plugin k8s允许限制容器对资源的使用,比如CPU和内存,并以此作为调度的依据。 当其他非官方支持的设备类型需要参与到k8s的工作流程中时,就需要实现一个device plugin。 Kubernetes提供了一个设备插件框架,你可以用…...
Android LruCache源码分析
文章目录 Android LruCache源码分析概述LruCache和LinkedHashMap关系源码分析属性写入数据读取数据删除缓存 Android LruCache源码分析 概述 LruCache(Least Recently Used Cache,最近最少使用缓存)是 Android 中的一种缓存机制。 根据数据…...
如何使用Inno Setup制作Unity构建程序的Windows安装程序
1. 准备 (1)准备好Unity构建的程序集合 必须包括: Data文件夹(xxx_Data) Mono文件夹(MonoBleedingEdge) 打包的应用程序文件(xxx.exe) Unity播放器dll文件ÿ…...
linux 面试题
1.linux操作系统的常用指令可以详细说下吗,平常哪些用的比较多 文件目录操作命令: ls cd more cat tail mkdir touch rm rmdir 拷贝复制: cp mv 打包解包压缩解压: tar -z 解亚压缩 -c 打包 -x 解包 -v 显示过程 -f 指定文件名 文本编辑: vi vim 查找: find 查找文件 gre…...
嵌入式中逻辑分析仪基本操作方法
前期准备 1.一块能触摸的屏对应的主板机 2.逻辑分析仪对应的软件工具 3.对应的拓展板 4.确定拓展板的引脚分布情况 第一步:逻辑分析仪j基本操作 1.数据捕捉需要先进行对应软件安装,并按照需求进行配置 2.这里以A20为例:此手机使用显示驱动芯片CST148,触摸屏分辨…...
ONLYOFFICE 桌面编辑器 v8.0 更新内容详细攻略
文章目录 引言PDF 表单RTL 支持电子表格中的新增功能Moodle 集成用密码保护 PDF 文件从“开始”菜单快速创建文档本地界面主题下载安装桌面编辑工具总结 引言 官网链接: ONLYOFFICE 官方网址 ONLYOFFICE 桌面编辑器是一款免费的文档处理软件,适用于 Li…...
2024-2-22 作业
作业要求: 复习前面知识点(指针、结构体、函数)整理思维导图顺序表(按位置插入、按位置删除和去重、重新写)理解链表的代码,尝试写一下链表的尾插和输出 1.复习前面知识点(指针、结构体、函数) 2.整理思维导图 3.顺序表(按位置插入、按位置删除和去重、…...
2.1 RK3399项目开发实录-升级固件介绍(物联技术666)
1. 介绍 1.1. 前言 AIO-3399J 出厂默认安装Android操作系统,如果用户要运行其他操作系统,需要使用对应的固件烧写到主板。 AIO-3399J 有灵活的启动方式。一般情况下,除非硬件损坏,AIO-3399J 开发板是不会变砖的。 如果在升级过…...
Uniapp + VUE3.0 实现双向滑块视频裁剪效果
效果图 <template><view v-if"info" class"all"><video:src"info.videoUrl"class"video" id"video" :controls"true" object-fit"fill" :show-fullscreen-btn"false"play-btn…...
【算法小讲堂】#1 贪心算法
引入——关于贪心算法 我们先来做一个小游戏——现在假设自己是一个小偷,桌上有一些物品,包括一台iPhone15、一个充电宝、一个眼罩和一个溜溜梅。此时,你听说警察即将到来,那么你会先带走哪个东西呢? 一般来讲…...
判断当前shell版本
查看$SHELL环境变量: echo $SHELL输出的结果将是当前使用的shell的路径。例如,如果输出为 /bin/bash,则表示当前使用的是Bash shell。 查看ps命令输出: ps -p $$上述命令将显示当前终端进程的信息,其中 $$ 代表当前进…...
如何实现两个电脑之间通过以太网(网线)实现文件互传
如何实现两个电脑之间通过以太网(网线)实现文件互传 本帖目的:介绍如何通过以太网(网线)连接两台电脑,通过文件夹共享的方式,实现两台电脑之间的文件互传。 本帖以笔者实际工作上遇到的场景为例…...
Jenkins 中部署Nodejs插件并使用,并构建前端项目(3)
遇到多个版本nodeJS需要构建的时候 1、第一种就是一个配置安装,然后进行选中配置 2、第二种就是插件:nvm-wrapper,我们还是选用NodeJS插件: (1)可以加载任意npmrc文件; (2&#x…...
VUE为什么有的属性要加冒号
<el-menu-item :index "/item.menuClick" v-for"(item,i) in menu"><i class"item.menuIcon" ></i><span slot"title">{{item.menuName}}</span></el-menu-item>不加不行 加了好像是吧整体作为…...
微信小程序 --- wx.request网络请求封装
网络请求封装 网络请求模块难度较大,如果学习起来感觉吃力,可以直接学习 [请求封装-使用 npm 包发送请求] 以后的模块 01. 为什么要封装 wx.request 小程序大多数 API 都是异步 API,如 wx.request(),wx.login() 等。这类 API 接口…...
通义千问Qwen-7B-Chat Windows本地部署教程-详细认真版
通义千问本地部署教程🚀 本专栏的第四弹,在实现了联网调用通义千问模型进行多轮对话,流式输出,以及结合LangChain实现自建知识库之后,开始准备考虑实现对大模型进行本地部署,网上找不到看着比较舒服的教程&…...
探索C语言位段的秘密
位段 1. 什么是位段2. 位段的内存分配3. 位段的跨平台问题4. 位段的应用4. 使用位段的注意事项 1. 什么是位段 我们使用结构体实现位段,位段的声明和结构体是类似的,有两个不同: 位段的成员必须是int,unsigned int,或…...
数据库-数据库设计-社交关系
佛 每有一个新方案,就要考虑有什么影响增删改查可扩展性 MySQL 根据ER图设计表 create table follow(id bigint unsigned not null auto_increment comment 主键,gmt_create datetime null default current_timestamp,gmt_modified null default current_timest…...
YOLO算法改进Backbone系列之:EfficientViT
EfficientViT: Memory Effificient Vision Transformer with Cascaded Group Attention 摘要:视觉transformer由于其高模型能力而取得了巨大的成功。然而,它们卓越的性能伴随着沉重的计算成本,这使得它们不适合实时应用。在这篇论文中&#x…...
JANGOW: 1.0.1
kali:192.168.223.128 主机发现 nmap -sP 192.168.223.0/24 端口扫描 nmap -p- 192.168.223.154 开启了21 80端口 web看一下,有个busque.php参数是buscar,但是不知道输入什么,尝试文件包含失败 扫描目录 dirsearch -u http://192.168.223.154 dirse…...
基于MCP协议构建AI代码安全沙盒:原理、实现与工程实践
1. 项目概述:一个为AI模型安全执行代码的“沙盒”工具最近在折腾AI应用开发,特别是那些能调用外部工具、执行代码的智能体(Agent)时,一个绕不开的核心问题就是:如何让AI安全地运行它生成的代码?…...
SpringBoot快速入门指南
Spring Boot 是一个基于 Spring 框架的“约定优于配置”的快速应用开发框架,旨在简化基于 Spring 的应用初始搭建和开发过程。它通过自动配置、起步依赖和嵌入式容器等特性,使开发者能够快速创建独立的、生产级别的 Spring 应用程序。 一、 核心特性与快…...
LLM长文本处理实战:模块化分割策略与向量化预处理指南
1. 项目概述:一个为LLM打造的文本处理中心如果你和我一样,经常和大型语言模型打交道,无论是用它来总结文档、分析代码,还是处理客服对话,那你肯定遇到过这个痛点:喂给模型的文本太长了怎么办?模…...
AI编程助手集成DRPC技能包:无缝查询区块链数据的实践指南
1. 项目概述:为AI编程助手解锁区块链数据能力 如果你正在使用Claude Code、Cursor这类AI编程助手,并且需要频繁查询区块链上的数据——比如检查钱包余额、追踪交易状态、读取智能合约信息,那么你很可能已经厌倦了在代码编辑器和区块链浏览器之…...
Kubernetes配置管理神器Monokle:可视化IDE提升YAML开发效率
1. 项目概述:一个被低估的Kubernetes配置管理神器如果你和我一样,每天都在和成堆的YAML文件、复杂的Kubernetes资源关系以及让人头疼的配置漂移问题打交道,那你一定理解那种在终端、IDE和Dashboard之间反复横跳的疲惫感。几年前,当…...
记一次ubuntu 22.04安装旧版 MongoDB 4.2
22.04版本比较新,由于mongodb 2.4太老了,安装会遇到问题。特此记录1. 下载mongodb包wget https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-ubuntu1804-4.2.24.tgz2. 解压到当前目录sudo tar -zxvf mongodb-linux-x86_64-ubuntu1804-4.2.24.tgz3.…...
UWB-IMU、UWB定位对比研究(Matlab代码实现)
💥💥💞💞欢迎来到本博客❤️❤️💥💥 🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。 ⛳️座右铭&a…...
图解人工智能(11)让人惊讶的AI
人工智能已经融入到我们的生活之中,如便捷的刷脸支付,帮我们扫地的机器人。想一想,你身边还有哪些有趣的人工智能设备?以一种设备为例,搜索它的相关信息,看它为我们的生活带来了哪些便利。开放讨论题&#…...
喜马拉雅音频本地化实战:绕过xm格式,直接获取mp3文件的两种方法对比
喜马拉雅音频本地化实战:两种高效获取MP3文件的技术方案深度评测 作为国内领先的音频分享平台,喜马拉雅拥有海量优质内容,但其特有的XM格式却给用户跨平台使用带来了困扰。许多技术爱好者尝试过各种转换工具,却发现市面上几乎没有…...
工业 AI 赋能采购:智能供应商匹配重构招标流程
Q1:传统企业采购招标,供应商对接与筛选存在哪些固有痛点?传统工业企业采购招标模式高度依赖人工经验,存在三大核心痛点:供应商资源固化:每次招标都需从零手动联络供应商,仅依靠采购人员个人记忆…...
