TT Lab
はじめる
学ぶ 学習パス コース

GPU Operatorとタイムスライシング

オペレーターとオペランド — 六つの DaemonSet をノードごとに点けたり消したり

TT Labで続きを見る

一言でいうと

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ができたり消えたりすることと、テイントのあるノードが候補から外れることは、模擬ではなく実物です。ドライバーが本当にインストールされたかどうかは見られず、そのため見てもいません。

参考ドキュメント

次のラボですること

オペレーターが展開したはずのDaemonSet5つを、ラベルで直接配置します。GPUノードが2台なのに、ドライバーだけが1台のノードで起動するのを確認し、ラベルを1つ外してPodが消えるのを確認し、ラベルだけを手で付けて、ツールキットなしでデバイスプラグインだけが動くノードを、わざと作ります。そして、「あるべきオペランドがすべてあるか」と「順序がずれたノードがあるか」を判定するツールを作り、ノードがもう1台入っても嘘をつかないかを確認してもらいます。