如何实现一个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-1
      image: registry.k8s.io/pause:2.0
      resources:
        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 server
	DevicePluginImpl PluginInterface
	ResourceName     string
	Name             string
	// socket文件路径
	Socket           string
	Server           *grpc.Server
	Running          bool
	Starting         *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 = true

	return 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)被挂载到插件中。

实现一个设备插件

  1. 实现一个虚假设备, 用于测试插件。(可选)
  2. 实现DevicePlugin接口。 我们可以仅实现ListAndWatchAllocate两个关键方法。
  3. 注册gRPC服务, 并向kubelet注册插件。
  4. 监听kubelet的socket文件变化, 重新注册插件。

代码实现

待补充…

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/399727.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

一文读懂函数式接口、Lambda表达式、Stream

文章目录 前言版本函数式接口定义特点使用 Lambda表达式主要场景用法无参写法有参写法 Lambda 表达式的基础&#xff1a;函数式接口 类型推断自定义函数接口使用 Lambda 表达式底层实现JDK7JDK8 this 的含义JDK7JDK8 Stream特点操作Stream 流创建中间操作和终端操中间操作无状…

vue.js el-tooltip根据文字长度控制是否提示toolTip

一、需求&#xff1a;如何判断当前文本文字是否超出文本长度&#xff0c;是否需要出现提示toolTip。效果图如下&#xff1a; 二、实现&#xff1a; 1、表格字段鼠标放置el-popover出现 “引用主题” 的具体内容&#xff1b; <!-- 表格字段&#xff1a;引用主题 --> <…

006 矢量数据属性表的使用和关联

1 空间属性关系 1.1 数据导入和图层组合 读取数据 除了使用关键项的连接之外&#xff0c;还有一种根据属性组合中的空间位置关系进行组合的方法。 在本节中&#xff0c;我们将介绍一种方法&#xff0c;用于将空间关系中的多边形数据属性与任意点数据属性相结合。 我们使用的是…

文件上传漏洞--Upload-labs--Pass11--(GET)00绕过

一、环境准备&#xff1a; php版本&#xff1a;推荐 php5.2.17&#xff08;官方推荐版本&#xff09;。小于php5.3.4也可以&#xff0c;但是要在 php.ini 配置文件中手动将 magic_quotes_gpc 的状态改为 Off。 magic_quotes_gpc的作用是对 get请求、post请求、cookie...传入的…

DIcom调试Planar configuration

最近和CBCT组同事调dicom图像 这边得图像模块老不兼容对方得dicom文件。 vtk兼容&#xff0c;自己写得原生解析不兼容。 给对方调好了格式&#xff0c;下次生成文件还会有错。 简单记录下&#xff0c;日后备查。 今天对方又加了 个字段&#xff1a;Planar configuration 查…

【开源】JAVA+Vue.js实现高校学生管理系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 学生管理模块2.2 学院课程模块2.3 学生选课模块2.4 成绩管理模块 三、系统设计3.1 用例设计3.2 数据库设计3.2.1 学生表3.2.2 学院课程表3.2.3 学生选课表3.2.4 学生成绩表 四、系统展示五、核心代码5.1 查询课程5.2 新…

FPS游戏漫谈弱网环境时延优化

游戏在弱网情况下会变得体验很差&#xff0c;玩家的直观感受就是我的操作怎么没有反应&#xff0c;整个游戏世界都是一卡一顿的。这个就是因为网络问题导致了游戏体验变差。 那什么是弱网环境&#xff1f;弱网环境就是指网络不好的环境&#xff0c;尤其是移动网络下&#xff0…

Java毕业设计-基于springboot的人才招聘管理系统-第68期

获取源码资料&#xff0c;请移步从戎源码网&#xff1a;从戎源码网_专业的计算机毕业设计网站 项目介绍 基于springboot的人才招聘管理系统&#xff1a;前端jquery、easyui&#xff0c;后端 maven、springmvc、spring、jpa、hibernate&#xff0c;集成职位浏览、我的简历、投…

web前端宝藏题库,适用于0-3年前端工程师...

前些天意外认识了一个前端大佬&#xff0c;某大厂在职&#xff0c;我怕打扰就没敢多聊&#xff0c;没想到大佬挺平易近人&#xff0c;很亲切的给我出谋划策&#xff0c;还给了我一套面试题&#xff0c;难度不是很高&#xff0c;但是胜在全面十分契合我现在的状况&#xff0c;所…

在VsCode中通过Cookie登录LeetCode

在vscode中配置好leetcode之后&#xff0c;一般最常用的就是通过cookie登录leetcode ; 首先点击sign in &#xff0c; 然后选择最下面的 &#xff0c; LeetCode Cookie ! 然后输入username(也就是你的lc用户名) 或者 你leetcode绑定的邮箱 ; 输入完成之后 ; 就是要你输入你的l…

UE5 C++ 创建可缩放的相机

一.要将相机设置在Pawn类里 1.在MyPawn头文件里&#xff0c;加上摇臂和相机组件 #include "GameFramework/SpringArmComponent.h" #include "Camera/CameraComponent.h" 2.在Pawm里声明SceneComponet&#xff0c;SpringArmComponent,CameraComponent组件…

6.【架构师成长之路】职场新人:维护一张能力图谱

文章目录 导言一、能力图谱就是技能体系1、时刻提醒你&#xff0c;你有一个目标2、你可以知道靠近目标的具体方式3、你会变得更加自信 二、周期性review自己的能力图谱1、review能力项进度2、review能力项完整度3、固定周期review 本文总结说明 导言 上两篇文章我们讲了&#…

C语言中关于#include的一些小知识

写代码的过程中&#xff0c;因为手误&#xff0c;重复包含了头文件 可以看到没有报错 如果是你自己编写的头文件&#xff0c;那么如果没加唯一包含标识的话&#xff0c;那么编译器会编译报错的。如果是系统自带的头文件&#xff0c;由于其每个头文件都加了特殊标识&#xff0c…

【动态规划】【矩阵快速幂】LeetCode2851. 字符串转换

作者推荐 【深度优先搜索】【树】【有向图】【推荐】685. 冗余连接 II 涉及知识点 【矩阵快速幂】封装类及测试用例及样例 LeetCode 2851. 字符串转换 给你两个长度都为 n 的字符串 s 和 t 。你可以对字符串 s 执行以下操作&#xff1a; 将 s 长度为 l &#xff08;0 <…

安达发|APS排产软件的机台产线任务甘特图功能详解

在现代制造业中&#xff0c;高级计划与排产是制造业运营的关键环节。为了提高生产效率、降低成本并确保产品质量&#xff0c;企业需要对生产过程进行精细化管理。APS&#xff08;高级计划与排产&#xff09;系统作为一种先进的生产计划和调度工具&#xff0c;可以帮助企业实现这…

在计算机上设置和使用 KVM

为了使用 gem5 的 KVMCPU 来快进你的模拟&#xff0c;你必须有一个 KVM 兼容的处理器并且在你的机器上安装了 KVM。本页将引导您完成在计算机上启用 KVM 并将其与 gem5 一起使用的过程。 注意&#xff1a;以下教程假设 X86 Linux 主机。本教程的各个部分可能不适用于其他体系结…

IBM V5000存储更换控制器及电源模块

LED故障状态 后面板故障状态 系统内电源模块报错信息(可安全卸下状态为"是"&#xff0c;此时可直接热拔插) 控制器报错信息&#xff08;当前已是脱机状态可直接拔插&#xff0c;该型号控制器不需要更换缓存可直接热拔插更换&#xff09; 更换故障备件应先核对新旧备件…

CTFHub技能树web之RCE(二)

第五题&#xff1a;远程包含 根据题目&#xff0c;使用远程包含进行 打开phpinfo&#xff0c;可以看到allow_url_fopen和allow_url_include都是On&#xff0c;因此可以使用php://input&#xff0c;由于代码会检查file中的内容&#xff0c;因此不能够使用php://filter包含文件&a…

C++正则表达式笔记

最近翻了翻正则表达式的一些资料&#xff0c;做个记录。 1、微软官方 <regex> 函数 | Microsoft Learn 2、正则表达式语法简介 正则表达式语法简介 - 简书 3、正则表达式基础语法大全 正则表达式基础语法大全_正则表达式语法大全-CSDN博客 4、练习 &#xff08;1…

ffmpeg TS复用代码详解——mpegtsenc.c

一、mpegtsenc.c 整体架构 二、主要函数 mpegts_write_pes(AVFormatContext *s, AVStream *st, const uint8_t *payload, int payload_size, int64_t pts, int64_t dts)这个函数就是TS打包的主函数了&#xff0c;这个函数主要功能就是把一帧数据拆分成188字节的TS包&#xff0…