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

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

サンドボックスワークロード — ラベルが決める資源名と、分かれてしまう在庫

TT Labで続きを見る

目標

3台のノードを、それぞれ別のGPUワークロードの用途として作り、用途ごとにリソース名が変わることと、その結果、在庫が分かれることを、実際のスケジューラーで確認したうえで、ラベルとアドバタイズが合っているかを判定するチェッカーを作ります。

なぜ重要なのか

GPUを使うワークロードが、すべてコンテナというわけではありません。ライセンスやレガシーのために、仮想マシンの中で動く必要があるものがあり、そのようなVMにカードを渡すには、ノードにインストールされるドライバーから違う必要があります。コンテナにはデータセンタードライバー、パススルーにはvfio-pci、vGPUにはvGPU Managerです。GPU Operatorは、その選択をノードラベル1行で受け付けます。そして、その選択の結果が、ノードがアドバタイズするリソース名まで変えます。名前が違うと、スケジューラーにとっては別のリソースなので、片方の待ち行列が長くても、もう片方の空きは空いたまま残ります。在庫を用途別に分けることが、なぜ元に戻しにくい決定なのかが、ここから出てきます。

ステップ

  1. /root/gpuwl/out・/root/gpuwl/bin・/root/gpuwl/k8sを作成して、ネームスペースgpu-vmを作成してください。3台のノードに、ラベルnvidia.com/gpu.workload.configを付けてください。lab-node-0はcontainer、lab-node-1はvm-passthrough、lab-node-2はvm-vgpuです。そして/root/gpuwl/out/01-nodes.txtに3行を書いてください。1行が<노드이름> <라벨값>の形です(ノード名の昇順、プレースホルダーはノード名とラベルの値です)。
  2. /root/gpuwl/out/operands.txtに5行を書いてください。前の3行は<라벨값>=<오퍼랜드 목록>で(プレースホルダーはラベルの値とオペランドの一覧です)、一覧はカンマでつなぎます(順序は問いません)。containerはdatacenter-driver、container-toolkit、device-plugin、dcgm-exporter、vm-passthroughはvfio-manager、sandbox-device-plugin、vm-vgpuはvgpu-manager、vgpu-device-manager、sandbox-device-pluginです。4行目は、NO_LABEL=に、ラベルがないときにオペレーターが想定する値を、5行目は、ENABLE_FLAG=に、このラベルを使われるようにするClusterPolicyのフラグ名を書きます。
  3. 3台のノードのstatus.capacityとstatus.allocatableの両方に、それぞれのリソースを入れてください。lab-node-0はnvidia.com/gpuを"4"、lab-node-1はnvidia.com/GA102GL_A10を"2"、lab-node-2はnvidia.com/NVIDIA_A10-12Qを"4"です。そして/root/gpuwl/out/03-resources.txtに3行を書いてください。1行が<노드이름> <자원이름> <광고량>の形です(ノード名の昇順、プレースホルダーはノード名とリソース名とアドバタイズ量です)。
  4. /root/gpuwl/k8s/pod-container.yamlに、job-containerというPodを書いてください。ネームスペースgpu-vm、コンテナ名cuda、イメージnvcr.io/nvidia/cuda:12.4.1-base-ubuntu22.04、limitsにnvidia.com/gpu: 1です。nodeSelectorは書かないでください。適用したあと、どのノードで起動したかを、/root/gpuwl/out/04-container.txtに1行で書いてください。NODE=<노드이름>です(プレースホルダーはノード名です)。
  5. /root/gpuwl/k8s/pod-passthrough.yamlに、vmi-passthroughというPodを書いてください。ネームスペースgpu-vm、limitsにnvidia.com/GA102GL_A10: 1、nodeSelectorなしです。残りはステップ4と同じです。適用したあと、/root/gpuwl/out/05-passthrough.txtに2行を書いてください。NODE=とRESOURCE=(要求したリソース名そのまま)です。
  6. /root/gpuwl/k8s/pod-vgpu.yamlに、vmi-vgpuというPodを書いてください。ネームスペースgpu-vm、limitsにnvidia.com/NVIDIA_A10-12Q: 1、nodeSelectorなしです。適用したあと、/root/gpuwl/out/06-placement.txtに3行を書いてください。ここまでに作成した3つのPodが、それぞれどこで起動したかを<파드이름> <노드이름>の形で(プレースホルダーはPod名とノード名です)、Pod名の昇順で書きます。
  7. /root/gpuwl/k8s/pod-cross.yamlに、job-crossというPodを書いてください。ネームスペースgpu-vm、nodeSelectorでnvidia.com/gpu.workload.config: vm-passthroughをかけて、limitsにはnvidia.com/gpu: 1を要求します。パススルーノードに、コンテナ用のリソースを求めることになります。適用したあと、/root/gpuwl/out/07-cross.txtに3行を書いてください。PHASE=、NODE=(空ならnone)、MESSAGE=(PodScheduled条件のメッセージ)です。
  8. /root/gpuwl/out/nodes.jsonにkubectl get nodes -o jsonの結果を保存して、/root/gpuwl/bin/check-config.sh <노드JSON파일>を作成してください(プレースホルダーはノードのJSONファイルです)。ファイルの中のノードのうち、nvidia.com/gpu.workload.configラベルがあるものだけを見て、その値とアドバタイズしたnvidia.com/のリソース名が合っているかを判定します。containerはnvidia.com/gpuである必要があり、vm-vgpuはvGPUのプロファイル名(番号と大文字で終わるもの)である必要があり、vm-passthroughは、その2つのどちらでもないデバイスモデル名である必要があります。合わないノードごとに、MISMATCH=<노드이름> <이유>を1行ずつ出力して1で終わり(プレースホルダーはノード名と理由です)、すべて合っていれば、OK=<검사한 노드 수>を出力して0で終わります(プレースホルダーは検査したノード数です)。作成したら、保存したダンプにかけて、出力を/root/gpuwl/out/consistency.txtに保存してください。

参考

ノード3台に用途を書く

/root/gpuwl/out・/root/gpuwl/bin・/root/gpuwl/k8sを作成して、ネームスペースgpu-vmを作成してください。3台のノードに、ラベルnvidia.com/gpu.workload.configを付けてください。lab-node-0はcontainer、lab-node-1はvm-passthrough、lab-node-2はvm-vgpuです。そして/root/gpuwl/out/01-nodes.txtに3行を書いてください。1行が<노드이름> <라벨값>の形です(ノード名の昇順、プレースホルダーはノード名とラベルの値です)。

このラベル1つが、そのノードに載るオペレーターのソフトウェアを丸ごと変えます。ラベルは、kubectl label node <이름> --overwrite <키>=<값>で付けます(プレースホルダーはノード名とキーと値です)。値は3つだけで、タイプミスをしても何の警告もありません。そのため、付けたあとに確認する習慣が必要です。kubectl get nodes -L <키>で、1画面で見られます(プレースホルダーはキーです)。

ラベルの値ごとに、何が載るのかを表にする

/root/gpuwl/out/operands.txtに5行を書いてください。前の3行は<라벨값>=<오퍼랜드 목록>で(プレースホルダーはラベルの値とオペランドの一覧です)、一覧はカンマでつなぎます(順序は問いません)。containerはdatacenter-driver、container-toolkit、device-plugin、dcgm-exporter、vm-passthroughはvfio-manager、sandbox-device-plugin、vm-vgpuはvgpu-manager、vgpu-device-manager、sandbox-device-pluginです。4行目は、NO_LABEL=に、ラベルがないときにオペレーターが想定する値を、5行目は、ENABLE_FLAG=に、このラベルを使われるようにするClusterPolicyのフラグ名を書きます。

3行を並べて見ると、共通項がsandbox-device-pluginだけであることが見えます。VM側の2つの用途は、デバイスをアドバタイズする方法は同じで、ドライバーの準備方法が違います。5行目が最も重要です。そのフラグがオフだと(既定値はオフです)、ラベルをどれだけ正確に付けても読まれず、すべてのノードがコンテナ用に準備されます。フラグ名は、ドットでつながった2つの単語です。

用途ごとに違うリソース名をアドバタイズする

3台のノードのstatus.capacityとstatus.allocatableの両方に、それぞれのリソースを入れてください。lab-node-0はnvidia.com/gpuを"4"、lab-node-1はnvidia.com/GA102GL_A10を"2"、lab-node-2はnvidia.com/NVIDIA_A10-12Qを"4"です。そして/root/gpuwl/out/03-resources.txtに3行を書いてください。1行が<노드이름> <자원이름> <광고량>の形です(ノード名の昇順、プレースホルダーはノード名とリソース名とアドバタイズ量です)。

実際のクラスターでは、ノードごとに違うデバイスプラグインがこの場所を埋めます。コンテナノードはKubernetesデバイスプラグインが、VM側の2台のノードはサンドボックスデバイスプラグインが埋めます。パススルーのリソース名はPCIデバイスのモデルから来て、vGPUのリソース名はプロファイルから来ます。後者だけが番号と大文字で終わるという規則が、最後のステップの判定の根拠になります。JSONパッチで、リソース名のドットとスラッシュをエスケープする必要はありません。jsonpathで読むときだけ、ドットをエスケープします。

コンテナワークロードは、コンテナノードに行く

/root/gpuwl/k8s/pod-container.yamlに、job-containerというPodを書いてください。ネームスペースgpu-vm、コンテナ名cuda、イメージnvcr.io/nvidia/cuda:12.4.1-base-ubuntu22.04、limitsにnvidia.com/gpu: 1です。nodeSelectorは書かないでください。適用したあと、どのノードで起動したかを、/root/gpuwl/out/04-container.txtに1行で書いてください。NODE=<노드이름>です(プレースホルダーはノード名です)。

条件を1つも指定していないのに、行けるノードは1か所だけです。リソース名そのものがノードを選んだのです。このラボ全体の要点が、この1行に入っています。拡張リソースは、limitsにだけ書きます。requestsとlimitsが同じでなければならないという規則があるため、limitsだけで十分です。

パススルーの要求は、デバイスモデル名を呼ぶ

/root/gpuwl/k8s/pod-passthrough.yamlに、vmi-passthroughというPodを書いてください。ネームスペースgpu-vm、limitsにnvidia.com/GA102GL_A10: 1、nodeSelectorなしです。残りはステップ4と同じです。適用したあと、/root/gpuwl/out/05-passthrough.txtに2行を書いてください。NODE=とRESOURCE=(要求したリソース名そのまま)です。

本物のクラスターでは、ここにVirtualMachineInstanceが来て、リソースはspec.domain.devices.gpus[].deviceNameに書きます。このPodにはKubeVirtがなくVMを起動できませんが、スケジューリング段階で起きることは同じです。VMも結局、そのリソースを要求するPodが代わりに実行されるからです。リソース名はPCIデバイスのモデルから来ます。1文字だけ間違えても、そのノードは候補から消えます。

vGPUの要求は、プロファイル名を呼ぶ

/root/gpuwl/k8s/pod-vgpu.yamlに、vmi-vgpuというPodを書いてください。ネームスペースgpu-vm、limitsにnvidia.com/NVIDIA_A10-12Q: 1、nodeSelectorなしです。適用したあと、/root/gpuwl/out/06-placement.txtに3行を書いてください。ここまでに作成した3つのPodが、それぞれどこで起動したかを<파드이름> <노드이름>の形で(プレースホルダーはPod名とノード名です)、Pod名の昇順で書きます。

3行を並べると、リソース名1つが配置を丸ごと決めたことが、一目でわかります。どのPodにもnodeSelectorを指定していないのに、3つがそれぞれ別のノードに行きました。vGPUのプロファイル名は、番号と大文字で終わります(例: 12Q)。その形が、パススルーの名前と区別できる点で、ステップ8のチェッカーが使うシグナルでもあります。配置は、kubectl get pods -n <ns> -o custom-columns=で、一度に取り出せます。

他のリソースを呼ぶと、どこにも行けない

/root/gpuwl/k8s/pod-cross.yamlに、job-crossというPodを書いてください。ネームスペースgpu-vm、nodeSelectorでnvidia.com/gpu.workload.config: vm-passthroughをかけて、limitsにはnvidia.com/gpu: 1を要求します。パススルーノードに、コンテナ用のリソースを求めることになります。適用したあと、/root/gpuwl/out/07-cross.txtに3行を書いてください。PHASE=、NODE=(空ならnone)、MESSAGE=(PodScheduled条件のメッセージ)です。

そのノードには、カードが2枚もアドバタイズされていて、空きもあります。それでも行けません。スケジューラーは、リソース名を文字列として照合するだけだからです。クラスターが在庫を用途別に分けておくと、片方の待ち行列が長くても、もう片方の空きは空いたまま残ります。これが、サンドボックスワークロードを導入するときに、最初に計画すべき制約です。メッセージが何と言っているかに注目してください。「リソースが足りない」と言います。

ラベルとリソースのアドバタイズが合っているかを判定するチェッカー

/root/gpuwl/out/nodes.jsonにkubectl get nodes -o jsonの結果を保存して、/root/gpuwl/bin/check-config.sh <노드JSON파일>を作成してください(プレースホルダーはノードのJSONファイルです)。ファイルの中のノードのうち、nvidia.com/gpu.workload.configラベルがあるものだけを見て、その値とアドバタイズしたnvidia.com/のリソース名が合っているかを判定します。containerはnvidia.com/gpuである必要があり、vm-vgpuはvGPUのプロファイル名(番号と大文字で終わるもの)である必要があり、vm-passthroughは、その2つのどちらでもないデバイスモデル名である必要があります。合わないノードごとに、MISMATCH=<노드이름> <이유>を1行ずつ出力して1で終わり(プレースホルダーはノード名と理由です)、すべて合っていれば、OK=<검사한 노드 수>を出力して0で終わります(プレースホルダーは検査したノード数です)。作成したら、保存したダンプにかけて、出力を/root/gpuwl/out/consistency.txtに保存してください。

このチェッカーは、クラスターには接続せず、ファイル1つだけを読みます。そうすれば、他の人が送ってくれたダンプにも、デプロイ前の審査にも使えます。判定の核心は、名前の形です。vGPUプロファイルは、-12Q、-4Qのように、ハイフンの後ろに数字と大文字1文字で終わります。1台のノードが複数種類のnvidiaリソースをアドバタイズしているのも、不一致です。1台のワーカーノードは、1種類のGPUワークロードしか動かしません。アドバタイズ量が"0"のリソースは、ないものとして扱う必要があります。採点ツールは、わざとずらしたダンプにもかけます。