如何实现一个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…...
后进先出(LIFO)详解
LIFO 是 Last In, First Out 的缩写,中文译为后进先出。这是一种数据结构的工作原则,类似于一摞盘子或一叠书本: 最后放进去的元素最先出来 -想象往筒状容器里放盘子: (1)你放进的最后一个盘子(…...
生成xcframework
打包 XCFramework 的方法 XCFramework 是苹果推出的一种多平台二进制分发格式,可以包含多个架构和平台的代码。打包 XCFramework 通常用于分发库或框架。 使用 Xcode 命令行工具打包 通过 xcodebuild 命令可以打包 XCFramework。确保项目已经配置好需要支持的平台…...
Axios请求超时重发机制
Axios 超时重新请求实现方案 在 Axios 中实现超时重新请求可以通过以下几种方式: 1. 使用拦截器实现自动重试 import axios from axios;// 创建axios实例 const instance axios.create();// 设置超时时间 instance.defaults.timeout 5000;// 最大重试次数 cons…...
工业自动化时代的精准装配革新:迁移科技3D视觉系统如何重塑机器人定位装配
AI3D视觉的工业赋能者 迁移科技成立于2017年,作为行业领先的3D工业相机及视觉系统供应商,累计完成数亿元融资。其核心技术覆盖硬件设计、算法优化及软件集成,通过稳定、易用、高回报的AI3D视觉系统,为汽车、新能源、金属制造等行…...
Android 之 kotlin 语言学习笔记三(Kotlin-Java 互操作)
参考官方文档:https://developer.android.google.cn/kotlin/interop?hlzh-cn 一、Java(供 Kotlin 使用) 1、不得使用硬关键字 不要使用 Kotlin 的任何硬关键字作为方法的名称 或字段。允许使用 Kotlin 的软关键字、修饰符关键字和特殊标识…...
C++:多态机制详解
目录 一. 多态的概念 1.静态多态(编译时多态) 二.动态多态的定义及实现 1.多态的构成条件 2.虚函数 3.虚函数的重写/覆盖 4.虚函数重写的一些其他问题 1).协变 2).析构函数的重写 5.override 和 final关键字 1&#…...
C++.OpenGL (20/64)混合(Blending)
混合(Blending) 透明效果核心原理 #mermaid-svg-SWG0UzVfJms7Sm3e {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-SWG0UzVfJms7Sm3e .error-icon{fill:#552222;}#mermaid-svg-SWG0UzVfJms7Sm3e .error-text{fill…...
在Mathematica中实现Newton-Raphson迭代的收敛时间算法(一般三次多项式)
考察一般的三次多项式,以r为参数: p[z_, r_] : z^3 (r - 1) z - r; roots[r_] : z /. Solve[p[z, r] 0, z]; 此多项式的根为: 尽管看起来这个多项式是特殊的,其实一般的三次多项式都是可以通过线性变换化为这个形式…...
macOS 终端智能代理检测
🧠 终端智能代理检测:自动判断是否需要设置代理访问 GitHub 在开发中,使用 GitHub 是非常常见的需求。但有时候我们会发现某些命令失败、插件无法更新,例如: fatal: unable to access https://github.com/ohmyzsh/oh…...
快速排序算法改进:随机快排-荷兰国旗划分详解
随机快速排序-荷兰国旗划分算法详解 一、基础知识回顾1.1 快速排序简介1.2 荷兰国旗问题 二、随机快排 - 荷兰国旗划分原理2.1 随机化枢轴选择2.2 荷兰国旗划分过程2.3 结合随机快排与荷兰国旗划分 三、代码实现3.1 Python实现3.2 Java实现3.3 C实现 四、性能分析4.1 时间复杂度…...
