Skip to content
Published on

デバイスプラグインとGPUスケジューリング — nvidia.com/gpuはどこから来るのか

シェア
Authors

はじめに — nvidia.com/gpuはどこから来るのか

kubectl describe nodeを叩くとGPUノードにはnvidia.com/gpuという行があり、その横に数字が付いています。一見するとKubernetesがGPUを認識したように見えますが、実際は逆です。Kubernetesのコアには GPU という概念がありません。あの行は、ノード上で動いている一つのプロセスがkubeletに「この名前のものが自分に八つある」と伝えた結果にすぎません。

そのプロセスがデバイスプラグインです。本記事ではその登録の道筋と、そこから派生するスケジューリングの制約を見ます。GPU Podがpendingに留まるとき、どこを見るべきかがここで決まります。

メトリクス名と設定は2026-08-12に公式ドキュメント・リポジトリで確認しました。バージョンによって異なる場合があるため、使用中のバージョンで再確認してください。

デバイスプラグインの仕事

Kubernetesのドキュメントは、デバイスプラグインが実装すべきgRPCサービスを明示しています。インターフェースは五つのメソッドです。

service DevicePlugin {
      rpc GetDevicePluginOptions(Empty) returns (DevicePluginOptions) {}
      rpc ListAndWatch(Empty) returns (stream ListAndWatchResponse) {}
      rpc Allocate(AllocateRequest) returns (AllocateResponse) {}
      rpc GetPreferredAllocation(PreferredAllocationRequest) returns (PreferredAllocationResponse) {}
      rpc PreStartContainer(PreStartContainerRequest) returns (PreStartContainerResponse) {}
}

このうちGetPreferredAllocationPreStartContainerは必須ではありません。kubeletがまずGetDevicePluginOptionsを呼び、どの任意機能があるかを確認します。

登録はUnixソケット経由です。プラグインは/var/lib/kubelet/device-plugins/kubelet.sockへ接続して自身を登録し、自分自身のgRPCサーバも同じディレクトリ配下のソケットとして開きます。ドキュメントはこのパスがハードコードされており、kubeletの--root-dirなどの設定に影響されないと明言しています。つまりこのファイルが無い、あるいはプラグインPodがこのディレクトリをマウントできない場合、登録自体が起こりません。

リソース名はvendor-domain/resourcetype形式に従う必要があり、だからNVIDIA GPUはnvidia.com/gpuになります。名前にベンダードメインが入るのは規則であって慣習ではありません。

プラグインの挙動はいくつかのフラグで調整されます。リポジトリのドキュメントで確認したもののうち知っておく価値があるのは次のとおりです。--mig-strategyは環境変数MIG_STRATEGYに対応し既定値はnoneです。--fail-on-init-errorは既定値が真なので初期化に失敗するとプラグインがそのまま死にますが、この挙動が重要です。静かに生きたままリソースを広告しないより、死んで再起動を繰り返すほうが診断しやすいからです。ほかに、デバイス一覧をコンテナへ渡す方式を決める--device-list-strategyが既定値envvarを、デバイス識別子の方式を決める--device-id-strategyが既定値uuidを持ちます。

デバイスの状態は二つだけです。HealthyとUnhealthyです。 プラグインがListAndWatch応答であるデバイスを異常と示すと、kubeletはそのリソースのノードallocatableを減らします。ドキュメントが次に明確にする点が重要です。capacityの値は変わりません。 この非対称が診断に効きます。capacityが8でallocatableが7なら、カードが一枚死んでおり、プラグインは既にそれを知っているということです。

requestsとlimitsの規則

CPUに慣れた人がGPUで最初に間違えるのがここです。Kubernetesの規則は三行です。

  • limitsだけ書いてrequestsを省略できます。Kubernetesがlimit値をrequestとして使います。
  • 両方書くこともできますが、二つの値は必ず一致していなければなりません。
  • requestsだけ書いてlimitsを省略することはできません。

つまりGPUは常にGuaranteedに相当する形でしか要求されません。CPUでよく使う「requestは低く、limitは高く」という戦略は通用しません。

apiVersion: v1
kind: Pod
metadata:
  name: gpu-single
spec:
  restartPolicy: OnFailure
  containers:
    - name: worker
      image: 'nvcr.io/nvidia/k8s/cuda-sample:vectoradd-cuda12.5.0-ubuntu22.04'
      resources:
        limits:
          nvidia.com/gpu: 1

なぜGPUはCPUのように分けて使えないのか

Kubernetesのドキュメントは、GPUがオーバーコミットできず、共有できず、小数単位に分けられないと明言しています。理由は二つの層にまたがります。

一つ目はリソースモデルの層です。拡張リソースは整数単位のリソースです。CPUのミリコアに相当する概念がそもそも定義されていません。だからnvidia.com/gpu: 0.5は構文エラーというより、スケジューラが解釈できない値です。

二つ目はハードウェアとドライバの層です。CPU時間はカーネルスケジューラがプロセス単位で強制的に分割でき、メモリはcgroupで上限を掛けられます。GPUには既定でそのどちらもありません。同じカードに二つのコンテナが載ると、VRAMは先に掴んだ側が持っていき、カーネル実行は互いに押し合います。片方がメモリを使い切れば、もう片方は単に失敗します。隔離が無いままリソース数だけ割ると、スケジューラの約束をハードウェアが守れません。

そのためGPUの共有はスケジューラ設定ではなく別の技術で解かれます。時間を分けるtime-slicingとハードウェアを分けるMIGですが、この二つは隔離の水準がまったく異なります。次回の主題がまさにこれです。

ノードラベルでGPUを選ぶ

カード種別が混在するクラスタでは、リソース数だけでは足りません。GPU Feature Discoveryがノードに付けるラベルを使います。リポジトリのドキュメントで確認したラベルのうち実務で頻繁に使うのは、nvidia.com/gpu.productnvidia.com/gpu.countnvidia.com/gpu.memorynvidia.com/gpu.familynvidia.com/gpu.machinenvidia.com/cuda.driver-version.fullnvidia.com/cuda.runtime-version.fullnvidia.com/gpu.compute.majornvidia.com/gpu.compute.minornvidia.com/mig.capablenvidia.com/gpu.sharing-strategynvidia.com/gfd.timestampあたりです。

# GPUノードのラベルとリソースを確認
kubectl get nodes -L nvidia.com/gpu.product,nvidia.com/gpu.count,nvidia.com/gpu.memory

kubectl describe node <node-name> | sed -n '/Capacity/,/System Info/p'

# プラグインが登録されたかをkubeletソケットディレクトリで確認
kubectl debug node/<node-name> -it --image=busybox -- ls /host/var/lib/kubelet/device-plugins/

ラベルをスケジューリングに使う方法はごく普通のnodeSelectorです。

apiVersion: v1
kind: Pod
metadata:
  name: gpu-on-specific-card
spec:
  restartPolicy: OnFailure
  nodeSelector:
    nvidia.com/gpu.product: Tesla-T4
  containers:
    - name: worker
      image: 'nvcr.io/nvidia/k8s/cuda-sample:vectoradd-cuda12.5.0-ubuntu22.04'
      resources:
        limits:
          nvidia.com/gpu: 1

ラベル値の正確な文字列はカードごとに異なります。推測せず、上のkubectl get nodes -Lで実際の値を先に確認するほうが常に速いです。

おわりに — pendingの原因はたいてい三つのどれか

GPU Podが起動しないとき、原因はほぼ常に三つのどれかにあります。ノードがそもそもリソースを広告していない(プラグイン未登録)、広告はしているが空きが無い(他のPodが占有)、ラベル条件がどのノードとも一致しない、の三つです。いずれもkubectl describe nodekubectl describe podの二つで数秒で切り分きます。

まとめるとこうです。デバイスプラグインはリソースの存在を作り、拡張リソースの規則はそれを整数に固定し、GFDラベルはそのどれかを選ばせます。 一枚を複数で分け合いたいという要求はこの三層のどこでも解かれず、だから別の技術が必要になります。次回はその二つを比較します。

試してみる

シリーズ

参考資料