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=
という名前のソケットファイルが新たに作成されます。
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 はこのソケットとの通信を通じて、デバイスプラグインの登録やリソースの割り当てなどの操作を行います。これにより、Pod を作成する際に resource request/limit でnvidia.com/TU106_GEFORCE_RTX_2060_REV__A
というリソースを要求できるようになります。
注 1: 拡張リソースのリソース使用量は整数でなければなりませんが、現在 nvidia-kubevirt-gpu-dp は 1000m = 1 core、500m = 0.5 core のような効果を提供していません。現在のところ「1」は 1 枚のグラフィックカードを意味します。
注 2: nvidia-kubevirt-gpu-dp をデプロイする前に、クラスターのノードの grub で iommu を有効にするなどの操作が必要です。これにより、GPU をゲストに直通させる能力が得られます。
注 3: 残りの kubevirt-kvm.sock などのファイルは、virt-handler 内のデバイスプラグインによって作成されたものです。
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()
インターフェースを呼び出します。このインターフェースは、レスポンスデータ内の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 はデバイスプラグインの Allocate インターフェースのロジックによって割り当てられます。
virt-launcher Pod が実行されると、libvirt ドメインの 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 はホストの PCI デバイス(つまりグラフィックカード)を直接使用できるようになります。
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
は外部デバイスプラグイン(つまり nvidia-kubevirt-gpu-dp)がこのデバイスを管理することを示します。もしこのデバイスのexternalResourceProvider: false
を指定した場合、virt-handler 内のデバイスプラグインマネージャーがこのデバイスを管理し、このデバイスのためにデバイスプラグインを起動します。