社区考虑了硬件的多样性,很难说把各种管理不同硬件资源的代码都合入K8s项目中,所以就试图提供统一的插件化方案来让用户(主要是设备提供商)在K8s上自定义这些资源的管控。
这个方案叫Device Manager,实际上是通过在K8s中内置Extended Resource和Device Plugin两个模块,来允许用户自己编写对应硬件资源的Device Plugin,最终串起硬件资源在集群级别的调度、以及在nodes上的实际绑定。
Extended Resource:
这个模块提供了自定义资源的扩展,用户可以把资源的名称和数量上报给API Server。Scheduler调度pod时,会根据该类资源的数量条件进行判断。
目前这类资源的delta必须为整数,未来可能允许浮点数。
Device Plugin:
Device Plugins其实是一些以Pod中容器或者bare-metal-mode运行的简单的gRPC servers。
这些servers会实现Device Manager定义的gRPC interface,在Kubelet中注册,让Kubelet通过gRPC去调用Device Plugin的ListAndWatch()和Allocate()方法。

ListAndWatch()方法帮助Kubelet discover和watch设备资源的变化;Allocate()方法允许Kubelet在创建使用该设备资源的容器时,告诉Device Plugin进行设备绑定容器的具体操作,并告诉Device Plugin使用的device、volume以及env配置。
Lifecycle
一开始,Device Plugin需要主动去提醒Kubelet的gRPC server,以告知它的存在。
Kubelet和Device Plugin的gRPC server通过host上的Unix Socket进行通信,比如/var/lib/kubelet/device-plugins/nvidiaGPU.sock。
具体的Device Plugin生命周期如下图所示,分为注册、发现、分配、停止四个阶段。

Registration
- 社区推荐的是以daemonset的方式去部署Device Plugin,i.e.
kubectl create -f nvidia.io/device-plugin.yml。Daemonset的容器会在所有nodes上run起来,看当前node上是否有可用的设备资源,如果没有就terminate掉(假设restartPolicy是OnFailure)。 - Device Plugin启动时,通过gRPC往
/var/lib/kubelet/device-plugins/kubelet.sock(Kubelet gRPC server的socket)发RegisterRequest,在Kubelet中注册,并提供Device Plugin的Unix Socket、API version、device name(ResourceName)。Kubelet会回复RegisterResponse,包含了可能触发的error,比如API version不支持或者已有同名的Device Plugin注册过。如果Kubelet没有回复error,Device Plugin才会开始运行自己的gRPC server。 - 同时,Kubelet也会将设备资源信息暴露到Node status中,设备资源会以Extended Resources的形式(
vendor-domain/vendor-device的格式,如nivida.com/gpu)在Apiserver上公布,后续Scheduler会利用该信息进行调度。
Discovery
- Device Plugin启动后,Kubelet会和它建立一个ListAndWatch的长连接。Device Plugin向Kubelet公告一个devices list,如果设备情况发生变化则会重发。
- 当某个设备的health check失败,就会通知Kubelet。如果该设备空闲,Kubelet会将其挪出可分配列表;如果该设备被某个Pod使用,Kubelet会将该kill掉该Pod。
- Device Plugin可以利用Kubelet的socket持续检查Kubelet的状态,在Kubelet重启时让插件也重启,向Kubelet重新注册。
Allocation
Scheduler从cache中选择资源满足的node;Node收到Add Pod,对Pod信息进行
admit()方法来判断是否可运行。判断中就包括了UpdatePluginResource()这个handler。该方案会调用devicePluginManager的Allocate()方法,对Kubelet缓存中记录的资源可用量进行判断和计算,并选定要使用的设备。确定创建容器时,Kubelet会调用Device Plugin的
Allocate()函数。Device Plugin被call后,根据request的device id,检查设备是否可用;可用的话会执行设备特定的命令,比如GPU cleanup、QRNG初始化等;并向Kubelet返回在容器创建时对设备的配置,包括
Env、Mounts、Devices;该配置会被记录在podDevices这个map中。Kubelet创建Pod的容器时,产生如下调用链,从
podDevices在获取容器使用的设备,组织成容器运行时的参数opts,传递到container runtime中,最终run container时会用opts到。比如GPU容器,会在opts中增加--devices的参数指定,让容器挂上需要的设备。1
2GenerateRunContainerOptions() -> containerManager.GetResources()
-> devicePluginManager.GetDeviceRunContainerOptions() -> deviceRunContainerOptions()
Stop
- 这里的Stop指Device Plugin自身停止时想做的清理工作,比如清理Socket、unload driver之类的事情,即对应Device Plugin生命周期的Stop,而非Kubelet控制的Pod生命周期。
- 至于设备失效,Device Plugin会通过
ListAndWatchgRPC stream来提示Kubelet,Kubelet会据此让Pod失效,比如Kill掉Pod。
参考链接:
https://blog.nowcoder.net/n/2be3d64786a54c40901ba885babff860