状態:ドラフト(2026-10-17)、一部実測。 基準バージョン:NVIDIA/gpu-operator v26.3.3(コミット b0a49c0e7b2e)、NVIDIA/k8s-device-plugin v0.19.3(570e776d6813)、NVIDIA/dcgm-exporter 4.5.3-4.8.2(691c92762eb5)。小さなクラスターがまさにこれらのバージョンを稼働させているため、実測は2026-10-17にそのクラスターから 読み取りのみ を行って取得しました(ClusterPolicy・デーモンセット・ノードラベル・ポッド構成の読み取り、APIサーバープロキシを通じた
/metricsの読み取り、実行中ポッド内のファイルの読み取り)。2026-10-16により長く測定した記録も併せて引用します。作成ルール:実測データを基本とし、測定できなかったものは「未測定」、確認できなかったものは「未確認」と表示します。
目次
- 1. 一行要約:GPUノード向けの「設置担当者 + チェックリスト」
- 2. 構成図:状態のリストとノード内の準備チェーン
- 3. コードマップ
- 4. コアパスを辿る
- 5. 筆者の貢献と続く箇所
- 6. 実測:小さなクラスターでの読み取り結果
- 7. どこから読むべきか
- 8. 最初に貢献しやすい場所
- 9. 制限事項
- 参考
1. 一行要約:GPUノード向けの「設置担当者 + チェックリスト」
KubernetesでGPUを使用するには、ノードごとに複数のレイヤーがかみ合う必要があります。カーネルドライバ、コンテナがGPUデバイスを認識できるようにするコンテナツールキット、kubeletに nvidia.com/gpu リソースを通知するデバイスプラグイン、GPUの仕様をノードラベルとして付与するGPU Feature Discovery(GFD)、指標をエクスポートするdcgm-exporter。1つでも順序が狂うと、ポッドは Init の状態で停止するか、GPUを見つけられなくなります。
GPU Operatorは、これらのレイヤーを 設置担当者のように順にインストールし、 チェックリストのように前の段階が完了したことを確認してから次の段階に進みます。ユーザーが使用するのは ClusterPolicy という1つのCRのみです。
| コンポーネント | 役割 | 動作場所 |
|---|---|---|
| gpu-operator(オペレータ) | ClusterPolicyを確認して状態のリストを順次適用 | Deployment 1つ |
| NFD(node-feature-discovery) | PCIデバイスを確認し、 feature.node.kubernetes.io/pci-10de.present などのラベルを付与 |
マスターDeployment + ワーカーデーモンセット(全ノード) |
| driver | カーネルモジュールをインストール(コンテナドライバ)。ホストにドライバがある場合は無効化 | GPUノードデーモンセット |
| container-toolkit | ランタイムの設定、CDI spec(management.nvidia.com)の生成 |
GPUノードデーモンセット |
| operator-validator | ドライバ・ツールキット・CUDA・プラグインが実際に動作するかを確認し、「準備完了ファイル」を残す | GPUノードデーモンセット |
| device plugin | nvidia.com/gpu 広告・割り当て、時分割複製、CDI spec(k8s.device-plugin.nvidia.com) |
GPUノードデーモンセット |
| GFD | GPUの数・複製数・共有方式などをノードラベルとして付与 | GPUノードデーモンセット |
| dcgm-exporter | DCGMの値をPrometheus形式で、ポッドラベルを付与して出力 | GPUノードデーモンセット |
2. 構成図:状態のリストとノード内の準備チェーン
図の読み方:
- 上部はオペレータ側です。「状態(state)」の1つはリポジトリの
assets/下にある1つのフォルダであり、そのフォルダ内のYAML(ServiceAccount、RBAC、ConfigMap、DaemonSet…)がひとまとめにして適用されます。番号は実際の適用順序であり、点線の枠は小さなクラスターの設定で無効化されている状態です。 - 下部はGPUノード内です。オペレータが順序を定めてもデーモンセットは同時に起動するため、 ノード内の順序はファイルで 合わせます。バリデータが
driver-ready,toolkit-ready…を残し、他のデーモンセットのinitContainerはそのファイルが生成されるまで待機します。 - オペレータは「デーモンセットの準備ができたか」のみを確認し、「GPUが実際に動作するか」はノード内のバリデータが確認します。役割が2つに分かれていることが本設計の核心です。
3. コードマップ
3つのリポジトリを合わせて見ていきます。最上位の中から重要な部分のみを抜粋しました。
| パス | 内容 | 最初に読むべきファイル |
|---|---|---|
gpu-operator api/ |
ClusterPolicy・NVIDIADriver CRDのGo型定義 | api/nvidia/v1/clusterpolicy_types.go |
gpu-operator controllers/ |
リコンサイルループ、状態管理、オブジェクトごとの適用・変換 | clusterpolicy_controller.go, state_manager.go |
gpu-operator assets/ |
状態ごとのマニフェストバンドル(state-device-plugin/0500_daemonset.yaml など) |
state-operator-validation/0500_daemonset.yaml |
gpu-operator cmd/nvidia-validator/ |
ノード内バリデータ(ドライバ・ツールキット・CUDA・プラグイン) | main.go |
k8s-device-plugin internal/plugin/ |
kubelet device plugin gRPCサーバー | server.go |
k8s-device-plugin internal/rm/ |
デバイスリストの作成、時分割複製(::N) |
device_map.go, devices.go |
k8s-device-plugin internal/cdi/ |
CDI specの生成・保存 | cdi.go |
dcgm-exporter internal/pkg/transformation/ |
指標へのpod・namespaceラベルの付与 | kubernetes.go |
dcgm-exporter internal/pkg/server/ |
/metrics 処理、変換の適用 |
server.go |
controllers/object_controls.go は5,000行を超えています。最初から読まず、下記のパスで名前が登場した際にその関数のみを探して読む方が効率的です。
4. コアパスを辿る
4.1 リコンサイルループ:状態を1つずつ「一歩ずつ進める」
- オペレータがClusterPolicyを受信すると、
controllers/clusterpolicy_controller.go:93Reconcileが稼働します。ClusterPolicyはクラスター内に1つのみ認められ(2つ目以降はIgnored)、初回であればinitを呼び出します。 controllers/state_manager.go:854initはGPUノードを探してラベルを付与し(state_manager.go:521labelGPUNodes)、状態リストを登録します。GPUノードであるかはNFDラベルfeature.node.kubernetes.io/pci-10de.present=true(10deはNVIDIAのPCIベンダーID)で判定し、そのようなノードにnvidia.com/gpu.present=trueとnvidia.com/gpu.deploy.<구성요소>=trueのラベルを付与します。各デーモンセットのnodeSelectorがまさにこのラベルです。- 登録は
state_manager.go:175addStateがフォルダパスごとにcontrollers/resource_manager.go:102addResourcesControlsを呼び出し、YAMLのkindを確認して「このオブジェクトを適用する関数」のリストを作成します。1つの状態 = 1つの関数リストです。 Reconcileはstate_manager.go:1051stepを最後の状態まで繰り返します。stepはその状態の関数をすべて実行し、1つでも準備ができていなければその状態をNotReadyに示して 次の状態に進みます。最後まで実行した後に準備ができていない状態があれば、5秒後に再度実行されます。
for _, fs := range n.controls[n.idx] {
stat, err := fs(*n)
if err != nil {
return stat, err
}
// successfully deployed resource, now check if its ready
if stat != gpuv1.Ready {
result = stat
}
}
// move to next state
n.idx++
出所:NVIDIA/gpu-operator v26.3.3 controllers/state_manager.go(step の一部)、Apache-2.0。
ここで設計上の選択が見えてきます。 前の状態の準備ができるのを待って処理をブロックすることはありません。 デーモンセットはとりあえずすべて作成し、実際の順序はノード内の準備ファイルによって合わせます(4.2)。オペレータがノードごとに待機するように作ってしまうと、1つのノードの処理が遅延した際にクラスター全体の適用が停止してしまうためです。
- デーモンセットを適用する関数は
controllers/object_controls.go:4621DaemonSetです。状態が無効化されている場合はオブジェクトを削除し、Disabledを返します。有効な場合はobject_controls.go:728preProcessDaemonSetがデーモンセット名に応じた 変換関数を選択します(nvidia-device-plugin-daemonset→TransformDevicePlugin,nvidia-dcgm-exporter→TransformDCGMExporter…)。ClusterPolicyのイメージ・環境変数・設定がYAMLに埋め込まれるのはこれら変換関数の中です。準備ができているかどうかはobject_controls.go:3989isDaemonSetReadyが確認します。 - 新しいGPUノードが参加すると、
clusterpolicy_controller.go:246addWatchNewGPUNodeのノード監視がリコンサイルを再び呼び起こします。
4.2 ノード内の準備チェーン:バリデータと「-ready」ファイル
バリデータは cmd/nvidia-validator/main.go の1つのファイルにあります。状態ファイルを配置する場所は /run/nvidia/validations(146行目 defaultStatusPath)であり、ファイル名が161〜189行目に定数としてまとめられています(driver-ready, toolkit-ready, cuda-ready, plugin-ready …).
operator-validatorデーモンセットのinitContainerが driver → toolkit → cuda → plugin の順序で検証し、ファイルを残します(assets/state-operator-validation/0500_daemonset.yaml).- 他のデーモンセットはファイルを 待ち続ける だけです。デバイスプラグインのinitContainer
toolkit-validationはtoolkit-readyが生成されるまで5秒ごとに確認する1行のシェルスクリプトです(assets/state-device-plugin/0500_daemonset.yaml:29). - ドライバの検証は
main.go:810runValidationから始まります。まずホストにインストールされたドライバを確認し(main.go:745validateHostDriver)、失敗した場合はドライバコンテナ側を待機します(ズレ)。main.go:765validateDriverContainer).
ファイルで繋ぐ方式は単純ですが大きなメリットがあります。ノードが再起動されると /run が空になるため、検証が自然と最初から再び実行され、管理者がノード上で ls /run/nvidia/validations を1回実行するだけでどこで停止したかを確認できます。
4.3 デバイスプラグイン:時分割複製とCDI spec
デバイスプラグインは、kubeletのdevice plugin APIを実装したgRPCサーバーです。フローは3つの関数に要約されます。
internal/plugin/server.go:229Register— kubeletソケットに「nvidia.com/gpuを自分が担当する」と登録。server.go:267ListAndWatch— デバイスリスト(健康状態を含む)をkubeletにストリーミング。server.go:306Allocate— ポッドにデバイスを割り当てる際、コンテナに渡すべき環境変数・デバイス・CDI名を返却。
時分割(time-slicing) は2番目のステップで発生します。設定に replicas: 5 がある場合、 internal/rm/device_map.go:286
updateDeviceMapWithReplicas は1枚のGPUを GPU-<uuid>::0 … ::4 5つの「デバイス」に拡張して広告します。 ::N を付け外しする作業は
internal/rm/devices.go:243 NewAnnotatedID 付近の小さな型が担当します。kubeletはこれが同じGPUであることに気づかず、5つのポッドに1つずつ割り当てます。メモリの分離はありません。この ::N が第5節の問題に繋がります。
CDI spec は、コンテナランタイムに対して「このデバイスを渡すには、このデバイスファイルとこのライブラリを含めよ」と記述したファイルです。
internal/cdi/cdi.go:187 CreateSpecFile が /var/run/cdi(41行目 cdiRoot)にspecを保存し、この関数はプラグインの起動時に一度だけ呼び出されます(cmd/nvidia-device-plugin/plugin-manager.go:84)。specにはライブラリファイル名が ドライババージョンまで付いた状態で 含まれます。そのため、ホストのドライバのみを更新してプラグインを再起動しない場合、specが指すライブラリと実際にロードされているカーネルモジュールのバージョンが食い違う可能性があります。プラグインはkubeletの再起動・設定変更・シグナルを受信すると再起動するため(main.go start のrestartループ)、ドライバの更新後はノードの再起動またはプラグインポッドの再起動が実質的に必要となります。
4.4 dcgm-exporter:「このGPUは誰のものか」
dcgm-exporter は DCGM から GPU ごとの値気を取り出して /metrics に出力します。 DCGM_EXPORTER_KUBERNETES=true の場合、各行に pod·namespace·container
ラベルを付与しますが、その情報を Kubernetes API ではなく kubelet の pod-resources gRPC(デフォルトは /var/lib/kubelet/pod-resources/kubelet.sock)から取得します。
「このコンテナにどのデバイス ID が割り当てられたか」を知っているのは kubelet だからです。
/metricsリクエストが来ると、internal/pkg/server/server.go:249Metrics→renderが 変換リストを巡回します(server.go:309transformation.Process). スクラップごと に巡回します)。internal/pkg/transformation/kubernetes.go:296getMappingsがソケットに接続し、kubernetes.go:533listPodsに向かってList()を呼び出します。- デフォルト(
--kubernetes-virtual-gpus無効)の状態では、kubernetes.go:712toDeviceToPodが 「デバイス ID → 単一のポッド」のマップを作成します。時分割デバイス ID のGPU-<uuid>::Nは、::の前部分を切り捨てて GPU UUID をキーとして使用します。
} else if strings.Contains(deviceID, "::") {
gpuInstanceID := strings.Split(deviceID, "::")[0]
// (디버그 로그 생략)
deviceToPodMap[gpuInstanceID] = podInfo
}
出典: NVIDIA/dcgm-exporter 4.5.3-4.8.2 internal/pkg/transformation/kubernetes.go 887〜899行目の要約、Apache-2.0。
同じ GPU を 3 つのポッドで共有している場合、3 つのポッドが順に 同じキーを上書きします(kubernetes.go:898)。
結果として、レスポンスの最後に出現したポッドが勝つことになります。また、そのレスポンスの順序は kubelet がポッドのリストを Go のマップから取り出して作成するため、呼び出すたびに変動します
(kubelet v1.34 pkg/kubelet/pod/pod_manager.go におけるマップの走査、ソースに基づく)。
コード 1 行の「後勝ち(last-wins)」とレスポンス順序のランダム性が組み合わさることで、 スクラップごとにラベルが変化します。
--kubernetes-virtual-gpus(環境変数 KUBERNETES_VIRTUAL_GPUS)を有効にすると、 kubernetes.go:662
toDeviceToSharingPods が 1 つの GPU にポッドの リストを紐付け、デバイス指標をポッドごとに 1 行ずつ複製します(一部のフィールドはプロセスごとの値に分割)。
GPU Operator のデフォルトのデプロイではこの値は有効化されません。
5. 筆者の貢献と関連する箇所
- NVIDIA/gpu-operator#3086(Issue #3085、レビュー待ち): ホストドライバーとライブラリのバージョンが不一致の際、検証ツールのログが
「ホストにドライバーがない」と出力し、リトライ警告に理由が存在しなかった問題です。4.2 の
runValidation(810行目)がvalidateHostDriverの失敗を 「なし」として記録する行(817行目)と、validateDriverContainerのリトライ警告(802行目、末尾にリテラルな\nが出力される)を修正します。 障害に関する経緯は 自動更新が GPU を停止させた日 に記載されています。 - NVIDIA/cloud-native-docs#524(レビュー待ち): 同様の「ドライバー/ライブラリのバージョン不一致」をトラブルシューティングドキュメントに追記する PR です。
- dcgm-exporter の時分割ラベルの揺らぎ(準備中、掲載前): 4.4 の
toDeviceToPodをポッド名の昇順で処理するようにして、同じポッドが一貫して選択されるようにする提案です。デフォルトの挙動を変更するものであるため、まず Issue で意見を問う予定であり、現在はドラフトのみが存在します。
6. 実測: 小規模クラスターでの観測結果
2026-10-17、すべて読み取りコマンドです(kubectl get、実行中のポッド内 ls·grep、API サーバープロキシ kubectl get --raw …/proxy/metrics)。
ノードは役割ごとに、ポッドは P・Q・R でマスキングしています。
ClusterPolicy と DaemonSet。
| 項目 | 読み取った値 |
|---|---|
| ClusterPolicy のステータス | state: ready、条件 Reconciled(2026-10-14 から) |
driver.enabled |
false — ホストにインストールされたドライバーを使用(ステータス 3 でドライバー無効) |
dcgm.enabled / dcgmExporter.enabled |
false / true — 別途 DCGM を使用せず exporter 内のエンジンを使用 |
cdi.enabled |
true |
migManager.enabled, sandboxWorkloads.enabled |
両方とも false |
devicePlugin.config |
デフォルト no-sharing、設定バンドル名 time-slicing-config |
| DaemonSet | 望ましい数 / 準備完了数 | nodeSelector |
|---|---|---|
| NFD worker | 8 / 8 | なし(すべてのノード) |
| container-toolkit | 5 / 5 | nvidia.com/gpu.deploy.container-toolkit=true |
| operator-validator | 5 / 5 | …deploy.operator-validator=true |
| device-plugin | 5 / 5 | …deploy.device-plugin=true |
| mps-control-daemon | 0 / 0 | 上記 + nvidia.com/mps.capable=true(該当ノードなし) |
| dcgm-exporter | 5 / 5 | …deploy.dcgm-exporter=true |
| gpu-feature-discovery | 5 / 5 | …deploy.gpu-feature-discovery=true |
8 台のノードのうち GPU ノードは 5 台であり、5 台すべてに nvidia.com/gpu.present=true と gpu.deploy.* の 8 つのラベルが付与されていました(4.1 のステップ 2)。
検証の連鎖。 operator-validator ポッドの initContainer は driver-validation → toolkit-validation → cuda-validation → plugin-validation
の順序であり、device plugin ポッドは toolkit-validation → config-manager-init、dcgm-exporter ポッドは toolkit-validation が 1 つでした。図の下部の連鎖そのものです。
時分割。 1 台の GPU ノード(GPU ノード B)のみが nvidia.com/device-plugin.config=shared-5 ラベルにより異なる設定を使用していました。その設定は
replicas: 5, failRequestsGreaterThanOne: true(1 つのポッドが複数のスロットを占有しないように制限)、 renameByDefault: false であり、結果は次のようになります。
| GPU ノード | gpu.sharing-strategy |
gpu.replicas |
allocatable nvidia.com/gpu |
|---|---|---|---|
| B | time-slicing | 5 | 5 |
| 残りの 4 台 | none | 1 | 1 |
1 枚の GPU(24GB 相当)が kubelet には 5 枚として認識されます。ラベル gpu.replicas·gpu.sharing-strategy は GFD によって付与されたものです。
CDI spec。 device plugin ポッド内で /var/run/cdi を確認したところ、2 つのファイルが存在していました。プラグインによって作成された k8s.device-plugin.nvidia.com-gpu.json と
ツールキットによって作成された management.nvidia.com-gpu.yaml です。プラグイン spec の更新日時は 5 台のノードすべてにおいて そのプラグインポッドが起動した時刻であり(4.3 の
「起動時に一度」)、spec 内の libnvidia-ml.so.<판> のバージョンと /proc/driver/nvidia/version のカーネルモジュールバージョンは本日は 5 台のノードすべてで同一でした。
ノードごとに spec の日付が 9 月初旬から 10 月中旬まで分散していることから、ドライバーを更新したノードではその後にプラグインも再起動されたことがわかります。
dcgm-exporter のポッドラベル。 GPU ノード B の exporter の /metrics を 10 秒間隔で 6 回読み取りました。1 回あたり 15 個の指標(設定ファイル
dcp-metrics-included.csv)が GPU ごとに 1 行ずつ出力され、そのノードで GPU を要求して実行中だったポッドは 3 つでした。
| スクラップ | 1 | 2 | 3 | 4 | 5 | 6 |
|---|---|---|---|---|---|---|
DCGM_FI_DEV_GPU_UTIL の pod |
P | Q | P | R | Q | R |
5 回の遷移の間、5 回すべて変化しました。2026-10-16 に同じノードを 2 秒間隔で 30 回読み取った際は、3 つのポッドがそれぞれ 19・8・3 回(2 回目の実行時は 19・10・1、連続する
2 回の間での変化は 12 回)であり、Prometheus(15 秒間隔)に蓄積された 1 時間分のデータでは 1 つの GPU が 3 つの時系列に分割されて 129・76・35 サンプルとなり、24 時間の間に異なる
exported_pod の値が 7 種類存在していました。ダッシュボードでポッドごとにグループ化すると線がまばらに途切れる原因はこれです。
7. どこから読み始めるべきか
controllers/clusterpolicy_controller.go:93Reconcile— 150 行以内に全体像のフローが記載されています。controllers/state_manager.go:854initと 1051 行目のstep— ステータス一覧と詳細なステップ。assets/state-device-plugin/フォルダー — 「個別のステータス」がどのような YAML バンドルであるかを視覚的に確認。object_controls.go:1520TransformDevicePlugin— ClusterPolicy の値が YAML に組み込まれる実際の例。internal/plugin/server.goのRegister·ListAndWatch·Allocate— device plugin API の 3 つの関数。internal/pkg/transformation/kubernetes.go:296からProcessまで — 指標にポッドが結び付くまでのプロセス。
8. 初めてのコントリビュートに最適な箇所
- dcgm-exporter の README に Kubernetes フラグの説明が記載されていません。
pkg/cmd/app.goには環境変数名を持つフラグが 38 個ありますが、 README は--kubernetes-virtual-gpusや pod-resources ソケットのパスについて言及していません。さらに、環境変数名が混在しています。大半はDCGM_EXPORTER_で 始まりますが、7 個はこのプレフィックスに従っておらず、そのうちKUBERNETES_VIRTUAL_GPUS,KUBERNETES_ENABLE_DRA,NVIDIA_RESOURCE_NAMESなどの 4 つにはDCGM_すら存在しません (pkg/cmd/app.go:303)。1 つの表にまとめるドキュメントの PR は良い第一歩になります。 - device plugin の CDI spec がドライバーの更新を認識できません。 4.3 のように、spec はプラグインの起動時にのみ作成されます。「ドライバーの更新後はプラグインを再起動する必要がある」ことを ドキュメントに記載するか、ドライバーのバージョンの変更を検知して spec を再作成する仕組みについては、まず Issue で意見を問う価値があります。
- gpu-operator の TODO。
stepの先頭にある TODO(他のコントローラーが所有するオブジェクトを変更する前に OwnerRef を確認する)、transformValidationInitContainerの 「TransformValidatorComponentとの重複」に関する TODO(object_controls.go:3901)は 変更の範囲が小さく、テスト(transforms_test.go)が既に充実しているため着手しやすくなっています。 - ルール: gpu-operator は CLA を必要とせず、コミット署名(
Signed-off-by)のみを要求します(CONTRIBUTING.md)。外部からの PR は NVIDIA 側の CI 検証に長い時間がかかります。
9. 制限事項
- driver・MIG・vGPU・サンドボックス関連のステータスは小規模クラスターで無効になっていたため、コードの読み取りのみを行いました(未実測)。NVIDIADriver CRD のパスについても扱っていません。
--kubernetes-virtual-gpusを有効にした場合のラベルについては測定していません(未実測)。有効化した場合に指標の行数がポッド数に応じて増加するコストについても未実測です。- CDI spec が実際に不整合を起こした状態でポッドがどのように失敗するのかについては、今回は再現していません(パート 2 の障害ノートの記録のみ)。
- 検証ツールによる CUDA 検証で起動されるワークロードポッドや MPS のパスについては扱っていません。
ティーティの一言。 1台の卓球台を5人で交代して使っているのですが、スコアボードの所有者欄には「最後尾に並んだ人」の名前が書かれるんです。並ぶ順番が毎回変わるから、所有者も毎回変わります。名前順に並べるだけでも静かになるはずなのに!
参考文献
閲覧日はすべて 2026-10-17 です。
- NVIDIA/gpu-operator v26.3.3:
controllers/,assets/,cmd/nvidia-validator/main.go,CONTRIBUTING.md - NVIDIA/k8s-device-plugin v0.19.3:
internal/plugin/server.go,internal/rm/device_map.go,internal/cdi/cdi.go,api/config/v1/replicas.go - NVIDIA/dcgm-exporter 4.5.3-4.8.2:
internal/pkg/transformation/kubernetes.go,internal/pkg/server/server.go,pkg/cmd/app.go - kubernetes/kubernetes v1.34.1:
pkg/kubelet/apis/podresources/server_v1.go,pkg/kubelet/pod/pod_manager.go - 公開 PR・Issue: NVIDIA/gpu-operator#3085・#3086、NVIDIA/cloud-native-docs#524
- 引用したコード断片は上記のレポジトリのものであり、すべて Apache License 2.0 です。記事内では短縮して掲載しています。