ClusterPolicy・RuntimeClass・資源のアドバタイズ
一言でいうと
GPU Podが起動するには、互いにまったく関係のない2つのゲートを通過する必要があります。スケジューラーが見るnvidia.com/gpuリソースと、ノードのランタイムが見るRuntimeClassハンドラーです。片方だけを整えても、Podは、どちらで止まったのかを教えてくれません。
なぜこの区別が必要なのか
「GPU Podが起動しない」という報告を受けたら、症状はちょうど2つのうちのどちらかです。
- Podが
Pendingから動きません。→ リソース側です。どのノードもnvidia.com/gpuをアドバタイズしていないか、空きがすべて埋まっています。 - Podはノードに割り当てられたのに、
ContainerCreatingで止まって失敗します。→ ランタイム側です。そのノードのcontainerdに、ハンドラーが登録されていません。
この2つを切り分けられないと、device pluginのログを調べながら、実はcontainerdの問題だったものに何時間も時間を取られます。逆も同じです。2つのゲートがなぜ分かれているのかを知っていれば、最初の3分で切り分けを決められます。
どう動くのか
リソース側。GPUは、cpuやmemoryのようにkubeletが自分で数えるリソースではありません。device pluginがノード上でデバイスを開いて確認し、kubeletにその一覧を渡すと、kubeletがその個数を、ノードオブジェクトのstatus.capacityとstatus.allocatableに、拡張リソースとして書き込みます。スケジューラーが見るのはデバイスではなく、その数字です。そのため、数字だけがあってデバイスがなくてもスケジューリングは成立し(それがこのコースのラボの立ち位置です)、逆にデバイスが正常でもpluginが止まっていれば、スケジューラーはそのノードにGPUがないと判断します。
拡張リソースには、もう1つ規則があります。requestsとlimitsを別々の値にはできません。limitsに書いた値がそのままrequestsになり、整数でなければなりません。GPUを0.5枚だけ渡すことはできないからです。
ランタイム側。RuntimeClassは、「このPodをどのランタイムハンドラーで作るか」を選ぶ、クラスタースコープのオブジェクトです。
apiVersion: node.k8s.io/v1
kind: RuntimeClass
metadata:
name: nvidia
handler: nvidia # 이 문자열이 containerd 설정의 runtimes.<이름> 과 맞아야 한다
kubeletはPodを作るとき、このhandler文字列をCRIリクエストに載せて送り、containerdは自分の設定のruntimesテーブルから同じ名前を探します。なければ、コンテナの作成が拒否されます。ここで重要なのは、APIサーバーがこの名前を検証しないという点です。RuntimeClassはkubectl applyでいくらでも作成でき、存在しないハンドラーを指していても、誰も警告しません。そのずれは、Podを作る瞬間、しかもそのノードでだけ表面化します。
2つのゲートの関係を1行でまとめると、次のとおりです。
스케줄러 : nvidia.com/gpu 숫자를 보고 "어느 노드로 보낼까" 를 정한다
kubelet : RuntimeClass handler 를 containerd 에 넘겨 "어떻게 만들까" 를 정한다
現場での姿
第一に、runtimeClassNameを書かなくてよいクラスターが多くあります。toolkitがcontainerdのdefault_runtime_nameをそもそもnvidiaに書き換えておく構成が、一般的だからです。便利ですが、代償があります。GPUを使わないPodまでnvidiaランタイムを通ることになり、その設定が壊れると、GPU PodだけでなくクラスターのすべてのPodが起動できなくなります。事故の影響範囲が変わります。
第二に、CDIへの移行が進んでいます。Container Device Interfaceは、「デバイスをコンテナに入れる方法」を、ランタイムの種類に依存しないJSON/YAMLの仕様として標準化したものです。CDIを使うと、nvidia専用のランタイムバイナリがなくても、nvidia.com/gpu=allのようなデバイス名で注入できます。ただし、containerd側のCDIサポートは、バージョンごとに有効にする方法が異なるため、現場では今、2つの方式が混在しています。
第三に、Pendingの原因はリソースだけではありません。GPUノードにはたいていnvidia.com/gpu=present:NoScheduleのようなテイントが付いていて、一般のワークロードが高価なノードを占有しないようにしています。GPU Podにトレラレーションがないと、リソースが余っていてもスケジュールされません。kubectl describe podの最後のイベントが、常に最初に読むべき一文である理由です。
次のラボですること
すぐ後のラボで、2つのゲートのうちリソース側を、8つのステップで最後まで進みます。ノードのstatusに拡張リソースを書き込んでスケジューラーに判定させ、requestsとlimitsの規則がapiserverで拒否されるのを確認し、同じPendingでも原因がテイントなのかリソースなのかを、メッセージで切り分けて読みます。ここで切り分けを正確にできれば、あとに出てくる障害の話がずっと早く読めます。