Skip to content
HoginHogin
Go back

GPUs in Kubernetes without device plugins: how DRA changes accelerator allocation

10 мин чтения

For years GPUs in Kubernetes were allocated by a single number: nvidia.com/gpu: 1 in resources.limits — and the node agent handed the pod whatever card it grabbed. In Kubernetes 1.34 Dynamic Resource Allocation (DRA) went GA, in 1.35+ the feature gate is locked on for good, and NVIDIA moved its device plugin to maintenance mode: new capabilities land only in the DRA driver. Here is how to ask for a GPU now — by attributes (“memory ≥ 16 GB”) instead of a counter — and what breaks along the way.

Table of contents

Open Table of contents

Device plugins: why the counter is a dead end

The Device Plugin API dates back to 2017, when “a GPU in a pod” meant “one whole accelerator”. The scheme is simple: a daemon on the node lists the cards over a gRPC socket, kubelet publishes them as the extended resource nvidia.com/gpu, and the scheduler sees a number — nothing more. Only the node agent “knows” about the devices — it also mounts them into the container via NVIDIA_VISIBLE_DEVICES and toolkit hooks. The cluster API holds no object and no attributes for accelerators: nothing to ask — kubectl get on GPUs has nothing to return.

Device plugin: the scheduler only sees a counter

Trouble starts the moment GPUs stop being identical:

An extended resource cannot express anything but an integer — an architectural ceiling, not a bug anyone will fix.

The three DRA objects: ResourceSlice, ResourceClaim, DeviceClass

DRA (API group resource.k8s.io/v1) flips the model: instead of a per-node counter, the cluster API holds an inventory with attributes, and kube-scheduler plans against it.

The three DRA objects: who creates and who consumes

The key difference from early DRA prototypes: kube-scheduler decides from structured parameters, with no synchronous calls into an external driver webhook — that webhook is exactly what delayed GA. The DRA driver takes no part in scheduling: it publishes the inventory and prepares devices once allocated.

A claim’s lifecycle is human-readable: created → the scheduler writes the devices into status.allocation → the pod starts on the chosen node. Devices in a claim are reserved for it — two pods never share a card by accident — and the claim is a full-fledged API object: inspectable, quotable in tickets, cleanable with kubectl. Priorities work too: when devices run short, a pod with a higher PriorityClass evicts someone else’s claim — the scheduler’s usual preemption. The mechanism is not NVIDIA-specific: NICs, FPGAs, and licenses plug in through the same slices and classes.

A pod with a ResourceClaim: how to ask for a GPU now

Instead of resources.limits: a separate object, referenced from the pod spec.

# gpu-claim.yaml — "a card with 16+ GB", not "a unit of GPU"
apiVersion: resource.k8s.io/v1
kind: ResourceClaim
metadata:
  name: gpu-16gb
spec:
  devices:
    requests:
      - name: gpu
        exactly:
          deviceClassName: gpu.nvidia.com
          allocationMode: Count
          count: 1
          selectors:                        # attributes instead of a counter
            - cel:
                expression: "device.attributes['gpu.nvidia.com'].memory >= 16000"
# gpu-pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: vectoradd
spec:
  runtimeClassName: nvidia
  containers:
    - name: cuda
      image: nvcr.io/nvidia/k8s/cuda-sample:vectoradd-cuda12.5.1
      command: ["./vectorAdd"]
  resourceClaims:                 # ← what replaced nvidia.com/gpu
    - name: gpu
      source:
        resourceClaimName: gpu-16gb
  resources:
    claims:
      - name: gpu                  # the device is available to containers

The CEL selector combines conditions: model (productName), memory, driver version. For Jobs and other multi-pod workloads, don’t create claims by hand — generate them from a template, and every pod gets its own:

# claim-template.yaml — a per-pod claim generated from a template
apiVersion: resource.k8s.io/v1
kind: ResourceClaimTemplate
metadata:
  name: gpu-16gb-tpl
spec:
  spec:
    devices:
      requests:
        - name: gpu
          exactly:
            deviceClassName: gpu.nvidia.com
            allocationMode: Count
            count: 1
            selectors:
              - cel:
                  expression: "device.attributes['gpu.nvidia.com'].memory >= 16000"

In the pod, resourceClaimName becomes resourceClaimTemplateName: gpu-16gb-tpl: the controller creates a uniquely named claim and deletes it with the pod. A standalone shared claim (as above) fits when several pods legitimately share one GPU.

What you need to move a cluster to DRA

You need Kubernetes ≥ 1.34, the NVIDIA GPU Operator, and the DRA driver. Before you start, check three things: the control plane version (kubectl version), a containerd or CRI-O runtime (device delivery rides on CDI), and nothing in your stack hard-requiring nvidia.com/gpu in pod limits — older Kubeflow and platform controllers sometimes validate exactly that. On managed clusters, confirm the control plane is already upgraded: providers upgrade on their own schedule. The order:

1. GPU Operator without the device plugin — the GPU driver, toolkit, and DCGM exporter install as usual; the plugin we turn off:

helm upgrade --install gpu-operator nvidia/gpu-operator \
  --namespace gpu-operator --create-namespace \
  --set driver.enabled=true \
  --set devicePlugin.enabled=false

2. The NVIDIA DRA driver — publishes the ResourceSlice and DeviceClass, generates CDI specs:

helm repo add nvidia https://helm.ngc.nvidia.com/nvidia --force-update
helm upgrade --install nvidia-dra-driver nvidia/k8s-dra-driver \
  --namespace nvidia-dra-driver --create-namespace \
  --set runtimeClassName=nvidia

Recent GPU Operator releases can deploy the DRA driver from the same chart — if yours does, skip step 2.

3. CDI in the container runtime. Devices reach the container through CDI: the driver writes a spec with the exact list (nvidia.com/gpu=2), and containerd/CRI-O mount exactly that. Requires containerd ≥ 1.7 with enable_cdi = true (on by default in 2.0). A quick readiness check: crictl info | grep -i cdi.

No node drain required: existing pods with nvidia.com/gpu keep working the old way until you migrate them — the Device Plugin API is still there.

DRA vs device plugins

Device pluginDRA
What the pod asks fornvidia.com/gpu: 1 — a numbera ResourceClaim with attributes
Who picks the devicethe node agent, “whatever it grabs”kube-scheduler against the inventory
MIGre-publishing and racesslices are devices with attributes
Sharinginvisible oversubscriptionhonest fractions for the scheduler
Quotas and monitoringextended resource in kubeletResourceSlice/claim in the cluster API
Cluster requirementsany Kubernetes version≥ 1.34, containerd/CRI-O with CDI
Statusmaintenance mode at NVIDIAGA in 1.34, gate locked in 1.35+

What breaks during migration

The move itself is gentle — old pods keep running — but everything built around the counters refuses to come along.

What breaks when moving to DRA

apiVersion: v1
kind: ResourceQuota
metadata:
  name: gpu-claims
spec:
  hard:
    count/resourceclaims.resource.k8s.io: "4"

When DRA actually pays off

If all your cards are identical and each pod owns a whole card, DRA brings nothing revolutionary — only standards compliance. The payoff is on heterogeneous hardware:

How to verify it works

After installing the driver:

kubectl get resourceslices                      # inventory published
kubectl describe deviceclass gpu.nvidia.com     # which attributes you can select on
kubectl apply -f gpu-claim.yaml
kubectl get resourceclaim gpu-16gb              # allocation: the exact card
kubectl get pod vectoradd -w                    # Pending → Running
kubectl exec vectoradd -- nvidia-smi -L         # the pod sees exactly what was claimed
kubectl describe resourceclaim gpu-16gb         # attributes and the allocated device

If the pod hangs in Pending, run kubectl describe pod: the events hold the reason — say, “no devices match selector”: your selector is stricter than the inventory. And if kubectl get resourceslices is empty, check the nvidia-dra-driver pods: no driver, no inventory.

Bottom line

Device plugins for GPUs are legacy: NVIDIA invests only in the DRA driver, and in 1.35+ the feature gate is locked on. The price of the move is rewriting a couple of quotas and dashboards; the payoff is a scheduler that sees MIG, sharing, and memory size instead of a “whatever’s free” lottery. Migrate now, while both paths run in parallel: run the DRA driver next to the plugin, move non-critical Jobs, rewrite the quotas and dashboards — and only then turn the device plugin off, namespace by namespace. If an observability stack already lives on your cluster, see Kubernetes audit logs in Wazuh.


Share this post:

Next Post
A contact form on a static site: Cloudflare Turnstile + Pages Functions + D1 + Telegram — no backend of your own