TT Lab
はじめる
学ぶ 学習パス コース

GPU Operatorとタイムスライシング

workload.config — ラベル一つがノードの正体を決める

TT Labで続きを見る

一言でいうと

GPU Operatorは、nvidia.com/gpu.workload.configラベルの値(container・vm-passthrough・vm-vgpu)を見て、そのノードにどのオペランドを載せるかを丸ごと入れ替え、その結果、ノードがアドバタイズするリソース名まで変わります。コンテナノードはnvidia.com/gpuをアドバタイズし、パススルーのノードはnvidia.com/GA102GL_A10のようなデバイスモデル名を、vGPUノードはnvidia.com/NVIDIA_A10-12Qのようなプロファイル名をアドバタイズします。

なぜ必要なのか

GPUを使うワークロードが、すべてコンテナというわけではありません。ライセンスが特定のOSを要求したり、Windowsドライバーが必要だったり、レガシーなシミュレーションが丸ごと仮想マシンの中にあったりする場合が、実際に多くあります。そのような組織は、長い間クラスターを2つに分けて運用してきました。コンテナ用のKubernetes1つと、仮想マシン用のハイパーバイザー1つです。同じGPUの在庫を2か所に分けておくと、片方が遊んでいても、もう片方に貸せません。

KubeVirtは、仮想マシンをKubernetesのオブジェクトにすることで、その壁をなくし、GPU Operatorは、その上でワーカーノードを2種類の用途にそれぞれ準備できるようになりました。公式ドキュメントは、その違いを1行で要約しています。コンテナにはデータセンタードライバーが、GPUパススルーにはvfio-pciドライバーが、vGPUにはNVIDIA vGPU Managerが必要です。必要なドライバーが違うことが、すべての違いの根っこです。

ラベル1つがノードのソフトウェアを丸ごと変える

ノードにラベルを付けるのがすべてです。

kubectl label node <노드이름> --overwrite nvidia.com/gpu.workload.config=vm-vgpu

すると、オペレーターがそのノードに載せるオペランドが変わります。

ラベルの値 そのノードに載るもの 役割
container データセンタードライバー・コンテナツールキット・Kubernetesデバイスプラグイン・DCGMとDCGM Exporter コンテナがGPUを使えるようにして、メトリクスを出力します
vm-passthrough VFIO Manager・サンドボックスデバイスプラグイン vfio-pciを読み込んで、そのノードのすべてのGPUにバインドし、パススルーGPUをkubeletにアドバタイズします
vm-vgpu vGPU Manager・vGPU Device Manager・サンドボックスデバイスプラグイン vGPUドライバーをインストールし、vGPUデバイスを作成してから、kubeletにアドバタイズします

ここで必ず押さえるべき文が2つあります。

第一に、ラベルがなければ、既定値はcontainerです。オペレーターは、ラベルのないノードをコンテナ用に準備します。既定を変えるには、ClusterPolicyのsandboxWorkloads.defaultWorkloadを直します。

第二に、このラベルは、sandboxWorkloads.enabledが有効なときにしか使われません。そのフラグは既定でオフで、オフならすべてのノードがコンテナ用に準備され、ラベルはそもそも読まれません。ラベルだけを付けて、なぜ変わらないのかと何時間も見ている場所が、ここです。ラベルは構文エラーでもなく、警告も出しません。

そして、公式ドキュメントが固定する制約がもう1つあります。1台のワーカーノードは、1種類のGPUワークロードしか動かしません。コンテナとパススルーVMを、1台のノードで混在させることはできません。vfio-pciがそのノードのGPUをすべて持っていくので、ホストのデータセンタードライバーがそのカードを手放す必要があるからです。在庫計画がノード単位で固まるという意味で、これが実務で最も大きく体感される制約です。

リソース名が変わる

コンテナノードがアドバタイズするのは、私たちがよく知っているnvidia.com/gpuです。サンドボックスのノードは違います。公式ドキュメントの出力例が、そのまま語ってくれます。

# 패스스루 노드
$ kubectl get node <노드> -o json | jq '.status.allocatable | with_entries(select(.key | startswith("nvidia.com/")))'
{ "nvidia.com/GA102GL_A10": "1" }

# vGPU 노드 (기본 설정: 카드마다 절반 크기 Q 프로파일 두 개)
{ "nvidia.com/NVIDIA_A10-12Q": "4" }

前者はPCIデバイスのモデル名から、後者はvGPUプロファイル名から来ています。A10のカード2枚に既定の設定を適用すると、カードごとに12Qが2つできて、合計4つになります。ここで、ノードのラベルnvidia.com/vgpu.configでプロファイルを変えられます。A10-4Qを選ぶと、カードごとに6つずつ、2枚なら12個がアドバタイズされます。同じハードウェアなのに、ラベル1つでアドバタイズ量とリソース名が一緒に変わります。

名前が違うことの結果は、単純で厳しいものです。nvidia.com/gpuを要求するPodは、パススルーノードには絶対に行けません。空きが残っていても、スケジューラーにとって、そのノードはそのリソースを持たないノードです。逆も同じです。クラスターがカードの在庫を分けておくと、片方で待ち行列が長くても、もう片方の空きは空いたまま残ります。

VMにカードを接続する形

デバイスがアドバタイズされたからといって、VMがすぐ使えるわけではありません。KubeVirt側に許可リストを別に書く必要があります。これが、人が最も抜かしやすい段階です。KubeVirtカスタムリソースのpermittedHostDevicesの下に、パススルーならpciHostDevices、vGPUならmediatedDevicesとして書き、どちらにもexternalResourceProvider: trueを付けます。そのフラグは、「このリソースは自分たちが作ったものではなく、外部のデバイスプラグイン(ここではサンドボックスデバイスプラグイン)がアドバタイズする」という意味です。

spec:
  configuration:
    developerConfiguration:
      featureGates:
        - GPU
        - DisableMDEVConfiguration
    permittedHostDevices:
      pciHostDevices:
        - externalResourceProvider: true
          pciVendorSelector: 10DE:2236
          resourceName: nvidia.com/GA102GL_A10

そのあとで初めて、VM側からカードを要求できます。Podのresources.limitsとは書く場所が違います。VirtualMachineInstanceは、spec.domain.devices.gpusに書きます。

spec:
  domain:
    devices:
      gpus:
        - deviceName: nvidia.com/GA102GL_A10
          name: gpu1

deviceNameがリソース名で、nameは、VMの中でそのデバイスを呼ぶエイリアスです。見た目は違いますが、スケジューリング段階で起きることはPodと同じです。VMを代わりに実行するPodがそのリソースを要求し、そのリソースをアドバタイズするノードに行きます。

最後に、公式ドキュメントがはっきり書いている事実が1つあります。GPU Operatorは、VMの中のNVIDIAドライバーのインストールを自動化しません。カードをVMに接続するところまでがその仕事で、ゲストOSの中のドライバーは、VMイメージを作る側の担当です。

現場での姿

第一に、BIOSとカーネルパラメーターが前提条件です。仮想化拡張とIOMMUがBIOSで有効になっている必要があり、カーネルのコマンドラインにintel_iommu=onまたはamd_iommu=onがある必要があります。Ampere以降のカードでvGPUを使うには、BIOSでSR-IOVも有効にする必要があります。これらは再起動が必要な変更なので、ノードをクラスターに入れる前に、イメージの段階で済ませておかないと、あとでノードを1台ずつ空にしながら直すことになります。

第二に、ノードを用途の間で移すことは、無停止ではありません。ラベルを変えるとオペランドが入れ替わり、ドライバーが変わります。公式ドキュメントは、vGPUの設定を変えるとき、そのノードで動いていたVMを先に停止するか移動するようにと書いています。在庫を柔軟に回そうとする計画は、この点で現実と出会います。1日単位で移すことはできますが、分単位ではできません。

第三に、リソース名がカードのモデルに結び付いていて、マニフェストがハードウェアに縛られます。nvidia.com/GA102GL_A10を要求するVMのマニフェストは、A10がないクラスターでは、ずっとPendingです。コンテナ側のnvidia.com/gpuがモデルと無関係だったこととは対照的です。そのため、サンドボックスワークロードを使う組織は、モデル名をマニフェストに直接書かず、Helmの値やkustomizeのパッチで1か所にまとめておきます。

第四に、この環境の正直な限界です。ラボのクラスターには、KubeVirtもvfio-pciもなく、VMは起動できません。そのため、次のラボでは、VMの要求を同じリソース名を要求するPodとして作ります。スケジューリング段階で起きることが実際に同じなので(どちらも拡張リソースを要求するPodです)、どこに行き、どこで止まるのかは、実物で学べます。ただし、VMが実際に起動してカードを確保する後半は確認できず、その部分は、上のVMIマニフェストの形で代用します。

参考ドキュメント

次のラボですること

3台のノードを、それぞれcontainer・vm-passthrough・vm-vgpuでラベル付けして、各ラベルに合うリソース名をノードのstatusに作ります。ラベル値ごとに、どのオペランドが起動する必要があるかを表ファイルにまとめて、3種類のワークロード(コンテナのPod・パススルーの要求・vGPUの要求)を載せ、それぞれが自分のノードに行くことと、他のノードのリソースを要求するとどこにも起動できないことを、実際のスケジューラーで確認します。最後に、ノードのダンプを受け取って、「このラベルとこのリソースのアドバタイズが合っているか」を判定するチェッカーを作り、わざとずらしたノードを、そのチェッカーが見つけ出すかを見ます。