ICTSC2025 本戦 問題解説: [7111] 与えるもの

問題文

概要

あなたは、とある配信基盤チームのオンコールエンジニアである。
チームでは、Kubernetes 上で新たに Dynamic Resource Allocation (DRA) を用いた GPU 利用基盤の検証を進めていた。

本番導入前の最終確認として、単一ノードの検証クラスタに DRA example driver (gpu.example.com) を導入し、Pod に GPU を割り当てて正しく参照できるかを確認しようとした。
今回は 2 つの Pod に対して、それぞれ GPU を 1 台ずつ割り当てる構成としている。
練習環境では実機の GPU を用意できなかったため、検証には仮想的な GPU を用いている。
また、この検証環境の設定ファイル類は、作業効率化のためにチームメンバーが AI に生成させたものをベースに作成していた。

ところが、2 つの Pod はいずれも Pending のままで起動せず、しかもそれぞれ異なる原因で失敗しているようだ。 原因を切り分けて修正し、両方の Pod を正常に起動させたうえで、どちらの Pod からも GPU を参照できる状態にしてほしい。

制約

  • Kubernetes のバージョン変更は禁止する。
  • ノード追加は禁止する。
  • DRA を使わない構成への変更は禁止する。
  • Pod 内で GPU 関連の環境変数を手動で定義し、あたかも GPU が割り当てられたように見せかけることは禁止する。GPU 関連情報は DRA によってコンテナ内に反映されていなければならない。

初期状態

  • Pod が 2つとも起動していない。
$ kubectl get pods -n dra-tutorial
NAME                                     READY   STATUS    RESTARTS   AGE
dra-example-driver-kubeletplugin-7zbm2   1/1     Running   0          11s
pod0                                     0/1     Pending   0          13m
pod1                                     0/1     Pending   0          21m

終了状態

  • Pod が2つとも起動している。
  • どちらの Pod からもそれぞれ仮想的な GPU が 1 枚ずつ割り当てられていることが確認できる。
$ kubectl exec -n dra-tutorial pod0 -- sh -c 'env | grep GPU_'
GPU_DEVICE_0_TIMESLICE_INTERVAL=Default
GPU_DEVICE_0_RESOURCE_CLAIM=1a3d4985-edd6-4954-a29e-a59bf1eebdf9
GPU_DEVICE_0=gpu-0
GPU_DEVICE_0_SHARING_STRATEGY=TimeSlicing
$ kubectl exec -n dra-tutorial pod1 -- sh -c 'env | grep GPU_'
GPU_DEVICE_1_TIMESLICE_INTERVAL=Default
GPU_DEVICE_1_RESOURCE_CLAIM=79316ffe-5d02-4284-b434-f34f2767116b
GPU_DEVICE_1=gpu-1
GPU_DEVICE_1_SHARING_STRATEGY=TimeSlicing

解説

Kubernetes の Dynamic Resource Allocation(DRA)は、従来の単純なリソース管理よりも柔軟にデバイスを扱える仕組みです。
従来の GPU などのデバイス利用では、ノードが持つ拡張リソースを Pod が直接要求する形が一般的であり、「どの種類のデバイスを」「どのような条件で」確保したいのかを細かく表現することは困難でした。
これに対して DRA では、ResourceClaim、ResourceClaimTemplate、DeviceClass、ResourceSlice といったリソースを組み合わせることで、デバイス要求の定義・割り当て・公開を段階的に管理できます。
DRA は最近追加されたシステムなので、まずは DRA を軽く紹介していきます。

まず ResourceClaim は、Pod が必要とするデバイス資源そのものを表すオブジェクトです。
Pod は直接デバイスを要求するのではなく、あらかじめ定義された ResourceClaim を参照することで、「この条件に合うデバイスを使いたい」と宣言します。
これにより、デバイスの確保と Pod の実行を分離でき、どのデバイスがどの Pod に割り当てられたのかを Kubernetes 上で明確に追跡できます。
また、同じ Pod 定義でも、参照する Claim を変えることで異なる条件のデバイスを利用できるため、運用上の柔軟性が高まります。

ResourceClaimTemplate は、その ResourceClaim を Pod 作成時に自動生成するためのテンプレートです。
毎回個別に ResourceClaim を手書きしなくても、Pod や Deployment 側でテンプレートを参照することで、必要な Claim を都度生成できます。
これにより、アプリケーションのデプロイとデバイス要求を一体的に扱いやすくなり、大量のワークロードを扱う場面でも設定を簡潔に保てます。

次に DeviceClass は、どのようなデバイスを要求するかを表現するための仕組みです。
例えば「GPU であること」「特定ベンダのデバイスであること」「一定の属性を満たすこと」といった条件を定義し、ResourceClaim はその DeviceClass を参照して必要なデバイス種別を指定します。
これにより、単に要求する数だけではなく、条件に基づいてデバイスを選択できるようになります。

さらに ResourceSlice は、各ノード上にどのようなデバイスが存在するかを DRA ドライバが Kubernetes に公開するための情報です。
言い換えると、DeviceClass が「欲しいデバイスの条件」を表すのに対し、ResourceSlice は「実際に利用可能なデバイスの一覧」を表します。
DRA ドライバはノード上のデバイスを検出し、その結果を ResourceSlice として API サーバに登録します。
Kubernetes はこの情報をもとに、ResourceClaim の条件を満たすデバイスがどこにあるかを判断します。

「与えるもの」という名前の通り、今回は DRA で Pod に GPU を与えようとするも、なかなかうまく行かない現象を扱いました。
それでは問題の解説に移ります。

まず、問題の段階は 3 ステップに分かれます。

  1. 1つも Pod が起動できない
  2. Pod に GPU を割り当てられない
  3. 2 個目の Pod が起動できない

各ステップごとに何が問題かを確認していきましょう。

まず、1つも Pod が起動できない原因としては、DRA driver が ResourceSlice を作成できていないことが原因でした。
今回の環境では DRA example driver が fake GPU を広告する構成になっていますが、その広告に必要な ResourceSlice を API サーバに登録するための権限が不足していました。
実際に driver 用の ClusterRole を見ると、resourceslices に対して get, list, watch しか与えられておらず、作成に必要な権限がありません。
ResourceSlice が存在しないということは、クラスタから見ると「使える GPU が 1 台も見えていない」のと同じであり、その結果として Pod は起動に必要なデバイスを見つけられませんでした。

rules:
-    verbs: ["get", "list", "watch"]
+    verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]

次に、RBAC を修正して ResourceSlice が見えるようになると、今度は「Pod に GPU を割り当てられない」という第二段階に進みます。
この時点では driver は正常に動作しており、クラスタからも fake GPU の存在が見えるようになっています。
しかし、それでも ResourceClaim は割り当てられませんでした。
ここで原因になるのが DeviceClass の selector の typo です。
今回の DeviceClass では device.driver == 'gpu.exmaple.com' となっており、本来の gpu.example.com が誤って記述されています。
この typo により、存在している GPU デバイスが selector に一致せず、ResourceClaim は条件に合うデバイスを一つも見つけられません。
つまり、第二段階では「GPU は見えているが、条件一致に失敗している」状態です。

spec:
  selectors:
    - cel:
        expression: "device.driver == 'gpu.exmaple.com'"

上記 2点を修正することで、Pod0 は起動しました。
これで最初のステップは終了です。

次に、pod0 の中で env | grep GPU_ を実行してもエラーを吐き、GPU が実際には Pod に注入されていないことが分かります。
この原因は、containerd 側で enable_cdi = false になっていることです。
DRA driver は CDI spec を生成し、runtime がそれを解釈することでデバイスをコンテナ内へ注入しますが、runtime 側で CDI が無効になっていると、allocation には成功してもデバイス注入は行われません。

-  enable_cdi = false
+  enable_cdi = true

そして enable_cdi=true にすると、今度は逆に pod0 が CreateContainerError になり、unresolvable CDI devices というエラーが出ます。
これは runtime が CDI を使うようにはなったものの、参照すべき CDI spec をホスト側から見つけられないことを意味しています。
今回の driver DaemonSet では /var/run/cdi はホストと共有されていますが、driver はデフォルトの /etc/cdi に spec を出力しており、そのディレクトリはホストと共有されていません。
そのため、driver コンテナの中では spec が見えていても、containerd からは見えず、CDI device injection に失敗します。
ここで必要なのは、CDI_ROOT=/var/run/cdi を設定するなどして、driver が runtime から見える場所に spec を書くようにすることです。

env:
  - name: NODE_NAME
    valueFrom:
      fieldRef:
        fieldPath: spec.nodeName
  - name: NUM_DEVICES
    value: "1"
+  - name: cdi
+    mountPath: /var/run/cdi

この修正まで終わると、pod0 では GPU 関連の環境変数が見えるようになり、DRA と CDI の一連の流れが正しく通ったことが確認できます。

最後に pod1 の起動を行います。
まず、pod0 と pod1 に GPU を1台ずつ割り当てるならば、2枚の GPU が必要です。
ところが、NUM_DEVICE が 1 となっており、そもそも広告するデバイス数が足りていません。
これだと、pod0 に GPU を割り当てた時点で、別の GPU を pod1 に割り当てるということができなくなります。
そのため、1枚増やします。

spec:
  template:
    spec:
      containers:
        - name: plugin
          env:
            - name: NUM_DEVICES
-              value: "1"
+              value: "2"

さらに、ResourceQuota によって要求可能な GPU の枚数が制限されているのも問題です。
今回の ResourceQuota では gpu.example.com/deviceclass.resource.k8s.io/devices: "1" という制限があり、この namespace では gpu.example.com のデバイスを 1 台までしか利用できません。
pod0 用の ResourceClaim がすでに 1 台分を消費しているため、2 個目の second-gpu は quota 超過で拒否されます。
そこで、この制約を外すか、"2" に増やします。

spec:
-    gpu.example.com.deviceclass.resource.k8s.io/devices: "2"

これを修正し、再度 resourceclaim-second.yaml を apply すると、ついに Pod1 も起動し、内部で GPU の存在を確認することができます。

採点基準

  • 50点:rbac に create update patch delete の権限追加
  • 10点:DeviceClass の誤字修正できている
  • 50点:enable_cdi を true にする
  • 50点:/var/run/cdi を渡す
  • 20点:GPU 枚数を増やす
  • 20点:resource quota を増やす

満点条件:

  • Pod がどちらも起動している
  • Pod から GPU が見えていれば OK

確認例:

  • Pod から GPU が見えていれば OK
$ kubectl exec -n dra-tutorial pod0 -- sh -c 'env | grep GPU_'
GPU_DEVICE_0_TIMESLICE_INTERVAL=Default
GPU_DEVICE_0_RESOURCE_CLAIM=1a3d4985-edd6-4954-a29e-a59bf1eebdf9
GPU_DEVICE_0=gpu-0
GPU_DEVICE_0_SHARING_STRATEGY=TimeSlicing
$ kubectl exec -n dra-tutorial pod1 -- sh -c 'env | grep GPU_'
GPU_DEVICE_1_TIMESLICE_INTERVAL=Default
GPU_DEVICE_1_RESOURCE_CLAIM=79316ffe-5d02-4284-b434-f34f2767116b
GPU_DEVICE_1=gpu-1
GPU_DEVICE_1_SHARING_STRATEGY=TimeSlicing

※ VM 上では解決していても、報告書に記載されていなければ減点
※ 報告書の内容が再現可能なほど詳細に書かれていなければ減点

講評

DRA は、多くの参加者の皆さんにとって初めて触れる題材だったと思いますが、それにもかかわらず多くのチームが正しく解答にたどり着いていました。
普段あまり触れない概念が多く、難しく感じた方も多かったと思いますが、未知の技術に対しても調査・切り分けを進め、解決まで持っていけたのは非常に素晴らしかったです。