ラベルを一行下げたらポッドが消えた
目標
GPU Operatorが展開するDaemonSet5つをノードのラベルで配置し、ノード1台でオペランド1つだけを外してみて、「あるべきオペランドがすべてあるか」と「順序がずれたノードがあるか」を判定するツールを作ります。
なぜ重要なのか
GPU Operatorをインストールすると、Podが10個以上起動します。それらのPodは、1つのプログラムではなく、オペレーターがClusterPolicyを読んで展開したDaemonSet複数個です。ドライバー、コンテナツールキット、デバイスプラグイン、GPU Feature Discovery、DCGM exporter、バリデーター、MIGマネージャーが、それぞれ自分のDaemonSetを持ち、各DaemonSetはnodeSelectorでnvidia.com/gpu.deploy.<이름>ラベルを見ます(プレースホルダーはオペランド名です)。この構造を知っていると、運用でできることが変わります。ノード1台だけをオペランドから外すことも、ドライバーがすでにインストールされたノードにドライバーだけを載せないことも、問題が起きたノード1台を隔離することも、ラベル1行です。知らないと、逆になります。ラベルが1つ間違って付いて、ツールキットなしでデバイスプラグインだけが動くノードができると、リソースはアドバタイズされ、スケジューラーは成功するのに、ワークロードだけがデバイスを掴めません。誰もエラーを出さないので、何時間もそのまま過ぎます。
ステップ
/root/gpuopsで作業します(export KUBECONFIG=/root/.kube/config)。ネームスペースgpu-operatorを作成してください。lab-node-0(GPUノード、ドライバーなし)に、feature.node.kubernetes.io/pci-10de.present=true、nvidia.com/gpu.present=trueとともに、nvidia.com/gpu.deploy.driver=true、nvidia.com/gpu.deploy.container-toolkit=true、nvidia.com/gpu.deploy.device-plugin=true、nvidia.com/gpu.deploy.gpu-feature-discovery=true、nvidia.com/gpu.deploy.dcgm-exporter=trueを付けてください。lab-node-1(GPUノード、ドライバーがホストにすでにインストール済み)には、同じラベルを付けますが、nvidia.com/gpu.deploy.driverだけをfalseにします。lab-node-2(GPUなし)には、nvidia.com/gpu.deploy.で始まるラベルを1つも付けないでください。そして/root/gpuops/out/roster.txtに5行を書いてください。<오퍼랜드이름>=<그 오퍼랜드를 켜는 라벨 키>の形で(プレースホルダーはオペランド名と、そのオペランドを有効にするラベルキーです)、driver・container-toolkit・device-plugin・gpu-feature-discovery・dcgm-exporterの5つです。/root/gpuops/k8s/toolkit-ds.yamlに、nvidia-container-toolkit-daemonsetというDaemonSetを書いてください。ネームスペースはgpu-operator、Podのラベルとセレクターはapp: nvidia-container-toolkit-daemonset、nodeSelectorはnvidia.com/gpu.deploy.container-toolkit: "true"、コンテナイメージはnvcr.io/nvidia/k8s/container-toolkit:v1.16.2、メモリ要求は128Miです。適用したあと、desiredNumberScheduledが2になり、Podがlab-node-0とlab-node-1に1つずつ起動することを確認してください。/root/gpuops/k8s/driver-ds.yamlに、nvidia-driver-daemonsetというDaemonSetを書いてください。同じネームスペース、Podのラベルとセレクターはapp: nvidia-driver-daemonset、nodeSelectorはnvidia.com/gpu.deploy.driver: "true"、イメージはnvcr.io/nvidia/driver:550.90.07-ubuntu22.04、メモリ要求は512Miです。適用したあと、GPUノードが2台あるのに、このDaemonSetだけが1台のノードで起動することを確認し、/root/gpuops/out/driver.txtに2行を書いてください。DESIRED=<숫자>とSKIPPED=<빠진 노드 이름>です(プレースホルダーは数字と、外れたノードの名前です)。- DaemonSetをさらに2つ作成してください。
/root/gpuops/k8s/device-plugin-ds.yamlのnvidia-device-plugin-daemonsetは、nodeSelectorがnvidia.com/gpu.deploy.device-plugin: "true"、イメージがnvcr.io/nvidia/k8s-device-plugin:v0.16.2、メモリ要求が128Miです。/root/gpuops/k8s/gfd-ds.yamlのgpu-feature-discoveryは、nodeSelectorがnvidia.com/gpu.deploy.gpu-feature-discovery: "true"で、イメージもメモリ要求も同じです。どちらも、Podのラベルとセレクターはapp: <데몬셋 이름>です(プレースホルダーはDaemonSet名です)。適用したあと、2つのDaemonSetのdesiredNumberScheduledが、それぞれ2であることを確認してください。 /root/gpuops/k8s/dcgm-ds.yamlに、nvidia-dcgm-exporterというDaemonSetを書いてください。nodeSelectorはnvidia.com/gpu.deploy.dcgm-exporter: "true"、イメージはnvcr.io/nvidia/k8s/dcgm-exporter:3.3.7-3.5.0-ubuntu22.04、メモリ要求は128Mi、Podのラベルとセレクターはapp: nvidia-dcgm-exporterです。適用して2台のノードで起動することを確認したら、lab-node-1のnvidia.com/gpu.deploy.dcgm-exporterをfalseに変更し、Podが消えることを確認してください。/root/gpuops/out/optout.txtに2行を書いてください。BEFORE=<바꾸기 전 desiredNumberScheduled>とAFTER=<바꾼 뒤 값>です(プレースホルダーは、変更前のdesiredNumberScheduledと、変更後の値です)。/root/gpuops/operand-audit.shを作成してください。クラスターのすべてのノードを回り、そのノードにnvidia.com/gpu.deploy.<이름>ラベルがtrueで付いているオペランドごとに(プレースホルダーはオペランド名です)、そのDaemonSetのPodがそのノードでRunningかを確認します。なければ<노드> MISSING <오퍼랜드이름>を1行出力し(プレースホルダーはノードとオペランド名です)、そのノードに欠けているものが1つもなければ<노드> OKを1行出力します(プレースホルダーはノードです)。1つでも欠けていれば終了コード1、そうでなければ0です。オペランド名とDaemonSet名の組は、ステップ1の5つです。作成したら、今のクラスターに対して実行して、出力を/root/gpuops/out/audit.txtに保存してください。ノード名をスクリプトの中に書いておかないでください。採点ツールが、ノードをもう1台作ってから呼び出します。- 誰かがlab-node-2に、
nvidia.com/gpu.deploy.device-plugin=trueだけを手で付けたとします。そのラベルを実際に付け(他のdeployラベルは付けません)、デバイスプラグインのPodがそのノードで起動することを確認してください。そして/root/gpuops/order-check.sh <노드이름>を作成してください(プレースホルダーはノード名です)。そのノードでデバイスプラグインのPodはRunningなのに、コンテナツールキットのPodがなければ、device-plugin-without-toolkitの1行と終了コード1、それ以外の場合は、okの1行と0です。3台のノードに順番に実行して、/root/gpuops/out/order.txtに、<노드> <결과>の形で3行を書いてください(プレースホルダーはノードと結果です)。 /root/gpuops/out/matrix.txtに、ノードごとに1行ずつ、3行を書いてください。形式は<노드> driver=<yes|no> toolkit=<yes|no> device-plugin=<yes|no> gfd=<yes|no> dcgm=<yes|no>で(プレースホルダーはノード名です)、yesは、そのオペランドのDaemonSetのPodがそのノードでRunningという意味です。行の順序は、lab-node-0、lab-node-1、lab-node-2です。そして4行目に、TOTAL_PODS=<gpu-operator 네임스페이스에서 Running 인 데몬셋 파드 총수>を書いてください(プレースホルダーは、gpu-operatorネームスペースでRunningのDaemonSetのPodの総数です)。数字とyes/noは、APIに問い合わせて埋めてください。前のステップの記憶で書くと、実際とずれます。
参考
export KUBECONFIG=/root/.kube/configで始めます。ノードはlab-node-0/1/2の3台で、すべての出力物は/root/gpuopsの下、オブジェクトはネームスペースgpu-operatorに置きます。- この環境には、GPUもGPU Operatorもありません。そのため、オペレーターが作り出すはずのDaemonSetを、自分で書きます。その代わり、DaemonSetコントローラーとスケジューラーは本物なので、ラベルに応じてPodができたり消えたりするのは実物です。
- Podは実際には実行されず、Readyに偽装されます。ドライバーが本当にインストールされたかどうかは見られず、見てもいません。このラボが判定するのは、「どのノードに何が起動したか」だけです。
- DaemonSetを適用したり、ラベルを変更したりした直後は、コントローラーが反応する時間が数秒必要です。数字が以前のままなら、少し待ってからもう一度見てください。
- よくある間違い: nodeSelectorの値から引用符を外すことです。ラベルの値は文字列なので、
"true"と書く必要があります。 - よくある間違い: 判定を
desiredNumberScheduledだけで行うことです。ラベルは合っているのにPodが起動できなかったノードは、その数字に含まれません。 - GPU Operator: Getting Started・Installing the NVIDIA GPU Operator・DaemonSet
オペランドを有効・無効にするラベルを、ノードごとに変える
/root/gpuopsで作業します(export KUBECONFIG=/root/.kube/config)。ネームスペースgpu-operatorを作成してください。lab-node-0(GPUノード、ドライバーなし)に、feature.node.kubernetes.io/pci-10de.present=true、nvidia.com/gpu.present=trueとともに、nvidia.com/gpu.deploy.driver=true、nvidia.com/gpu.deploy.container-toolkit=true、nvidia.com/gpu.deploy.device-plugin=true、nvidia.com/gpu.deploy.gpu-feature-discovery=true、nvidia.com/gpu.deploy.dcgm-exporter=trueを付けてください。lab-node-1(GPUノード、ドライバーがホストにすでにインストール済み)には、同じラベルを付けますが、nvidia.com/gpu.deploy.driverだけをfalseにします。lab-node-2(GPUなし)には、nvidia.com/gpu.deploy.で始まるラベルを1つも付けないでください。そして/root/gpuops/out/roster.txtに5行を書いてください。<오퍼랜드이름>=<그 오퍼랜드를 켜는 라벨 키>の形で(プレースホルダーはオペランド名と、そのオペランドを有効にするラベルキーです)、driver・container-toolkit・device-plugin・gpu-feature-discovery・dcgm-exporterの5つです。
オペレーターは1つで、オペランドは複数です。ClusterPolicyを読んだオペレーターが、ドライバー・ツールキット・デバイスプラグイン・GFD・DCGM exporterのようなDaemonSetに展開します。各DaemonSetのnodeSelectorがnvidia.com/gpu.deploy.<이름>ラベルを見るので(プレースホルダーはオペランド名です)、ノード1台でオペランド1つだけを外すことが、ラベル1行でできます。公式ドキュメントは、ドライバーを特定のノードにだけ載せない方法として、nvidia.com/gpu.deploy.driver=falseを挙げています。kubectl label node <이름> <키>=<값> --overwriteで、複数を一度に付けられます(プレースホルダーはノード名とキーと値です)。
最初のオペランドをラベルで配置する
/root/gpuops/k8s/toolkit-ds.yamlに、nvidia-container-toolkit-daemonsetというDaemonSetを書いてください。ネームスペースはgpu-operator、Podのラベルとセレクターはapp: nvidia-container-toolkit-daemonset、nodeSelectorはnvidia.com/gpu.deploy.container-toolkit: "true"、コンテナイメージはnvcr.io/nvidia/k8s/container-toolkit:v1.16.2、メモリ要求は128Miです。適用したあと、desiredNumberScheduledが2になり、Podがlab-node-0とlab-node-1に1つずつ起動することを確認してください。
DaemonSetは「ノードごとに1つ」ではなく、「条件に合うノードごとに1つ」です。その条件がnodeSelectorで、GPU Operatorは、ここに自分のラベルをかけておき、ノード単位でオペランドをオン・オフします。desiredNumberScheduledは、DaemonSetコントローラーが「このDaemonSetが起動すべきだと判断したノード数」です。ラベルが合うノードが増えたり減ったりすると、この数字もそれに合わせて動きます。kubectl -n gpu-operator get ds -o wideで一目で見られます。
ドライバーだけが外れるノード
/root/gpuops/k8s/driver-ds.yamlに、nvidia-driver-daemonsetというDaemonSetを書いてください。同じネームスペース、Podのラベルとセレクターはapp: nvidia-driver-daemonset、nodeSelectorはnvidia.com/gpu.deploy.driver: "true"、イメージはnvcr.io/nvidia/driver:550.90.07-ubuntu22.04、メモリ要求は512Miです。適用したあと、GPUノードが2台あるのに、このDaemonSetだけが1台のノードで起動することを確認し、/root/gpuops/out/driver.txtに2行を書いてください。DESIRED=<숫자>とSKIPPED=<빠진 노드 이름>です(プレースホルダーは数字と、外れたノードの名前です)。
GPUノードが2台あるのに、ドライバーのDaemonSetは1台にしか起動しません。ラベルの値が"true"でなければ、nodeSelectorが合わないからです。falseと書くことと、ラベルをそもそも消すことは、人にとっては違って読めますが、nodeSelectorにとっては同じです。これが、「このノードには、すでにドライバーがインストールされている」を表現する方法です。ドライバーはカーネルモジュールを読み込むオペランドなので、ホストにすでにインストールされたドライバーと重なってはいけません。値は、出力を目で見て書き写すのではなく、-o jsonpathで受け取って書いてください。
デバイスプラグインとGFDを一緒に載せる
DaemonSetをさらに2つ作成してください。/root/gpuops/k8s/device-plugin-ds.yamlのnvidia-device-plugin-daemonsetは、nodeSelectorがnvidia.com/gpu.deploy.device-plugin: "true"、イメージがnvcr.io/nvidia/k8s-device-plugin:v0.16.2、メモリ要求が128Miです。/root/gpuops/k8s/gfd-ds.yamlのgpu-feature-discoveryは、nodeSelectorがnvidia.com/gpu.deploy.gpu-feature-discovery: "true"で、イメージもメモリ要求も同じです。どちらも、Podのラベルとセレクターはapp: <데몬셋 이름>です(プレースホルダーはDaemonSet名です)。適用したあと、2つのDaemonSetのdesiredNumberScheduledが、それぞれ2であることを確認してください。
デバイスプラグインは、kubeletにnvidia.com/gpuリソースをアドバタイズするオペランドで、GFDは、そのノードのGPUの事実をラベルに変えて付けるオペランドです。2つは同じイメージから出ていますが、役割が違うのでDaemonSetが別にあり、別にあるので、ラベルも別にあります。ドライバーを外したノードにも、この2つは起動する必要があります。ドライバーがホストにすでにあるからです。名前がnvidia-で始まらないオペランドもある点に注意してください。
ラベルを外すとPodが消える
/root/gpuops/k8s/dcgm-ds.yamlに、nvidia-dcgm-exporterというDaemonSetを書いてください。nodeSelectorはnvidia.com/gpu.deploy.dcgm-exporter: "true"、イメージはnvcr.io/nvidia/k8s/dcgm-exporter:3.3.7-3.5.0-ubuntu22.04、メモリ要求は128Mi、Podのラベルとセレクターはapp: nvidia-dcgm-exporterです。適用して2台のノードで起動することを確認したら、lab-node-1のnvidia.com/gpu.deploy.dcgm-exporterをfalseに変更し、Podが消えることを確認してください。/root/gpuops/out/optout.txtに2行を書いてください。BEFORE=<바꾸기 전 desiredNumberScheduled>とAFTER=<바꾼 뒤 값>です(プレースホルダーは、変更前のdesiredNumberScheduledと、変更後の値です)。
DaemonSetコントローラーは、nodeSelectorとノードのラベルを、常に突き合わせています。ラベルが条件から外れると、そのノードのPodを削除します。DaemonSetを変更していないのにPodが消える理由です。運用で、ノード1台だけをオペランドから外したいときに使う操作手段が、まさにこれです。公式ドキュメントには、ノードのすべてのオペランドを一度に外すnvidia.com/gpu.deploy.operands=falseもあります。2つの数字は、ラベルを変更する前と後に、それぞれ取得しておく必要があります。あとから一度に書くことはできません。
あるべきオペランドがすべてあるかを判定するツール
/root/gpuops/operand-audit.shを作成してください。クラスターのすべてのノードを回り、そのノードにnvidia.com/gpu.deploy.<이름>ラベルがtrueで付いているオペランドごとに(プレースホルダーはオペランド名です)、そのDaemonSetのPodがそのノードでRunningかを確認します。なければ<노드> MISSING <오퍼랜드이름>を1行出力し(プレースホルダーはノードとオペランド名です)、そのノードに欠けているものが1つもなければ<노드> OKを1行出力します(プレースホルダーはノードです)。1つでも欠けていれば終了コード1、そうでなければ0です。オペランド名とDaemonSet名の組は、ステップ1の5つです。作成したら、今のクラスターに対して実行して、出力を/root/gpuops/out/audit.txtに保存してください。ノード名をスクリプトの中に書いておかないでください。採点ツールが、ノードをもう1台作ってから呼び出します。
この判定は、「DaemonSetがいくつ望んでいるか」ではなく、「このノードに実際に起動しているか」で行う必要があります。この2つは違います。ラベルは合っているのに、テイントやリソースのせいでPodが起動できないノードが、実際にできます。ノードごとにkubectlを呼ぶと、ノードが増えるたびに遅くなります。ノード一覧とPod一覧をそれぞれ1回ずつ取得しておいて、jqでフィルタすれば、APIの呼び出しは2回で済みます。jqでラベルを読むときに//を使うと、値がfalseのラベルと、ないラベルが区別できません。
おかしな組み合わせを見つけ出す
誰かがlab-node-2に、nvidia.com/gpu.deploy.device-plugin=trueだけを手で付けたとします。そのラベルを実際に付け(他のdeployラベルは付けません)、デバイスプラグインのPodがそのノードで起動することを確認してください。そして/root/gpuops/order-check.sh <노드이름>を作成してください(プレースホルダーはノード名です)。そのノードでデバイスプラグインのPodはRunningなのに、コンテナツールキットのPodがなければ、device-plugin-without-toolkitの1行と終了コード1、それ以外の場合は、okの1行と0です。3台のノードに順番に実行して、/root/gpuops/out/order.txtに、<노드> <결과>の形で3行を書いてください(プレースホルダーはノードと結果です)。
コンテナツールキットがランタイムを登録して初めて、GPUコンテナがデバイスを見られます。デバイスプラグインだけが起動していると、リソースはアドバタイズされるのに、そのリソースを受け取ったPodはデバイスを掴めません。スケジューラーは成功したと言うのに、ワークロードは失敗するという、最も見つけにくい組み合わせです。そのため、「何が起動しているか」ではなく、「何と何が一緒に起動しているか」を見る必要があります。スクリプトは、引数で受け取ったノードだけを見て、判断は2つのDaemonSetのPodの数で行います。常に1を出力するスクリプトはだめです。採点ツールが、問題のないノードでも呼び出します。
ノードとオペランドの表を、実際の状態から取り出す
/root/gpuops/out/matrix.txtに、ノードごとに1行ずつ、3行を書いてください。形式は<노드> driver=<yes|no> toolkit=<yes|no> device-plugin=<yes|no> gfd=<yes|no> dcgm=<yes|no>で(プレースホルダーはノード名です)、yesは、そのオペランドのDaemonSetのPodがそのノードでRunningという意味です。行の順序は、lab-node-0、lab-node-1、lab-node-2です。そして4行目に、TOTAL_PODS=<gpu-operator 네임스페이스에서 Running 인 데몬셋 파드 총수>を書いてください(プレースホルダーは、gpu-operatorネームスペースでRunningのDaemonSetのPodの総数です)。数字とyes/noは、APIに問い合わせて埋めてください。前のステップの記憶で書くと、実際とずれます。
この表が、そのまま「今このクラスターのGPUスタックがどんな形か」です。障害が起きたときに最初に作るのがこの表で、表を作るコマンドを持っていれば、30秒で終わります。kubectl -n gpu-operator get pods -o jsonを1回実行すれば、必要なものがすべて入っています。.spec.nodeNameと.metadata.labels.appと.status.phaseを一緒に見てください。lab-node-2には、ステップ7で付けたラベルのせいで、デバイスプラグインだけが1つ起動している必要があります。