kubevirt源码分析之谁分配了gpu_device(3)

发布于:2025-03-05 ⋅ 阅读:(36) ⋅ 点赞:(0)

目标

当一个launcher pod被创建时,它会请求资源 ,如下

    Requests:
      cpu:                                16
      devices.kubevirt.io/kvm:            1
      devices.kubevirt.io/tun:            1
      devices.kubevirt.io/vhost-net:      1
      ephemeral-storage:                  50M
      hugepages-2Mi:                      8Gi
      memory:                             1574961152
      nvidia.com/GA102_GEFORCE_RTX_3090:  2

并且会在pod的环境变量中写入请求的显卡的ID :

declare -x PCI_RESOURCE_NVIDIA_COM_GA102_GEFORCE_RTX_3090="0000:e1:00.0,0000:e1:00.1,0000:01:00.0,0000:01:00.1"

我们可以看到两个3090的显卡被请求了,那么这个调度就会进入到k8s的调度系统中,k8s要负责为这个pod分配两块3090出来。 那么现在问题来了:

  • k8s是如何知道的哪个节点有空闲显卡的。
  • 谁给pod的环境变量中写入了显卡的id
  • 为什么请求了两个显卡却有4个Id,很明显,第2、4个id应该是声卡的id

k8s是如何知道哪个节点有空闲显卡的

根据copilot的解释,k8s会有个device选择的过程,这个是由k8s来处理的。 这个我们暂时放下不表。

谁给Pod的环境变量写入了显卡的id

我们合理的猜想有应该有三个地方:
virt-controller的updateStatus
virt-handler
virt-launcher

virt-controller(不是)

根据流程,sync函数主要是进行pod的编辑、创建和启动:编辑初始化的yaml,创建pod,并且启动pod。 其中创建和启动可以理解为一个步骤。 这时候pod还没有根据k8s的调试到任一节点,所以我们认为updateStatus会去操作获取pod。

但这也有点不对,因为virt-controller是一个调度级别的程序,它没有办法获取到每个节点的pci信息。

virt-handler(不是)

我们到现在还没有了解到virt-handler最终的目标是什么,为什么要有virt-handler这个程序。

在这里插入图片描述
根据官方的流程图,我们可以看到virt-handler才是真正的主导启动的一个程序,难道不是virt-launcher?

经过研究源码后,至少virt-handler也不是。

virt-launcher(不是)

virt-launcher是最后的接受方,他并不处理这些东西

gpu-device-plugin(是)

最终,发现还是gpu-device-plugin这个程序在处理这些东西,在allocate时,会返回一个ContainerResponses,其中包含envs变量,应该是k8s会将它的返回交给pod

type ContainerAllocateResponse struct {
	// List of environment variable to be set in the container to access one of more devices.
	Envs map[string]string `protobuf:"bytes,1,rep,name=envs,proto3" json:"envs,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
	// Mounts for the container.
	Mounts []*Mount `protobuf:"bytes,2,rep,name=mounts,proto3" json:"mounts,omitempty"`
	// Devices for the container.
	Devices []*DeviceSpec `protobuf:"bytes,3,rep,name=devices,proto3" json:"devices,omitempty"`
	// Container annotations to pass to the container runtime
	Annotations map[string]string `protobuf:"bytes,4,rep,name=annotations,proto3" json:"annotations,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
	// CDI devices for the container.
	CDIDevices           []*CDIDevice `protobuf:"bytes,5,rep,name=cdi_devices,json=cdiDevices,proto3" json:"cdi_devices,omitempty"`
	XXX_NoUnkeyedLiteral struct{}     `json:"-"`
	XXX_sizecache        int32        `json:"-"`
}

那么最合理的流程就是:

  • 1、virt-controller负责生成pod,规定pod的各个属性。
  • 2、k8s接收到pod后,会根据pod的请求进行调度,比如去各个节点查看gpu的剩余,如果有剩余,则请求分配gpu。
  • 3、gpu-device-plugin接收到请求后,会返回一个带有env的response,其中env就是最后virt-launcher pod中有的环境变量。
  • 4、k8s接收到gpu-device-plugin的返回后,会将该信息加入到Pod的初始的环境变量中,最后再启动pod。
  • 5、virt-handler在发现pod创建信息后,会继续一系列的处理,而不是由virt-launcher直接进行创建了。 可以理解virt-handler+virt-launcher才是一整套的nova-compute。
  • 6、最后,由virt-launcher进行启动,是的,virt-launcher还是会进行启动的。 只不过是由virt-handler来触发启动这个程序。

virt-handler

大概有这么几个作用:
virt-handler遍历pods目录,看看哪个是有virt-launcher的socket的,如果有,就证明这是个主机的Pod。
从k8s中拿到vmi,判断vmi要做的事儿,比如开机、关机、迁移啥的,通过socket去通知virt-launcher,让它去执行要做的操作。

声卡id的问题

前面我们说过,vfio-manager在使用vfio驱动gpu的时候,实际上是会连同声卡也给驱动上的。

总结

gpu的分配是由gpu-device-plugin来分配的,在使用allocated