オペレーターとオペランド — 六つの DaemonSet をノードごとに点けたり消したり
一言でいうと
GPU Operatorが起動する十数個のPodは、1つのプログラムではなく、オペレーターがClusterPolicyを読んで展開したDaemonSet複数個であり、各DaemonSetは、nodeSelectorでnvidia.com/gpu.deploy.<이름>ラベルを見ているため(プレースホルダーはオペランド名です)、ノード単位でオン・オフできます。
なぜ必要なのか
Kubernetesでは、GPUを使うために、ノードに揃っている必要があるものが複数あります。カーネルモジュールを読み込むドライバー、コンテナランタイムにGPUランタイムを登録するコンテナツールキット、kubeletにnvidia.com/gpuリソースを知らせるデバイスプラグイン、ハードウェアの事実をラベルに変えるGPU Feature Discovery、メトリクスを出力するDCGM Exporter、インストールが正しくできたかを確認するバリデーター、MIGを使うならMIGマネージャーまでです。
これを手でやると、ノードごとに順序を守りながら、インストール・アップグレード・ロールバックをする必要があります。オペレーターパターンは、ここに「望ましい状態を1つのオブジェクトに書いておけば、コントローラーが合わせてくれる」という仕組みを入れました。そのオブジェクトがClusterPolicyであり、コントローラーがそれを読んで作り出すものがオペランドです。
用語を一度整理すると、残りが楽になります。オペレーターは、ClusterPolicyを監視するコントローラーのPod1つです。オペランドは、オペレーターが作って管理するワークロード、つまり上に挙げたDaemonSetたちです。障害が起きたとき、「オペレーターが落ちた」と「オペランドが起動しなかった」は、まったく別の問題であり、見る場所も違います。
どう動くのか
オペランドは、ほとんどすべてがDaemonSetです。DaemonSetは、「すべてのノードに1つずつ」ではなく、「条件に合うノードごとに1つずつ」であり、その条件がnodeSelectorです。GPU Operatorは、ここに自分のラベルをかけています。
| オペランド | 有効にするラベル |
|---|---|
| ドライバー | nvidia.com/gpu.deploy.driver |
| コンテナツールキット | nvidia.com/gpu.deploy.container-toolkit |
| デバイスプラグイン | nvidia.com/gpu.deploy.device-plugin |
| GPU Feature Discovery | nvidia.com/gpu.deploy.gpu-feature-discovery |
| DCGM Exporter | nvidia.com/gpu.deploy.dcgm-exporter |
| バリデーター | nvidia.com/gpu.deploy.operator-validator |
この構造から出てくる、実務で使える操作手段が3つあります。
1つ目は、ノード全体をオペランドから外すことです。公式ドキュメントは、nvidia.com/gpu.deploy.operands=falseラベルで、そのノードにオペランドが配置されないようにする方法を書いています。
kubectl label nodes $NODE nvidia.com/gpu.deploy.operands=false
2つ目は、オペランド1つだけを外すことです。ドライバーだけを載せないなら、nvidia.com/gpu.deploy.driver=falseです。ラベルをfalseにしておくことと、そもそも消すことは、人にとっては違って読めますが、nodeSelectorにとっては同じです。値がちょうど"true"でなければ、条件に合いません。ただし、falseと書いておくほうが、「意図的に外した」ことを残せるので、運用ではこちらを使います。
3つ目は、クラスター全体でオペランドをオフにすることです。これはラベルではなく、ClusterPolicy(またはHelmの値)の側です。ドライバーがホストにすでにインストールされている環境ならdriver.enabled=false、GPUランタイムがすでに登録されているならtoolkit.enabled=falseです。
ラベルを外すと、DaemonSetコントローラーがそのノードのPodを削除します。DaemonSetを変更していないのにPodが消えるこの動作は、最初は驚きますが、コントローラーが条件と実際を合わせ続けていると知れば、当然です。逆に、ラベルを付けると、数秒以内にPodができます。
順序がある
オペランドは、互いを前提にしています。おおよそ、次のような連鎖です。
드라이버 → 커널 모듈이 올라가고 /dev 에 장치가 생긴다
컨테이너 툴킷 → 런타임에 nvidia 런타임이 등록된다
장치 플러그인 → kubelet 에 nvidia.com/gpu 자원을 광고한다
GFD → 모델·장수·드라이버 판을 라벨로 붙인다
검증기 → 앞의 것들이 실제로 되는지 확인한다
このコードブロックの韓国語の部分は、順に、ドライバーはカーネルモジュールを読み込んで/devにデバイスを作り、コンテナツールキットはランタイムにnvidiaランタイムを登録し、デバイスプラグインはkubeletにnvidia.com/gpuリソースをアドバタイズし、GFDはモデル・枚数・ドライバーのバージョンをラベルとして付け、バリデーターは前のものが実際に動くかを確認する、という意味です。
そのため、ラベルを手で付けるときに、連鎖を壊す組み合わせができてしまいます。最もたちが悪いのが、ツールキットなしでデバイスプラグインだけが動くノードです。デバイスプラグインはリソースをアドバタイズし、スケジューラーはその数字を見てPodを送り、Podはスケジュールに成功します。ところが、ランタイムがGPUをコンテナに接続してくれないので、ワークロードだけがデバイスを掴めません。どこでもエラーが出ないのに結果だけが間違っている故障なので、こうした組み合わせを見つける点検を持っているかどうかが、対応時間を分けます。
現場での姿
第一に、「GPUノードなのにPodが起動しません」。確認の順序は、ラベル → DaemonSetのdesiredNumberScheduled → そのノードのPodです。3つの場所が、それぞれ別のことを語ります。ラベルがなければ何も起きていないのであり、ラベルは合っているのにdesiredNumberScheduledが低ければ、セレクターかテイントがずれており、数字は合っているのにPodがなければ、そのノードで起動できなかったということです。
第二に、数字だけを見るとだまされます。desiredNumberScheduledは、DaemonSetコントローラーが起動すべきだと判断したノード数です。許容されないテイントがあるノードは、そもそも候補から外れるので、この数字に含まれません。そのため、「あるべきオペランドがすべてあるか」を判定するときは、数字ではなく、そのノードで実際にRunningのPodを見る必要があります。
第三に、ノード1台だけを隔離するやり方です。GPU1枚がおかしいノードが出たとき、ノードを丸ごとcordonする代わりに、オペランドだけを落とす選択肢があります。ラベル1行で、そのノードのオペランドを止めると、ワークロードは、リソースがアドバタイズされなくなるので、自然に別のノードへ行きます。クラスターを揺らさずに、1台だけを切り離す方法です。
第四に、ドライバーがすでにインストールされたノードです。オンプレミスでは、イメージにドライバーを焼き込んでおくチームが多くあります。そのようなノードでドライバーのオペランドが起動すると、カーネルモジュールを2回読み込もうとして、良いことはありません。クラスター全体がそうならdriver.enabled=false、一部のノードだけがそうなら、そのノードにnvidia.com/gpu.deploy.driver=falseです。公式ドキュメントは、GPU Operatorがホストに事前インストールされたドライバーのライフサイクルは管理しないと、はっきり書いています。つまり、そのノードのドライバーのアップグレードは、自分たちの担当だという意味です。
第五に、この環境の正直な限界です。ラボの環境には、GPUもGPU Operatorもないので、ClusterPolicyを適用できません。そのため、オペレーターが展開したはずのDaemonSetを、自分で書きます。その代わり、DaemonSetコントローラーとスケジューラーは本物なので、ラベルに応じてPodができたり消えたりすることと、テイントのあるノードが候補から外れることは、模擬ではなく実物です。ドライバーが本当にインストールされたかどうかは見られず、そのため見てもいません。
参考ドキュメント
- GPU Operatorを始める(オペランド配置ラベルとデプロイシナリオ): https://docs.nvidia.com/datacenter/cloud-native/gpu-operator/latest/getting-started.html
- GPU Operatorのインストールとチャートの値(driver.enabled・toolkit.enabled): https://docs.nvidia.com/datacenter/cloud-native/gpu-operator/latest/install-gpu-operator.html
- GPU Operatorの概要: https://docs.nvidia.com/datacenter/cloud-native/gpu-operator/latest/overview.html
- DaemonSet: https://kubernetes.io/docs/concepts/workloads/controllers/daemonset/
- Podをノードに配置する(nodeSelector): https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/
次のラボですること
オペレーターが展開したはずのDaemonSet5つを、ラベルで直接配置します。GPUノードが2台なのに、ドライバーだけが1台のノードで起動するのを確認し、ラベルを1つ外してPodが消えるのを確認し、ラベルだけを手で付けて、ツールキットなしでデバイスプラグインだけが動くノードを、わざと作ります。そして、「あるべきオペランドがすべてあるか」と「順序がずれたノードがあるか」を判定するツールを作り、ノードがもう1台入っても嘘をつかないかを確認してもらいます。