TT Lab
ブログ

한국어 English 日本語 中文

はじめる
ブログ

ブログ

GPU Operator スタックの解剖とトラブルシューティング

ステータス: ドラフト(2026-10-07 更新)、一部実測値を反映。 公式ドキュメントで確認した記述がベースであり、**第5節の containerd が SIGHUP によって再起動される経路(使い捨て仮想マシンで直接確認)**と、ノードの containerd バージョンおよびユニット設定を読み取った部分のみが直接確認した値です。残りはドキュメントを基準としています。 記事の終わりの「確認したバージョンと日付」および「確認できなかったこと」を先にお読みください。 作成ルール: 実測データを基本とし、計測できなかったものは「未計測」と表示します。

目次

概要

GPU Operator は、GPU ノードごとに手動で行っていたドライバ、コンテナランタイムの設定、デバイスプラグイン、メトリック収集エージェントのインストールを ClusterPolicy という1つのオブジェクトで宣言できるようにしたオペレーターです。この記事では、構成要素の役割と失敗時の挙動、Pod が GPU を受け取るまでに通過する2つの関門、症状別の診断手順、キャパシティプランニングの罠を順番に解説します。

この記事を読むことで面接で説明できるようになること

  1. オペレーターが起動する各 DaemonSet の役割と依存順序、失敗時にエラーが露見する場所
  2. スケジューラーが確認する拡張リソースと、ランタイムが確認するハンドラー・CDIという2つの関門、そして CDI デフォルトに変更された現在の経路
  3. Insufficient nvidia.com/gpu が異なる原因から同じ文言として出力される理由とその見分け方

1. 構成要素: 何が何をするのか

オペレーターはノードラベルを使って、どのノードに何をデプロイするかを決定します。NFD(Node Feature Discovery)が PCI ベンダーラベル(feature.node.kubernetes.io/pci-10de.present など)を付与すると、オペレーターが GPU ノードと判定して nvidia.com/gpu.present と nvidia.com/gpu.deploy.<이름>=true を付与し、各 DaemonSet はそのラベルを nodeSelector として読み込みます(オペレーターのソースコードを確認)。順序の依存関係は init コンテナが担当します。ツールキットは driver-validation、デバイスプラグインは toolkit-validation を先に通過しなければ起動しません。

構成要素 役割 失敗時の挙動
NFD, GFD ハードウェアの情報をラベル化する。GFD は gpu.product·gpu.count など ラベルがない場合や、テイントに耐えられない場合はエラーなしで DaemonSet の DESIRED が 0 になる
ドライバ カーネルモジュール。コンテナまたはホストに事前インストール nvidia-smi 失敗、他の Pod が Init 状態で待機(nouveau の競合を含む)
Container Toolkit nvidia ランタイムハンドラーを containerd の設定に登録 no runtime for "nvidia" is configured
Device Plugin kubelet への登録、デバイスリストの広告、 Allocate, XID の監視 nvidia.com/gpu なし、または減少
DCGM Exporter メトリックの公開 分離された DCGM に接続できない場合は CrashLoopBackOff
MIG Manager MIG プロファイルの適用 MIG 非対応の GPU では無意味
Validator driver, toolkit, cuda, plugin の順で検証 NVSwitch ノードで Fabric Manager が存在しない場合 system not yet initialized

Fabric Manager は、NVSwitch システム(HGX など)において GPU 間メモリファブリックを構築するための別サービスです。オペレーターのドライバコンテナは NVSwitch を検出するとこれをインストールし、ホストのドライバを使用している場合は手動でインストールする必要があります。

2. Pod が GPU を取得するまで

flowchart TD
  P["디바이스 플러그인<br/>kubelet 소켓에 등록, 장치 목록 전달"] --> N["kubelet<br/>노드 status 의 capacity·allocatable 갱신"]
  N --> S["kube-scheduler (NodeResourcesFit)<br/>요청 ≤ allocatable − 이미 배정 ?"]
  S --> A["kubelet: Allocate 호출<br/>응답: 장치 노드·환경변수·마운트·CDI 이름"]
  A --> R["containerd<br/>CDI 스펙 적용 또는 nvidia 런타임이 OCI 스펙 수정"]
  R --> C["runc 가 컨테이너 시작"]

関門 1 は数値です。 スケジューラーはデバイスではなく、ノード status の数値を参照します。GPU は limits にのみ使用され(requests を使用する場合は同じ値である必要がある)、整数となります。プラグインとの接続が切断されると、kubelet はデバイスを unhealthy に変更して allocatable を 0 に下げ、5分の猶予期間が過ぎるとリソース名を削除します(kubelet のソースコード)。ヘルス監視によって1台のデバイスが unhealthy になっても capacity はそのまま残り、allocatable のみが減少します(公式ドキュメント)。

関門 2 はインジェクション方式です。 以前は nvidia-container-runtime が runc をラップして prestart フックで nvidia-container-cli を呼び出す方式でしたが、オペレーター 25.10.0 からは CDI(Container Device Interface)がデフォルトとなっています。通常のワークロードには runtimeClassName は不要です。ただし、オペレーター自体の operand Pod はコントローラーが runtimeClassName: nvidia を付与するため(NRI プラグイン使用時を除く、ソースコードで確認)、ハンドラーが消失するとプラグインすら起動できなくなり広告が途絶えます。

3. インストール方式の選択

方式 メリット コスト
ドライバーコンテナ オペレーターがアップグレードステートマシン(cordon、待機、GPUポッド削除、オプションのdrain)として管理 すべてのGPUノードのOSバージョンを揃える必要がある。カーネルが上がったらドライバーを再調整する必要がある
ホスト事前インストール (driver.enabled=false) OSが混ざっていてもよい オペレーターはホストドライバーのライフサイクルを管理しない
事前コンパイル済みイメージ ノードでビルドしない タグが <브랜치>-<커널>-<OS> 形式であるため、カーネルバージョンが一致するイメージが必要である

ドライバーはカーネルモジュールであるため、置き換える前にGPUクライアントをすべて停止する必要があります。ドキュメント(Ubuntu 22.04の脚注)は unattended-upgrades の自動カーネルアップグレードをオフにすることを推奨し、アップグレードが停止したら nvidia.com/gpu-driver-upgrade-state=upgrade-failed ラベルの付いたノードを探すように伝えています。筆者の実験環境ではホストドライバー(595系)と toolkit.enabled=true としてv26.3.3を使用しています。Secure BootノードではDKMSモジュールが署名拒否により読み込まれない場合があり、ディストリビューションが署名したモジュールパッケージを使用する場合、そのモジュールがカーネルバージョンに固定されているため、カーネルバージョンをドライバーに合わせる必要があります。26.3.xはサポート状態が「Deprecated」であり、26.7.xが「Supported」です。コンシューマー向けGPUはドキュメントのサポート製品リストに載っていないため、筆者の環境はサポート対象外と読み取るべきです。

4. 症状別の診断手順

GPU 파드가 안 된다
├─ 파드 오브젝트가 없다 ........ 어드미션: ResourceQuota, 없는 RuntimeClass (apply 때 에러)
└─ 있다
   ├─ spec.nodeName 이 비어 있다 (Pending)
   │   ├─ 이벤트에 taint/selector ... 톨러레이션, 라벨
   │   └─ Insufficient nvidia.com/gpu → 노드별로 capacity / allocatable / 배정 합을 대조
   │        ├─ 자원 이름이 없다 ........ 플러그인 미등록 (5분 유예 경과 포함)
   │        ├─ capacity > allocatable .. unhealthy 장치 (XID), 또는 플러그인 연결 끊김 직후
   │        └─ 배정 합 = allocatable ... 진짜 자리 소진
   └─ 노드에 붙었다
       ├─ no runtime for "nvidia" ..... containerd config dump 의 런타임 목록
       ├─ operand 가 Init 에서 정지 .... 선행 단계(드라이버, 툴킷) 로그
       └─ Running 인데 nvidia-smi 실패 . limits 누락, 드라이버/NVML, 장치 접근 상실
                                        (노드에서 dmesg | grep -iE 'NVRM|Xid')

スケジューラーは 요청 > allocatable − 이미 배정 であれば原因を問わず Insufficient <자원 이름> と記録します(kube-schedulerのソース)。そのため、イベントの1行だけで原因を断定してはなりません。

5. よくある罠:ドロップインがCRI設定を丸ごと書き換えてしまった

筆者の実験環境で、GPU 4枚が1週間にわたり広告されませんでした。要約すると以下の通りです。

外部の根拠で確認できた内容は以下の通りです。containerd 1.7のソースの mergeConfig はコメントの通り「Pluginsの値を丸ごと置き換える」ものであり、公式manページの「mapはappendされる」という文言はこの動作を説明できません。NVIDIAツールキットのソースには、1.7のような古いバージョンではプラグインセクションがキー単位でしかマージされないため、ドロップインにCRI設定全体を複製して記述するというコメントがあります。2.xのソースにはこの置換コードは見当たりませんが、同様の再現が起こるかは確認できていません。ツールキットは終了時に自身のドロップインファイルを削除します。筆者が目にしたある解説では「SIGHUPではハンドラーが登録されない」と書かれていますが、containerdのソースの処理シグナルリスト(1.7、main)にSIGHUPはなく、ツールキットのデフォルトの再起動方式がSIGHUPであるため、筆者の解釈としてはプロセスが終了し、systemdが再起動させるパスとなります(アップストリームのユニットは Restart=always)。このパスを使い捨てのVMで直接確認しました。Ubuntuパッケージのcontainerd 2.2.1を別サービスとして起動し、 systemctl kill -s HUP でSIGHUPを3回送信したところ、3回とも プロセスが終了して新しいプロセスに置き換わりました(PIDが変更され、状態が activating を経由して activeになりました)。ジャーナルには Sent signal SIGHUP to main process の次に Scheduled restart job, Started containerd.service が記録され、ユニットは Restart=always、再起動待機時間は5秒でした。ログレベルを debug に変更した設定ファイルを配置した状態でSIGHUPを送信した際も新しいプロセスが起動して新しい設定を読み込んだため、これは設定の再読み込みではなく 再起動を通じた反映です。筆者環境のノードを確認したところ、containerdは一部が1.7.27、残りが2.3.5であり、ユニットはすべて Restart=always、再起動待機時間は5秒でした(確認のみ行い、SIGHUPは送信していません)。制限事項:検証したバージョンは2.2.1であり、1.7でのSIGHUPは検証していません(ソース上の処理シグナルリストにSIGHUPがないことまでは確認済み)。

6. 容量計画

7. 実際に試してみる(読み取り専用)

# 노드별 광고량
kubectl get nodes -o custom-columns=N:.metadata.name,CAP:.status.capacity.nvidia\.com/gpu,ALLOC:.status.allocatable.nvidia\.com/gpu
# operand 현황과 NFD 라벨
kubectl -n gpu-operator get ds,po -o wide
kubectl get nodes -L nvidia.com/gpu.present,nvidia.com/device-plugin.config
# 각 GPU 노드에서: 파일이 아니라 로드된 것을 본다 (0 이면 사고)
containerd config dump | grep -c runtimes.nvidia; ls /etc/containerd/conf.d
# 툴킷이 SIGHUP 을 보낸 시각에 containerd 가 어떻게 되었나
sudo journalctl -u containerd --since "-7d" | grep -E "Started|Stopped|signal" | tail

一時的なポッドで確認を完了させるには、 nvidia-smi -L を1行実行します(kubectl -n gpu-operator run gputest --rm -it --restart=Never ... --limits=nvidia.com/gpu=1 -- nvidia-smi -L)。このポッドはスロットを一時的に使用し、 --rm によってクリーンアップされます。その他の変更コマンドは実行していません。

8. 面接で出そうな質問

  1. GPUポッドがPendingになっています。どこから確認しますか? イベントの文章よりも、ノードのstatusにあるcapacity、allocatable、割り当ての合計を突合します。同じ Insufficient が、プラグイン未登録、unhealthyなデバイス、リソース枯渇のいずれの場合でも出力されます。
  2. ポッドが GPU を割り当てられる経路を説明してください。 プラグインの登録、ノード status の広告、スケジューラの数値判定、 Allocate, CDI または NVIDIA ランタイムのインジェクション、runc の順序です。前者はスケジューラの関門、後者はランタイムの関門です。
  3. ドライバーをコンテナに配置することとホストに配置することの違いは何ですか? コンテナの場合はアップグレードの自動化と OS 一致の制約があり、ホストの場合は自由度と直接管理の負担があります。カーネル変更時にドライバーが追随しなければならない点が共通のリスクです。
  4. タイムスライシングと MIG の容量はどのように計算しますか? タイムスライシングは広告が N 倍になるだけでメモリ保証は 0 です。MIG はハードウェアスライスのため、合計が物理容量より小さくなることがあります。
  5. containerd のドロップイン事故から何を学びましたか? 同じプラグインに手を加えた最後のファイルが設定全体を引き継ぐというマージルールを知っておく必要があります。判定はファイルではなく containerd config dump で行い、変更は 1 ノードずつ適用します。

9. よくある誤解

この記事の構成要素の表をポッド単位で分かりやすく解説した記事が 23 編(GPU Operator のポッド構成要素と役割、ホストドライバーがなくてもよいのか)です。

10. 参考資料と確認したバージョン

すべて 2026-10-05 に直接アクセスしました。

確認したバージョン:GPU Operator 26.7.1(2026-08-21、ツールキット 1.20.1、デバイスプラグイン 0.20.1、DCGM Exporter 4.6.1-4.8.4)、Kubernetes 安定版 v1.37.1、containerd 2.4.1 および 1.7.36。筆者の実験環境は GPU Operator v26.3.3。

確認できなかったこと:26.7.0 の containerd 最小バージョンがドキュメントに「1.8 から 2.0」と記載されていた理由(1.8 は誤記とみられる)、containerd 1.7 での SIGHUP の観測(2.2.1 でのみテスト)、2.x でのマージ動作、インストールのドキュメントにおける「ドライバーがない場合プラグインが静かに 0 個と報告する」(プラグインの README には FAIL_ON_INIT_ERROR デフォルトが true なので失敗すると記載)、障害当時のオペランドポッドの状態の記録。

ログインするといいねできます

コメント

まだコメントはありません。

ログインするとコメントできます