Expose NVIDIA GPU to Kubernetes#
k8s 默认提供给 Pod 的可使用资源有 Memory, CPU 等,并没有 GPU。 但 K8S 允许我们自己开发 Device Plugin
来暴露 Resource。 大致流程是这样:
按照 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。