delta_lt_0

delta_lt_0

人生苦短 及时行乐

KubeVirt 虚拟机 GPU 直通

Expose NVIDIA GPU to Kubernetes#


k8s 默认提供给 Pod 的可使用资源有 Memory, CPU 等,并没有 GPU。 但 K8S 允许我们自己开发 Device Plugin 来暴露 Resource。 大致流程是这样:

image

按照 Device Plugin 文档中描述的方式去开发,就能在 k8s 中暴露出你要的资源分配给 Pod。

kubevirt-gpu-device-plugin 就是这样一个插件,它以 DaemonSet 方式部署,将 NVIDIA GPU 作为一种可分配资源暴露给 kubelet。 我们在集群中安装这个 nvidia-kubevirt-gpu-dp 之后就可以看到效果,如下 :

root@node01:~# kubectl describe node node01
Name:               node01
Roles:              worker
CreationTimestamp:  Thu, 24 Nov 2022 15:13:21 +0800
Addresses:
  InternalIP:  172.16.33.137
  Hostname:    node01
Capacity:
  cpu:                                       72
  ephemeral-storage:                         921300812Ki
  hugepages-1Gi:                             0
  hugepages-2Mi:                             0
  memory:                                    131564868Ki
  nvidia.com/TU106_GEFORCE_RTX_2060_REV__A:  4
  pods:                                      110
Allocatable:
  cpu:                                       71600m
  ephemeral-storage:                         921300812Ki
  hugepages-1Gi:                             0
  hugepages-2Mi:                             0
  memory:                                    127462015491
  nvidia.com/TU106_GEFORCE_RTX_2060_REV__A:  4
  pods:                                      110
...

上面的输出结果中,Capacity 中描述的是 node01 这个节点有 4 张 nvidia.com/TU106_GEFORCE_RTX_2060_REV__A 显卡,Allocatable 描述的是 node01 这个节点中有 4 张 nvidia.com/TU106_GEFORCE_RTX_2060_REV__A 显卡可用于分配给 Pod。

这个插件的 Pod 运行起来后 ,每个节点的 /var/lib/kubelet/device-plugins/ 目录下都会新增了一个名为 kubevirt-TU106_GEFORCE_RTX_2060_REV__A.sock= 的 socket 文件。

root@node01:~# ll /var/lib/kubelet/device-plugins/
total 44
drwxr-xr-x 2 root root  4096 Dec  8 19:54 ./
drwx------ 8 root root  4096 Nov 24 15:13 ../
-rw-r--r-- 1 root root     0 Dec  5 09:13 DEPRECATION
-rw------- 1 root root 35839 Dec  8 19:54 kubelet_internal_checkpoint
srwxr-xr-x 1 root root     0 Dec  5 09:13 kubelet.sock=
srwxr-xr-x 1 root root     0 Dec  8 19:54 kubevirt-kvm.sock=
srwxr-xr-x 1 root root     0 Dec  8 19:54 kubevirt-sev.sock=
srwxr-xr-x 1 root root     0 Dec  8 19:52 kubevirt-TU106_GEFORCE_RTX_2060_REV__A.sock=
srwxr-xr-x 1 root root     0 Dec  8 19:54 kubevirt-tun.sock=
srwxr-xr-x 1 root root     0 Dec  8 19:54 kubevirt-vhost-net.sock=

kubelet 通过与该 socket 进行通讯,来达到设备插件的注册、分配资源等操作。这样一来我们创建 Pod 就可以在 resource request/limit 中申请 nvidia.com/TU106_GEFORCE_RTX_2060_REV__A 这个资源了。

注 1 : extend resource 的资源用量只能是整数,不过目前 nvidia-kubevirt-gpu-dp 没有提供类似 1000m = 1 core, 500m = 0.5 core 这样的效果。当下 "1" 就表示 1 张显卡。

注 2:在 nvidia-kubevirt-gpu-dp 部署之前我们需要给集群节点的 grub 启用 iommu 等操作,这样才能拥有 GPU 直通 guest 的能力。

注 3:剩余的 kubevirt-kvm.sock 等文件是 virt-handler 中的 device plugin 创建的。

KubeVirt VM use GPU#


创建 vm 如下,我们指定了使用 nvidia.com/TU106_GEFORCE_RTX_2060_REV__A 显卡:

apiVersion: kubevirt.io/v1
kind: VirtualMachine
metadata:
  name: vm-with-gpu
  namespace: cxl
spec:
  runStrategy: RerunOnFailure
  template:
    spec:
      domain:
        cpu:
          cores: 1
        devices:
          disks:
          # ...
          gpus:
          - deviceName: nvidia.com/TU106_GEFORCE_RTX_2060_REV__A
            name: gpu0
        machine:
          type: q35
        resources:
          requests:
            memory: 1Gi
      networks:
      # ...
      volumes:
      # ...

virt-controller 监听到 vmi 的 CREATE 后会为其构造出对应的 Pod (virt-launcher),源码部分见 传送门。 最终构造出来的 virt launcher Pod yaml 就是这样:

apiVersion: v1
kind: Pod
metadata:
  labels:
    kubevirt.io: virt-launcher
    vm.kubevirt.io/name: vm-with-gpu
  name: virt-launcher-vm-with-gpu-vvl4q
  namespace: cxl
spec:
  automountServiceAccountToken: false
  containers:
  - command:
    - ...
    image: quay.io/kubevirt/virt-launcher:v0.54.0
    imagePullPolicy: IfNotPresent
    name: compute
    resources:
      limits:
        devices.kubevirt.io/kvm: "1"
        devices.kubevirt.io/tun: "1"
        devices.kubevirt.io/vhost-net: "1"
        nvidia.com/TU106_GEFORCE_RTX_2060_REV__A: "1"
      requests:
        devices.kubevirt.io/kvm: "1"
        devices.kubevirt.io/tun: "1"
        devices.kubevirt.io/vhost-net: "1"
        cpu: 100m
        ephemeral-storage: 50M
        memory: 2262Mi
        nvidia.com/TU106_GEFORCE_RTX_2060_REV__A: "1"
...

假设 vm 被分配到节点 node05, node05 有 4 张 nvidia.com/TU106_GEFORCE_RTX_2060_REV__A 显卡,那么 kubevirt 会分配哪一张显卡给 vm 呢?

vm pod 里的容器启动时 kubelet 会调用 nvidia-kubevirt-gpu-dp 的 Allocate() 接口,这个接口需要在 response 数据中的 env 中把分配的 GPU 设备地址写上,最终会体现在容器的环境变量中。 如下 :

root@node01:~# kubectl exec -it virt-launcher-vm-with-gpu-vvl4q -n cxl -- bash
bash-4.4# env
PCI_RESOURCE_NVIDIA_COM_TU106_GEFORCE_RTX_2060_REV__A=0000:86:00.0

如果申请了多张同型号显卡,那么环境变量中的地址以逗号分隔 :

root@node01:~# kubectl exec -it virt-launcher-vm-with-gpu-vvl4q -n cxl -- bash
bash-4.4# env
PCI_RESOURCE_NVIDIA_COM_TU106_GEFORCE_RTX_2060_REV__A=0000:86:00.0,0000:af:00.0

也就是说 GPU 由 device plugin 的 Allocate 接口逻辑来分配。

在 virt-launcher Pod 运行起来后会去构造出 libvirt domain xml ,此时就会读取 Pod env 中的该环境变量,将其构造成对应的 xml 元素,如下 :

<hostdev mode='subsystem' type='pci' managed='yes'>
  <source>
    <address domain='0x0000' bus='0x04' slot='0x00' function='0x0'/>
  </source>
  <address type='pci' domain='0x0000' bus='0x00' slot='0x08' function='0x0'/>
  <alias name='ua-gpu0'>
</hostdev>

这样一来,libvirt 就能直接使用 host 的 pci device (也就是显卡) 了。

KubeVirt permitted host devices#


默认情况下所有 host dev 都可以被虚拟机使用, 但 kubevirt 允许在 kubevirt cr 中配置哪些 host device 可以被 vm 使用,如下:

...
configuration:
  permittedHostDevices:
    pciHostDevices:
    - pciVendorSelector: "10DE:1F08"
      resourceName: "nvidia.com/GeForce RTX 2060 Rev. A"
      externalResourceProvider: true
    - pciVendorSelector: "8086:6F54"
      resourceName: "intel.com/qat"
    mediatedDevices:
    - mdevNameSelector: "GRID T4-1Q"
      resourceName: "nvidia.com/GRID_T4-1Q"

如果你做了如上配置,那么你要分配给虚拟机的 GPU 必须符合上述配置的 Selector 规则。

pciVendorSelector 由 vendorID (厂商) 和 productID (产品) 组成,其中 10DE 表示 Nvidia , 1F08 表示 GeForce RTX 2060 Rev. A 这款产品 (详见: https://pci-ids.ucw.cz/read/PC/10de )。 也就是说这个 host dev 是被 kubevirt 允许用于虚拟机的。

对于该型号的 GPU,externalResourceProvider: true 表示让外部 device plugin (也就是 nvidia-kubevirt-gpu-dp) 接管该设备。 如果你指明了该设备的 externalResourceProvider: false ,那么意味着将由 virt-handler 中的 device plugin manager 来接管该设备,它会为此设备启动一个 device plugin。

加载中...
此文章数据所有权由区块链加密技术和智能合约保障仅归创作者所有。