All posts
GPU Infrastructure

DRA Is Stable. The Device Plugin Era Is Ending.

The device plugin API can only count. That single limitation is why your GPU scheduling runs on node labels and pre-partitioned MIG. Dynamic Resource Allocation went GA in Kubernetes 1.34, and in 1.36 the features that make it usable landed. Here is what changes and what to do about it.

By Sharon Sahadevan··11 min read

Here is the entire limitation of the device plugin API, in one line of YAML:

resources:
  limits:
    nvidia.com/gpu: 1

That is a count. It is the only thing the API can express.

Which GPU model. How much memory. Which MIG profile. Whether those four cards share an NVLink domain or sit three PCIe hops apart. Whether the driver version matches what your CUDA build needs. None of it fits in an integer, so all of it gets smuggled in around the side.

You know the workarounds because you built them. Node Feature Discovery writes labels. Your manifests carry nodeSelector: nvidia.com/gpu.product: NVIDIA-H100-80GB-HBM3. Someone decides the MIG layout on a node pool hours before any pod arrives, and a workload that wants a different profile goes to a different node pool. The real requirement lives in a Helm values file, a wiki page, and the memory of whoever set it up.

Dynamic Resource Allocation replaces that layer, and it is no longer something to keep an eye on. It shipped.

KEY CONCEPT

The device plugin advertises an opaque integer and the scheduler decrements it. DRA lets the driver publish structured device attributes and lets the workload describe what it needs. Everything else in this post follows from that one change: the requirement moves out of node labels and into the request.

Where DRA actually is#

This moved faster than most people tracked, so the versions matter.

  • Kubernetes 1.34 — DRA core graduated to GA, with resource.k8s.io/v1 enabled by default.
  • Kubernetes 1.35 — the feature gate is locked on. It cannot be disabled.
  • Kubernetes 1.36 — prioritized list went stable. Partitionable devices, device taints and tolerations, device binding conditions, and extended resource support all reached beta, which means on by default.
  • Ecosystem — NVIDIA donated its DRA driver to the CNCF at KubeCon EU 2026. OpenShift shipped DRA as GA in 4.21.

If you are running a current cluster, the API is already there. The question is not whether to adopt it eventually. It is what you stop building today.

The four objects#

DRA has more moving parts than the device plugin, and the parts map cleanly onto roles.

ResourceSlice is published by the driver, per node. It is the inventory: here are the devices on this machine and here is what is true about them. For GPUs the NVIDIA driver publishes attributes like product name, architecture, CUDA compute capability, driver version, and memory capacity. This is the object that replaces "we scraped it into node labels."

DeviceClass is cluster-scoped and defines a category of device with a CEL selector. Think of it the way you think about StorageClass: the platform team defines h100-80gb or mig-1g-10gb once, and workloads reference it by name rather than each one carrying its own hardware matching logic.

apiVersion: resource.k8s.io/v1
kind: DeviceClass
metadata:
  name: gpu-h100
spec:
  selectors:
  - cel:
      expression: device.attributes["gpu.nvidia.com"].productName == "NVIDIA H100 80GB HBM3"

ResourceClaim is a request for devices. It can be created directly and shared by multiple pods, which is itself a capability the device plugin never had.

ResourceClaimTemplate generates a claim per pod, which is what you want for a Deployment where each replica needs its own device.

apiVersion: resource.k8s.io/v1
kind: ResourceClaimTemplate
metadata:
  name: single-h100
spec:
  spec:
    devices:
      requests:
      - name: gpu
        deviceClassName: gpu-h100

The pod then references the claim by name, and each container declares which claims it uses:

apiVersion: v1
kind: Pod
metadata:
  name: inference
spec:
  resourceClaims:
  - name: gpu
    resourceClaimTemplateName: single-h100
  containers:
  - name: server
    image: registry.example.internal/vllm:latest
    resources:
      claims:
      - name: gpu

Note what is absent from that pod spec: no nodeSelector, no GPU-model label, no nvidia.com/gpu count. The hardware requirement lives in the claim, and the claim is a first-class object you can review, template, and reason about.

WARNING

The DRA API shape changed across alpha and beta versions, and examples on the internet are a mix of resource.k8s.io/v1alpha3, v1beta1, v1beta2, and v1. Field nesting differs between them, and a manifest copied from a 2025 blog post will not apply cleanly to a 1.36 cluster. Check the API reference for your exact version before you commit anything to a repo.

What you can express now that you could not#

This is the part that matters operationally. Five things, and each one retires a workaround.

Attribute selection instead of node labels#

A claim can select on real structured attributes published by the driver rather than on labels somebody arranged to be present. Driver version, memory capacity, compute capability, product name. The scheduler evaluates it against the ResourceSlice.

The practical difference: when a new GPU model arrives in the fleet, you do not go update nodeSelectors across dozens of manifests. The claim already describes the requirement, and the new hardware either satisfies it or does not.

Constraints across multiple devices#

This is the one worth the migration on its own. A claim can request several devices and constrain their relationship to each other using matchAttribute, which requires that all allocated devices share the same value for a given attribute.

That is how you express "four GPUs that are actually connected to each other" rather than "four GPUs, good luck." If you have ever watched two identically-specced training pods perform 30% apart because one got NVLink-connected cards and the other got four devices spread across two PCIe root complexes, this is the fix for that entire class of problem. Under the device plugin there was no way to say it, so people encoded it as "only schedule on these node pools" and hoped.

Partitioning at allocation time#

Partitionable devices, beta in 1.36, means MIG can be carved to fit the workload at allocation time rather than pre-configured on the node.

Under the device plugin, MIG layout is a node-level decision made ahead of demand. You pick 7x1g.10gb or 3x2g.20gb or 2x3g.40gb for a node pool, and every workload that lands there gets that shape whether it fits or not. Getting the mix wrong means stranded capacity in one pool and queuing in another. Partitionable devices moves that decision to the moment of allocation, where the actual demand is known.

Device-level taints#

Device taints and tolerations, also beta in 1.36, let you quarantine a single GPU.

Today, one card throwing XID errors means cordoning the node. On an 8xH100 box that is seven healthy GPUs taken out of service because one is failing. Device taints let you taint the bad device, drain what is on it, and leave the rest working while a repair is scheduled. For anyone running GPU fleets at scale, this is a straightforwardly large operational win.

Prioritized fallback#

Prioritized list went stable in 1.36. A claim can express "H100, else A100, else L4" as an ordered list, and the scheduler honours the fallback chain.

Under the device plugin, a workload pinned by nodeSelector to a GPU model waits for that model. There is no graceful degradation, so teams either over-provision the expensive tier or maintain parallel manifests per hardware generation.

The migration bridge nobody expected#

The reasonable objection to all of this is that you have hundreds of manifests carrying nvidia.com/gpu: 1, and rewriting them is a project nobody funded.

Extended resource support, beta in 1.36, is the answer. A DRA driver can back classic extended resource requests. The platform team migrates the backend to DRA while application teams keep their existing manifests unchanged, and move to ResourceClaim on their own schedule when they need something the count cannot express.

That turns a flag day into a gradual transition, and it is the single detail that makes DRA adoptable in a large organisation this year rather than next.

PRO TIP

The right sequencing for most teams: migrate the driver layer first while application manifests keep using nvidia.com/gpu, then move the workloads that actually need attribute selection, topology constraints, or dynamic MIG. The workloads that genuinely just want "one GPU, any GPU" can stay on the count indefinitely. There is no prize for converting them.

What to check before you plan anything#

Two things bite, and both are outside the Kubernetes version.

Cluster autoscaler awareness#

The autoscaler has to reason about resource claims to decide whether adding a node would satisfy a pending pod. Under the device plugin it compares an integer against node capacity. Under DRA it needs to understand what a hypothetical new node would publish in its ResourceSlice and whether that would satisfy the claim's selectors and constraints.

Support exists and it varies by platform. On GKE the autoscaler identifies DRA-driver node pools by a node label. Verify this on your distribution rather than assuming it, because the failure mode is quiet: pods sit Pending against an autoscaler that does not believe adding capacity would help, and nothing in the events makes the reason obvious.

Managed platform lag#

Your Kubernetes version is necessary and not sufficient. Driver packaging, autoscaler integration, and support policy each move on their own timeline per platform. OpenShift GA'd DRA in 4.21. GKE, EKS, and AKS are each somewhere different on driver availability and autoscaler behaviour.

Check three things specifically: is the DRA driver available as a supported install or are you self-managing it, does your autoscaler understand claims, and what happens to DRA workloads during a control plane upgrade.

Common mistakes#

  • Copying YAML from a 2025 post. The API shape changed across alpha and beta. Manifests from v1alpha3 or v1beta1 examples will not apply. Use the reference for your version.
  • Migrating workloads before the driver layer. Extended resource support exists precisely so you do not have to. Move the backend, leave the manifests.
  • Converting workloads that do not need it. A pod that wants one GPU of any kind gains nothing from a ResourceClaim. Spend the effort on the workloads with topology or profile requirements.
  • Assuming the autoscaler follows. The most common quiet failure. Test scale-up with a claim that cannot be satisfied by existing nodes, before you depend on it.
  • Treating DeviceClass as per-workload. It is cluster-scoped and platform-owned, like StorageClass. If every team is writing its own, you have rebuilt the nodeSelector sprawl with more YAML.
  • Ignoring quota. ResourceQuota counts ResourceClaim objects differently from how it counted extended resources. If you rely on quota to stop a team consuming the fleet, re-check that it still does what you think.
  • Keeping the node-label abstractions in parallel. Running both means two sources of truth for the same requirement, and they will disagree.

The mental model#

The device plugin models a GPU as a unit of capacity. It is a number the scheduler decrements, and everything else about the hardware is metadata the scheduler cannot see.

DRA models a GPU as a device with properties. The driver describes what exists, the workload describes what it needs, and the scheduler matches them.

That is not a bigger API for the same job. It is a different job. Once the scheduler can see attributes, the things you previously expressed by placing workloads on the right nodes become things you express by describing the device you want — and node pools stop being the mechanism by which hardware requirements are enforced.

The practical position for most teams right now is not "migrate everything." It is narrower and more useful: stop building new abstractions on node labels. Every nodeSelector you add this quarter to encode a hardware requirement is something you will unwind. Every new node pool created solely to hold a particular MIG layout is a workaround for a limitation that no longer exists.

The device plugin is not going away tomorrow, and nothing forces you to move quickly. But it is now the compatibility layer rather than the mechanism, and it is worth planning as if that is true, because it is.


GPU scheduling, the NVIDIA device plugin and GPU Operator, MIG configuration and profile selection, node pools, taints and tolerations, and the operational behaviour of GPU nodes under contention are covered in depth in the Production GPU Infrastructure on Kubernetes course. The capacity and cost side, including autoscaling GPU node pools and scale-to-zero, lives in GPU Cost Optimization. Related reading: MIG vs Time-Slicing: How to Share a GPU in Kubernetes for what DRA's partitionable devices are replacing, Your GPU Dashboard Says 100% Utilized. It's Lying. for the XID and ECC signals that make device-level taints worth having, and Draining a GPU Node Without Killing In-Flight Inference for the node-level drain problem that device taints narrow considerably.

More in GPU Infrastructure

GPU Infrastructure··17 min read

Your GPU Dashboard Says 100% Utilized. It's Lying. Welcome to DCGM.

Every post about GPU incidents starts with 'the dashboards looked fine.' That's the problem. nvidia-smi GPU utilization tells you a kernel ran, not whether the silicon is doing work. The metrics that actually matter, the DCGM + Prometheus stack that exposes them, and the queries and alerts that catch real GPU failures.

Read post