delta_lt_0

delta_lt_0

人生苦短 及时行乐

KubeVirt 虛擬機 GPU 直通

將 NVIDIA GPU 暴露給 Kubernetes#


k8s 預設提供給 Pod 的可用資源有 Memory, CPU 等,並沒有 GPU。 但 K8S 允許我們自己開發 Device Plugin 來暴露資源。 大致流程是這樣:

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 使用 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。

載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。