GPUs on Kubernetes Explained (2026): Device Plugin, MIG, Time-Slicing, DRA
When someone tells me their GPU cluster is broken, the GPU is almost never the problem. A pod sits in Pending because the node advertises zero nvidia.com/gpu, a driver container crash-loops after a kernel update, or a team that fought for eight H100s runs one notebook per card and wonders why the bill looks the way it does. The silicon is fine; the plumbing between the card and the scheduler is where it goes wrong. This post explains how GPUs on Kubernetes actually work in October 2026: how a pod gets a GPU through the device plugin, what the NVIDIA GPU Operator installs, how MIG, time-slicing and MPS share a card, what Dynamic Resource Allocation (DRA) changes now that every supported Kubernetes release ships it as GA, where Kueue fits, which managed node options are worth using, and what the utilisation data says about all of it.
How does Kubernetes see a GPU?
Out of the box, it doesn't. Kubernetes knows CPU, memory and storage; everything else arrives through the device plugin framework. A vendor daemon on each node registers with the kubelet and advertises an extended resource, for NVIDIA nvidia.com/gpu, as a plain integer. The scheduler treats that integer like any other countable resource: a node with eight cards has eight units, and a pod that asks for one takes one.
The rules for asking are stricter than for CPU. The Kubernetes Schedule GPUs page says GPUs are only supposed to be specified in limits; you can set requests too, but they must equal the limits, and you cannot set requests without limits. There are no fractions and no overcommit. A request for nvidia.com/gpu: 1 means one whole device, exclusively, for the life of the pod.
That integer is also all the scheduler knows. It cannot tell an L4 from an H100, so model selection happens through node labels that NVIDIA's GPU Feature Discovery writes (nvidia.com/gpu.product, nvidia.com/gpu.count, memory and MIG labels) and through ordinary nodeSelector or affinity rules. For a decade that was enough, because most clusters had one GPU model and one team. It stops being enough the moment you want to share a card, prefer one model and fall back to another, or charge three teams fairly for the same pool.
What does the NVIDIA GPU Operator install?
Running GPUs needs a kernel driver, a container runtime hook, the device plugin, labels, monitoring and, on Ampere or newer cards, something to manage MIG geometry. The GPU Operator deploys all of it as DaemonSets on every node that Node Feature Discovery identifies as carrying an NVIDIA PCI device, and reconciles it from one ClusterPolicy resource. The current release is v26.7.2, published on 14 October 2026, and its component matrix pins the pieces:
- Driver 595.91.07 by default and recommended, with 580.173.02, 610.57.04 and 615.71.09 also supported.
- Container Toolkit 1.20.1, device plugin and GPU Feature Discovery 0.20.1, MIG Manager 0.15.1, DCGM 4.6.1 and DCGM Exporter 4.6.1-4.8.4, Node Feature Discovery 0.19.0.
- Validated on Kubernetes 1.33 to 1.37 and containerd 2.0 to 2.3.
Two recent changes matter if you last looked a year ago. Since v25.10.0 the Operator injects GPUs through the Container Device Interface (CDI) by default and no longer makes the nvidia runtime class the default handler. And the 26.7 release notes add an OCI-hosted chart, a new GPUCluster resource that deploys the DRA stack instead of the device plugin (it needs Kubernetes 1.34.2 or later, a 580 or newer driver, and cannot coexist with ClusterPolicy), and, in 26.7.1, per-GPU CUDA memory limits through NVIDIA_GPU_MEMORY_REQUEST and NVIDIA_GPU_MEMORY_LIMIT on R615 drivers.
The practical point is that the Operator is a distribution, not a chart you install once. It moves the driver, the runtime hook and the scheduler-facing plugin together, and it should be upgraded on a schedule, from Git, like the cluster itself. The step-by-step install, verification and sharing setup are in the companion guide, How to Install NVIDIA GPU Operator and Share GPUs: Time-Slicing, MIG (2026).
MIG vs time-slicing vs MPS: how do you share a GPU?
There are three ways to put more than one workload on a card with the device plugin, and they make very different promises.
| Method | What is isolated | Memory limit per workload | Hardware | How it is configured | Source |
|---|---|---|---|---|---|
| Whole GPU (default) | Everything; one pod per device | Full card | Any NVIDIA GPU | nvidia.com/gpu: 1 in limits |
Kubernetes docs |
| Time-slicing | Nothing: no memory or fault isolation between replicas | None | Any, including cards without MIG | Device plugin ConfigMap with replicas |
NVIDIA time-slicing docs |
| MPS | Memory and compute capped per client by the MPS daemon | Equal fraction of device memory | Full GPUs only, not with MIG; experimental since plugin v0.15.0 | Device plugin ConfigMap, sharing.mps |
Device plugin README |
| MIG | Memory, cache and compute partitioned in hardware | Fixed per profile, e.g. 1g.10gb | Ampere and later (A100, H100 and newer) | MIG Manager, node label nvidia.com/mig.config |
NVIDIA MIG docs |
Time-slicing is oversubscription with a friendly name. You tell the device plugin to advertise each card as, say, four nvidia.com/gpu replicas, and CUDA gives every process an equal share of time. The device plugin README is blunt that each workload "runs in the same fault-domain" as the others, so one tenant exhausting memory takes its neighbours with it. Asking for two replicas does not buy twice the compute, which is why NVIDIA recommends failRequestsGreaterThanOne: true even though it defaults to false. There is also an observability cost: DCGM Exporter cannot associate metrics with containers when time-slicing is on, so per-pod GPU dashboards go blank.
MPS partitions memory and compute in software, each client getting an equal fraction, but it only works on full GPUs and the device plugin still labels it experimental. MIG is the only option with hardware isolation. On an H100 80GB the all-1g.10gb profile yields seven instances of about 10 GB and 16 SMs each, and all-balanced gives two 1g.10gb, one 2g.20gb and one 3g.40gb. The catch is that geometry is per node and changing it means MIG Manager stops every GPU pod on that node first. You can combine MIG with time-slicing on the resulting slices, which is how teams pack many small inference servers onto one card.
I run PandaStack, a Firecracker microVM cloud for agent code (it is my company, so weigh my bias), and the rule I apply there applies here: shared hardware without an isolation boundary is a scheduling convenience, not a tenancy model. Time-slicing is fine for one team's notebooks. I would not put two customers on it.
What is DRA, and does it replace the device plugin?
Dynamic Resource Allocation replaces the integer with a description. A DRA driver publishes ResourceSlice objects listing each device on a node with typed attributes and capacity. Admins define DeviceClass objects. Workloads create a ResourceClaim or a ResourceClaimTemplate that selects devices with CEL expressions, and the scheduler allocates a concrete device before binding the pod, much as a PersistentVolumeClaim binds storage.
The history is short and now settled. The core resource.k8s.io/v1 API went GA in Kubernetes 1.34, enabled by default, and the feature gate is locked on from 1.35. Kubernetes 1.36 made the prioritized list stable ("give me an H100, but if none are available, fall back to an A100") and moved partitionable devices, device taints, binding conditions and resource health to beta. Kubernetes 1.37, released in August, made extended-resource support GA, so a pod that still asks for a classic resource name can be satisfied by a DRA driver "without requiring a separate device plugin", made device taints stable, standardised a numaNode attribute, and added alpha compatibility groups that stop the scheduler pairing a MIG slice with a vGPU profile on the same card. The releases page lists 1.34 reaching end of life on 27 October 2026, so every release still in support (1.35 to 1.37) now has DRA as a stable API with the feature gate locked on.
One limitation is worth knowing before you plan around it: the DRA documentation states that the scheduler does not support preemption for DRA resources, so a high-priority pod waits until a device frees up.
NVIDIA's side is less finished than the Kubernetes side. The DRA driver was donated to the CNCF and moved to kubernetes-sigs/dra-driver-nvidia-gpu with v0.4.0 in May; v0.5.1 shipped on 12 October. It has two halves. ComputeDomains, which orchestrate Multi-Node NVLink for GB200-class systems, are officially supported. GPU allocation is not: the README says some GPU features "can be tried out" but are not yet officially supported, so the GPU kubelet plugin is disabled by default and the install command sets gpuResourcesEnabledOverride=true. v0.5.0 added multi-user MPS and alpha consumable capacity for sharing a GPU across namespaces.
So, does DRA replace the device plugin? For most clusters, not this quarter. What it gives you today is a cleaner request model, and 1.37's extended-resource bridge means you can move the backend later without rewriting every manifest. This is what a claim looks like with NVIDIA's driver; the attribute names come from its ResourceSlice reference.
# Kubernetes 1.36+ (prioritized list is stable from 1.36)
# NVIDIA DRA driver v0.5.1 installed with gpuResourcesEnabledOverride=true
apiVersion: resource.k8s.io/v1
kind: ResourceClaimTemplate
metadata:
name: inference-gpu
namespace: ml
spec:
spec:
devices:
requests:
- name: gpu
firstAvailable:
# First choice: a 3g.40gb MIG slice (60 SMs, about 40 GB on an H100 80GB)
- name: mig-slice
deviceClassName: mig.nvidia.com
selectors:
- cel:
expression: device.attributes["gpu.nvidia.com"].profile == "3g.40gb"
# Fallback: any whole GPU with at least 40Gi of memory
- name: whole-gpu
deviceClassName: gpu.nvidia.com
selectors:
- cel:
expression: device.capacity["gpu.nvidia.com"].memory.compareTo(quantity("40Gi")) >= 0
---
apiVersion: v1
kind: Pod
metadata:
name: inference-server
namespace: ml
spec:
restartPolicy: OnFailure
containers:
- name: server
image: nvcr.io/nvidia/k8s/cuda-sample:vectoradd-cuda12.5.0-ubuntu22.04
resources:
claims:
- name: gpu # matches resourceClaims[].name below
resourceClaims:
- name: gpu
resourceClaimTemplateName: inference-gpu
How do you schedule GPU jobs fairly? Kueue and quotas
The default scheduler places pods one at a time. It does not queue a training job until all eight of its workers fit, and it has no idea that the research team has used its share this week. Kueue adds that layer: ResourceFlavor describes a kind of node, ClusterQueue holds quota per flavor, LocalQueue is what a namespace submits to, and a workload is admitted whole, only when its quota is available. v0.20.0 landed on 30 September (v0.20.4 on 29 October) and no longer serves the v1beta1 API, so old manifests must move to v1beta2 before the upgrade. It also added an alpha KueueDRADeviceFeasibility gate so quota is not reserved for a DRA workload the scheduler cannot place.
# Kueue v0.20.x: only kueue.x-k8s.io/v1beta2 is served
apiVersion: kueue.x-k8s.io/v1beta2
kind: ResourceFlavor
metadata:
name: h100
spec:
nodeLabels:
nvidia.com/gpu.product: NVIDIA-H100-80GB-HBM3 # written by GPU Feature Discovery
---
apiVersion: kueue.x-k8s.io/v1beta2
kind: ClusterQueue
metadata:
name: ml-team
spec:
namespaceSelector: {}
resourceGroups:
- coveredResources: ["cpu", "memory", "nvidia.com/gpu"]
flavors:
- name: h100
resources:
- name: cpu
nominalQuota: 192
- name: memory
nominalQuota: 1536Gi
- name: nvidia.com/gpu
nominalQuota: 8 # with time-slicing on, this counts replicas, not cards
---
apiVersion: kueue.x-k8s.io/v1beta2
kind: LocalQueue
metadata:
name: training
namespace: ml
spec:
clusterQueue: ml-team
Jobs opt in with the kueue.x-k8s.io/queue-name: training label. Note the comment on the GPU quota: once a node advertises time-sliced replicas under nvidia.com/gpu, every quota and cost report that counts that resource is counting slices. The alternative if you want a replacement scheduler rather than an admission layer is NVIDIA's Apache-2.0 KAI Scheduler, at v0.18.6 as of 15 October. For the scaling side, node autoscalers and queue-based pod autoscaling, see Kubernetes Autoscaling Explained.
How busy are GPUs on Kubernetes, really?
Not very. Cast AI's 2026 State of Kubernetes Optimization report, which measured tens of thousands of clusters on AWS, Azure and Google Cloud through 2025, with GPU data to April 2026 and AI labs excluded, puts average GPU utilisation at 5 percent, against 8 percent for CPU and 20 percent for memory. It is one vendor's customer base, measured before its product was switched on, and the vendor sells the fix, so treat it as an upper bound on waste rather than an industry census.
The best counterweight is a hyperscaler's own paper. Alibaba Cloud told SOSP 2025 that before pooling it had dedicated 17.7 percent of its inference GPU fleet to serving 1.35 percent of requests, the long tail of rarely used models; its Aegaeon system cut the GPUs serving those models from 1,192 to 213 during a three-month beta, per The Register's write-up. Different method, same shape: GPUs are allocated by the card and used by the burst.
Measure your own before believing either. The GPU Operator ships DCGM Exporter, and its default metric set includes DCGM_FI_DEV_GPU_UTIL, the familiar busy percentage, plus DCGM_FI_PROF_GR_ENGINE_ACTIVE and DCGM_FI_PROF_PIPE_TENSOR_ACTIVE, the ratio of time the graphics engine is active and of cycles the tensor pipe is active. A card can show high busy time with a mostly idle tensor pipe; that gap is the honest utilisation number. The FinOps for AI post covers how Kubecost and OpenCost turn those counters into allocation.
Which cloud GPU node option should you use?
All three managed Kubernetes services will run GPUs without the GPU Operator. The question is how much control you need over drivers, sharing and metrics.
| Platform | Driver and plugin by default | Built-in sharing | To run the GPU Operator instead | Source |
|---|---|---|---|---|
| EKS Auto Mode | NVIDIA drivers and device plugin included, not visible as a DaemonSet | Not documented | Not applicable; Auto Mode manages the stack | AWS docs |
| EKS node groups (accelerated AMI) | Driver preinstalled, lags NVIDIA releases; you deploy the device plugin | Via device plugin config | Node group with an Operator-supported OS | NVIDIA EKS page |
| GKE | Google installs drivers and its own device plugin | Time-sharing, MIG (up to seven slices) and MPS per node pool | gpu-driver-version=disabled plus the gke-no-default-nvidia-gpu-device-plugin=true label |
GKE docs, NVIDIA GKE page |
| AKS | Driver installed on GPU node pools | Via Operator | --gpu-driver none on the node pool (Azure CLI 2.72.2+) |
Microsoft docs |
My rule of thumb: take the managed stack until you need something it will not give you, usually a specific MIG geometry, a newer driver branch for a new model or CUDA version, or the same DCGM metrics on every cloud. GKE also documents DRA for GPUs, which is the shortest path to trying claims on managed infrastructure. For the price side of the decision, the GPU cloud economics post has the hourly rates.
FAQ
Can Kubernetes share one GPU between pods?
Yes, but not with a plain request: nvidia.com/gpu is an integer and exclusive by default. Sharing comes from the NVIDIA device plugin (time-slicing or MPS), from MIG partitions on Ampere or newer cards, or from a DRA driver's per-claim sharing configuration. Only MIG gives hardware memory and fault isolation.
Is MIG better than time-slicing?
For isolation, yes: each MIG instance has its own memory and compute, so one workload cannot crash or starve another. Time-slicing works on any NVIDIA GPU and packs more workloads per card, but has no memory limits and breaks per-container DCGM metrics. Use MIG for multi-tenant inference on A100 or H100 class cards and time-slicing for development and notebooks.
Does DRA replace the NVIDIA device plugin?
Eventually, probably. The Kubernetes API has been GA since 1.34 and 1.37 lets DRA drivers satisfy classic extended-resource requests without a device plugin. NVIDIA's DRA driver v0.5.1 still marks GPU allocation as not yet officially supported, so production clusters should keep the device plugin and trial DRA on a separate node pool.
Do I need the GPU Operator on EKS, GKE or AKS?
No. EKS Auto Mode, GKE and AKS all install drivers and a device plugin for you. You need the Operator when you want control over the driver version, MIG Manager, NVIDIA's time-slicing configuration, or identical DCGM metrics across clouds.
If I were running GPU nodes on Kubernetes this quarter, I would do five things, in order. Move any cluster still on 1.34 now, because it reached end of life on 27 October and stops getting patches. Pin GPU Operator v26.7.2 in Git, with the driver branch written down next to the CUDA versions the images expect. Put DCGM_FI_PROF_GR_ENGINE_ACTIVE on a dashboard next to the allocation numbers and read it before anyone buys another card. Switch inference nodes on H100s to MIG, keep time-slicing for development pools with failRequestsGreaterThanOne on, and put Kueue quotas in front of both. Then stand up one non-production pool on the DRA driver and find out what breaks, because the API is finished and the drivers are catching up, and I would rather learn the migration on a quiet pool than during the release that forces it.
Related: How to Deploy vLLM on Kubernetes with GPUs: Step-by-Step (2026), GPU Clouds in 2026: H100s at $2.59 an Hour and Kubernetes as the Agent Control Plane in 2026.
I'm Ajay Kumar — I build and operate PandaStack, an open-source Firecracker microVM cloud for AI agents, and write about the infrastructure and tooling around it.
Need this kind of infrastructure work? See what I do or email hello@ajayk.sh.
Related
How to Install NVIDIA GPU Operator and Share GPUs: Time-Slicing, MIG (2026)
Step-by-step NVIDIA GPU Operator v26.7.2 install with Helm, verified with nvidia-smi and a CUDA test pod, then GPU sharing with a time-slicing ConfigMap or MIG profiles, DCGM Exporter metrics, and fixes for driver crash loops and nodes with no nvidia.com/gpu.
14 minOct 20, 2026How to Deploy vLLM on Kubernetes with GPUs: Step-by-Step (2026)
Deploy vLLM v0.32.0 on Kubernetes with NVIDIA GPUs: install GPU Operator v26.7.2, cache model weights on a PVC, serve an OpenAI-compatible API, autoscale on vllm:num_requests_waiting with KEDA, and fix CUDA out of memory and slow startup errors.
15 minOct 20, 2026How to Deploy an ML Model on Kubernetes with KServe 0.21 (2026)
Step-by-step KServe v0.21.0 guide: install in Standard mode with Helm, cert-manager and Gateway API, deploy scikit-learn and Hugging Face/vLLM InferenceServices, add HPA or KEDA autoscaling, canary rollouts, GPUs, and fix InferenceService not ready.
14 min