將 NVIDIA GPU 暴露給 Kubernetes#
k8s 預設提供給 Pod 的可用資源有 Memory, CPU 等,並沒有 GPU。 但 K8S 允許我們自己開發 Device Plugin
來暴露資源。 大致流程是這樣:
按照 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 使用 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 允許的主機設備#
預設情況下所有主機設備都可以被虛擬機使用,但 kubevirt 允許在 kubevirt cr 中配置哪些主機設備可以被 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 )。也就是說這個主機設備是被 kubevirt 允許用於虛擬機的。
對於該型號的 GPU,externalResourceProvider: true
表示讓外部 device plugin (也就是 nvidia-kubevirt-gpu-dp) 接管該設備。如果你指明了該設備的 externalResourceProvider: false
,那麼意味著將由 virt-handler 中的 device plugin manager 來接管該設備,它會為此設備啟動一個 device plugin。