Dynamic Resource Allocation — это стандартный механизм Kubernetes для запроса и совместного использования устройств. Он даёт фильтрацию по атрибутам (CEL), шаринг, централизованные классы устройств и более правильный рабочий процесс, чем device plugins.
Но при этом DRA — это не один переключатель. Внутри него есть фичи с разными стадиями (stable/beta/alpha), и миграция с device plugins на DRA должна идти по ступеням.
В первой статье мы разобрали, почему device plugin рано или поздно упирается в потолок, если вы строите коммунальный кластер с продом, обучением и кучей разношёрстных нагрузок, а не используете GPU для одной команды.
В этой же статье я хочу сделать три вещи:
Показать, что именно Kubernetes добавил вместе с DRA и почему это действительно меняет модель управления устройствами.
Разложить реальный путь миграции с device plugin на примере коммунального кластера на H100, чтобы переход не выглядел как проект «переписать половину манифестов и заодно придумать новую религию».
Рассказать, почему мы делаем свой DRA-драйвер в Deckhouse Kubernetes Platform, а не берём готовые.
Меня по-прежнему зовут Александр Подмосковный, и я менеджер продукта в команде Deckhouse Kubernetes Platform, где развиваю направление ML/AI. Погнали разбираться с возможностями, которые даёт DRA.

Представим, что у нас есть общий кластер для нескольких команд:
8 GPU-узлов;
на каждом по 8 × NVIDIA H100 80GB;
всего 64 GPU.
Внутри кластера живут три класса нагрузок:
ML-обучение и эксперименты — им нужны «честные» карты и предсказуемость;
продовый инференс — много сервисов, которым часто не нужна вся H100;
короткие GPU-задачи — CI, профилировки, batch inference, эпизодические нагрузки.
В device-plugin-модели все они упираются в один и тот же примитив: «дай N устройств». А дальше начинается отдельная инженерная жизнь: Multi-Instance GPU, time-slicing, Multi-Process Service, scheduler-extender, очереди, пулы, правила, исключения. Именно здесь на сцену выходит DRA.
Dynamic Resource Allocation делает с устройствами примерно то же, что Kubernetes когда-то сделал с хранилищем:
раньше диск «как-то» подключали;
потом появились StorageClass и PersistentVolumeClaim;
и дальше стало проще строить платформу, а не систему договорённостей.
В документации Kubernetes DRA прямо сравнивают с динамическим выделением томов. Device drivers и администраторы описывают классы устройств, DRA-драйвер публикует инвентарь, а Kubernetes подбирает устройство под конкретную заявку пользователя и размещает под там, где оно доступно.
Главное изменение модели работы с ресурсами здесь простое:
GPU перестаёт быть числом в
NodeStatusи становится сущностью, для которой есть инвентарь, атрибуты и правила выбора.
Если убрать из DRA всю «идеологию» и оставить только прикладную механику, то есть четыре ключевые сущности.
В DRA именно драйвер публикует в кластер ResourceSlice — инвентарь доступных устройств с атрибутами и capacity. Это то место, где GPU впервые перестаёт быть просто «ещё одним счётчиком на узле».
Для GPU это принципиально, потому что теперь планировщик Kubernetes может видеть:
тип устройства;
память;
профиль MIG;
готовность для CUDA/VFIO/DRM;
топологию, interconnect и близость к сетевым устройствам.

В DRA планировщик выбирает устройство не только по числу GPU на узле. Он сопоставляет ResourceSlice, DeviceClass и ResourceClaim, после чего записывает результат allocation в ResourceClaim.
DeviceClass — это кластерный объект, который описывает:
какие устройства нас вообще интересуют;
как их фильтровать (обычно через CEL);
если нужно, каким extended resource name этот класс должен выглядеть для пользователей.
Этот слой заменяет «зоопарк» лейблов и пулов на более осмысленную декларативную модель. Например, класс «H100 целиком» выглядит так:
apiVersion: resource.k8s.io/v1
kind: DeviceClass
metadata:
name: nvidia-h100-physical
spec:
selectors:
- cel:
expression: |
device.driver == "gpu.deckhouse.io" &&
device.attributes["gpu.deckhouse.io"].vendor == "nvidia" &&
device.attributes["gpu.deckhouse.io"].deviceType == "Physical" &&
device.attributes["gpu.deckhouse.io"].cudaReady == true &&
device.attributes["gpu.deckhouse.io"].productName.lowerAscii().contains("h100")Смысл не в строках, а в том, что критерии выбора теперь живут в API-объекте, а не в договорённостях между людьми.
ResourceClaim — это заявка «мне нужен вот такой девайс (или его часть), с такими свойствами, в таком режиме».
ResourceClaimTemplate — это удобный путь для рабочих нагрузок: Pod/Deployment/StatefulSet создаёт заявки автоматически из шаблона. Документация Kubernetes прямо рекомендует использовать Template для per-Pod claims.
И вот здесь DRA становится по-настоящему интересным: в заявке можно не только запросить устройство, но и задать capacity request — например, долю shared-устройства.
Чтобы под реально получил устройство, у него появляются две части (подробности смотрите в документации):
spec.resourceClaims — откуда берётся claim;
resources.claims у контейнера — какой claim использовать.
Впервые прочитав про все сущности, кто-то может сказать: «Это слишком сложно!» И такая реакция будет честной. Но есть и хорошие новости — можно переезжать на DRA, не заставляя всех пользователей изучать DRA API.
Итак, посмотрев на необходимость разбираться с DeviceClass, ResourceClaimTemplate, spec.resourceClaims и resources.claims, можно испытать желание остаться на nvidia.com/gpu: 1. Но Kubernetes добавил очень важный мостик для миграции с device plugin, который многие пропускают.
Есть фича Extended Resource allocation by DRA. Она позволяет указать в DeviceClass поле extendedResourceName. Тогда под может продолжать использовать привычный extended resource в limits/requests, а планировщик будет выбирать устройства через DRA и автоматически создавать специальный ResourceClaim.
apiVersion: resource.k8s.io/v1
kind: DeviceClass
metadata:
name: nvidia-h100-physical
spec:
selectors:
- cel:
expression: |
device.attributes["gpu.deckhouse.io"].vendor == "nvidia" &&
device.attributes["gpu.deckhouse.io"].deviceType == "Physical" &&
device.attributes["gpu.deckhouse.io"].cudaReady == true &&
device.attributes["gpu.deckhouse.io"].productName.lowerAscii().contains("h100")
extendedResourceName: gpu.deckhouse.io/h100
Под при этом всё ещё выглядит знакомо. Это уже не device plugin, хотя снаружи очень похоже:
resources:
limits:
gpu.deckhouse.io/h100: 8Важные детали:
На момент написания статьи фича Extended Resource allocation by DRA в бете, поэтому она включается feature gate’ом DRAExtendedResource на apiserver, scheduler, controller-manager и kubelet.
Один и тот же extended resource может предоставляться device plugin на одних узлах и DRA на других. Это и делает поэтапную миграцию реальной.
Но на одном и том же узле один и тот же ресурс не должен одновременно приезжать и от device plugin, и от DRA — это прямо учтено в KEP extended resource requests via DRA Driver.
Практический вывод:
Если вы хотите начать миграцию без переписывания всех подов в новый DRA-синтаксис, есть рабочий путь: сначала мостик через extended resources, потом уже всё остальное.
Вот место, которое важно проговорить отдельно.
DRA в Kubernetes — это не новый способ написать YAML. Это контракт, а реальная система появляется только вместе с DRA-драйвером.
Kubernetes так и формулирует: DRA-драйверы публикуют инвентарь, а kubelet вызывает их для подготовки и освобождения устройства в течение жизненного цикла пода. Вот что делает DRA-драйвер на узле:

Упрощённый workflow такой:
Драйвер публикует ResourceSlice.
Планировщик делает allocation и пишет его в ResourceClaim.
kubelet вызывает PrepareResourceClaims.
Драйвер готовит устройство и возвращает то, что нужно рантайму.
При завершении работы пода kubelet вызывает UnprepareResourceClaims.
Физически это gRPC-сервис на Unix-сокете. В helper-пакете Kubernetes прямо описаны dra.sock и каталог /var/lib/kubelet/plugins/<driver name> для плагина на узле.
[root@k8s-w3-gpu gpu.deckhouse.io]# pwd
/var/lib/kubelet/plugins/gpu.deckhouse.io
[root@k8s-w3-gpu gpu.deckhouse.io]# ls -lha
total 5.4M
drwxr-xr-x. 3 root root 4.0K Mar 16 12:39 .
drwxr-x---. 4 root root 4.0K Jan 30 07:12 ..
-rw-r--r--. 1 root root 2.0K Mar 16 12:39 checkpoint.json
-rw-------. 1 root root 0 Feb 18 18:57 cp.lock
srwxr-xr-x. 1 root root 0 Mar 12 08:56 dra.sock
-rw-r--r--. 1 root root 347 Mar 7 10:50 mig-placements-cache.json
drwxr-xr-x. 2 root root 4.0K Mar 15 10:11 mps
-rwxr-xr-x. 1 root root 554 Mar 12 08:56 nvidia-cdi-hook
-rwxr-xr-x. 1 root root 5.3M Mar 12 08:56 nvidia-cdi-hook.bin
-rw-r--r--. 1 root root 0 Feb 25 00:44 prepare.lock
-rw-r--r--. 1 root root 0 Jan 15 23:18 pu.lockСовременная экосистема DRA обычно опирается на CDI (Container Device Interface) как стандарт для описания устройства для контейнерного рантайма. CDI не решает вопрос, кому дать GPU, — он стандартизирует только способ инъекции устройства в контейнер.
И это важный практический момент: если у вас неправильно включён CDI в containerd/CRI-O, то DRA-allocation может выглядеть корректно в API, но контейнер устройства не увидит.
Описанные выше контракт DRA, драйверы, ResourceSlice, Prepare/Unprepare — это upstream-механика. Теперь коротко посмотрим, как она реализована в GPU-модуле Deckhouse Kubernetes Platform, ведь именно на примере нашей платформы я буду разбирать переезд коммунального кластера на DRA-модель.
Deckhouse Kubernetes Platform уже с версии 1.71 умеет управлять GPU-узлами на NVIDIA-стеке, включая режимы Exclusive, TimeSlicing и Multi-Instance GPU (MIG). Для работы GPU-узла нужны драйвер NVIDIA и Container Toolkit — их можно поставить вручную или автоматизировать через NodeGroupConfiguration. Но управление самими GPU-ресурсами на уровне Kubernetes — публикация инвентаря, allocation, подготовка устройства для контейнера — это уже задача DRA-драйвера внутри GPU-модуля. И этот драйвер мы пишем сами.
В нашей текущей реализации (до появления UX-слоя GPUClass) работа с GPU проходит через несколько этапов:
gpu-node-agent сканирует sysfs/PCI и создаёт PhysicalGPU.
gpu-handler читает атрибуты устройства и публикует ResourceSlice.
Пользователь вручную создаёт ResourceClaimTemplate и под с resourceClaims.
Планировщик делает allocation.
Kubelet вызывает DRA-плагин, а gpu-handler выполняет CDI-инъекцию, при необходимости создаёт MIG-слайсы и запускает MPS.

Это очень важный слой, потому что он показывает две вещи.
Когда есть PhysicalGPU и ResourceSlice, вы получаете реальные точки диагностики:
что за устройство;
в каком оно режиме;
какие capabilities есть;
что именно было опубликовано планировщику.
И это на порядок лучше, чем смотреть в логи плагина и догадываться.
Когда под получает allocation и попадает на узел, kubelet вызывает у DRA-драйвера два gRPC-метода: PrepareResources при старте и UnprepareResources при завершении.
В Prepare/Unprepare живёт вся тяжёлая работа:
создание MIG-инстансов под запрос;
включение Multi-Process Service (MPS) под конкретную долю;
подготовка VFIO;
настройка режимов выделения и правила шаринга.
Всё это происходит не в одном красивом DeviceClass, а в коде драйвера на узле.
И здесь — принципиальное отличие DRA от device-plugin-мира, где Allocate слишком узок для такой модели, а всё сложное быстро уползает во внешние контроллеры и побочные механизмы.
Теперь возьмём наш коммунальный кластер из примера в начале статьи и пройдём путь от device-plugin-модели к DRA — не абстрактно, а руками. Кластер работает под управлением Deckhouse Kubernetes Platform, где DRA-драйвер для GPU поставляется модулем GPU.
Напомню, что в кластере есть три класса нагрузок: обучение, продовый инференс и короткие GPU-задачи.

Переезд на DRA не обязан быть одним большим переключением. Training, prod inference и небольшие AI-сервисы переносятся разными слоями и проверяются по разным метрикам.
Обучение — не лучший кандидат на первую волну «умного шаринга». Там важнее сохранить предсказуемость.
Поэтому для H100 training-пул в DRA-модели можно начать так:
DeviceClass выбирает только physical H100, то есть целую видеокарту;
под продолжает просить extended resource, который теперь обслуживает DRA.
Пример класса:
apiVersion: resource.k8s.io/v1
kind: DeviceClass
metadata:
name: nvidia-h100-physical
spec:
selectors:
- cel:
expression: |
device.driver == "gpu.deckhouse.io" &&
device.attributes["gpu.deckhouse.io"].vendor == "nvidia" &&
device.attributes["gpu.deckhouse.io"].deviceType == "Physical" &&
device.attributes["gpu.deckhouse.io"].cudaReady == true &&
device.attributes["gpu.deckhouse.io"].productName.lowerAscii().contains("h100")
extendedResourceName: gpu.deckhouse.io/h100И training workload почти не меняется:
apiVersion: batch/v1
kind: Job
metadata:
name: llm-train-single-node
namespace: ml-team
spec:
template:
spec:
restartPolicy: Never
containers:
- name: trainer
image: pytorch/pytorch:2.9.1-cuda12.8-cudnn9-runtime
command: ["bash","-lc"]
args:
- |
nvidia-smi
python -c "print('training started')"
resources:
limits:
gpu.deckhouse.io/h100: 8Мостик через extended resources уже даёт вам полезную миграцию: путь выделения устройства новый, пользовательский контракт почти старый.
С инференсом всё интереснее. Там чаще нужен не «1 GPU», а:
профиль с конкретной памятью;
доля на общей карте;
контролируемый режим шеринга.
Если мы идём по пути MIG, DeviceClass начинает выражать уже не «любая из H100», а конкретный профиль. Например, под условную H100 MIG-профиль:
apiVersion: resource.k8s.io/v1
kind: DeviceClass
metadata:
name: nvidia-h100-mig-3g40
spec:
selectors:
- cel:
expression: |
device.driver == "gpu.deckhouse.io" &&
device.attributes["gpu.deckhouse.io"].vendor == "nvidia" &&
device.attributes["gpu.deckhouse.io"].deviceType == "MIG" &&
device.attributes["gpu.deckhouse.io"].migProfile == "3g.40gb"
extendedResourceName: gpu.deckhouse.io/h100-mig-40А дальше inference-сервис может писать почти «по-старому»:
resources:
limits:
gpu.deckhouse.io/h100-mig-40: 1Тут впервые становится видно, что DRA — это не просто новый API. Это способ говорить с кластером на языке «дай 40Gi-профиль», а не «дай целую карту, а дальше мы сами разберёмся».
Слой 3: небольшие AI-сервисы начинают делить профиль по правилам
Но на профилях переезд кластера ещё не заканчивается.
MIG-профиль решает проблему «мне не нужна целая H100». Но в реальном инференс-пуле быстро появляется следующий слой нагрузок: reranker, embeddings, summarizer, небольшие LLM-воркеры, batch inference и другие сервисы, которым часто не нужен даже весь выделенный MIG-профиль.
Если выдавать им профиль эксклюзивно, мы снова получаем старую проблему в новой форме: ресурс формально занят, а полезная загрузка ниже, чем могла бы быть.
Поэтому третий слой переезда — это уже не целая карта и не выделенный профиль, а контролируемая доля устройства.
И здесь важно разделить два уровня:
Kubernetes должен понимать, какая часть устройства уже занята и сколько ещё можно выдать.
Сам GPU должен уметь исполнять несколько CUDA-клиентов так, чтобы один сосед не забрал всё.
За первое в DRA отвечает consumable capacity. За второе в нашем примере отвечает MPS.

В DRA есть фича consumable capacity: одно и то же физическое устройство может выделяться нескольким независимым claims, а планировщик следит, чтобы суммарная потреблённая ёмкость не превышала capacity устройства. Это именно та модель, которая нужна для шаринга с контролем ресурсов.
На практике это значит, что если у GPU или MIG-профиля есть sharePercent: 100, то нескольким подам можно выдать 25 + 25 + 50, и Kubernetes будет понимать, что весь бюджет уже занят.
Это принципиально отличается от «просто размножили ресурс» в time-slicing. Здесь планировщик видит не набор условных одинаковых слотов, а потребление ёмкости конкретного устройства.
capacity:
requests:
sharePercent: "25"Но consumable capacity сам по себе не заставляет CUDA-процессы эффективнее жить на одной карте. Он отвечает за учёт бюджета в Kubernetes.
Теперь нужен механизм на стороне GPU.
Про Multi-Process Service люди часто слышат обрывками. Разберём по порядку: зачем он нужен, чем отличается от time-slicing, какие у него есть ограничения и настройки.
NVIDIA MPS нужен там, где каждый отдельный процесс не насыщает GPU. NVIDIA прямо пишет: без MPS процессы конкурируют за scheduling resources и контексты GPU приходится переключать; MPS позволяет нескольким процессам работать через общий MPS server, уменьшая эти накладные расходы и повышая concurrency. Это полезно именно для тех случаев, где рабочие нагрузки по отдельности недогружают GPU.
Для инференс-мира это очень понятный кандидат:
много небольших сервисов;
у каждого своя модель или свой batch;
отдельный сервис не нагружает H100 на 100 %;
хочется делить карту совместно, а не просто по очереди.
MPS часто лучше time-slicing. Последний делит GPU по времени. MPS же позволяет нескольким процессам одновременно иметь более эффективный доступ к GPU и уменьшает оверхед на переключение контекста.
При этом стоит иметь в виду, что MPS — это не замена Multi-Instance GPU. В целом выбор режима зависит от ваших требований к изоляции и утилизации ресурсов:
Режим | Когда использовать |
Multi-Instance GPU | Нужны аппаратная изоляция и жёсткие гарантии ресурсов |
Multi-Process Service | Нужно повысить concurrency и утилизацию в доверенном контуре, где важен controlled sharing |
Time-slicing | Нужен максимально простой массовый шаринг, а требования к изоляции ниже |
Итак, MPS позволяет нескольким процессам одновременно работать на одной видеокарте. Но без ограничений один клиент может «выжрать» все вычислительные ресурсы или всю память. Чтобы этого не произошло, предусмотрены два предохранителя.
В MPS есть официальный механизм active thread percentage. Это настройка, которая ограничивает долю вычислительных ресурсов, доступных клиенту MPS server. NVIDIA отдельно описывает команды set_default_active_thread_percentage / set_active_thread_percentage и объясняет, что эта настройка используется для управления справедливым распределением ресурсов между клиентами.
Это та техническая ручка, которая делает осмысленным sharePercent в DRA-модели.
У MPS есть не только ограничение по вычислениям, но и отдельный потолок по памяти видеокарты для клиентов. В документации NVIDIA он называется pinned device memory limit.
Название может сбивать с толку, но смысл простой: это ограничение на то, сколько памяти видеокарты клиент MPS может занять через CUDA. Память не резервируется заранее и не нарезается на фиксированные куски. MPS-сервер задаёт верхний предел: больше этого объёма клиенту выделить не дадут.
Для этого NVIDIA описывает команды set_default_device_pinned_mem_limit и set_device_pinned_mem_limit. Первая задаёт потолок по памяти, которую будут получать клиенты новых MPS-серверов. Вторая задаёт такой потолок для клиентов уже запущенного MPS-сервера.
Дополнительно лимит можно ужесточить на стороне самого клиента через переменную CUDA_MPS_PINNED_DEVICE_MEM_LIMIT. Но клиент не может поднять лимит выше того, что разрешил MPS-сервер.
Практический смысл простой: если вы используете видеокарту в общем режиме через MPS, нужно ограничивать не только вычисления, но и память. Иначе один клиент может занять слишком много памяти видеокарты и помешать остальным, даже если доля вычислений у него ограничена.
MPS приживается в DRA-парадигме лучше, чем в device-plugin-мире. В последнем нет нативной модели, которая позволит сказать: «для этого пода — 25 % карты». В DRA такая модель появляется через consumable capacity.
Ниже — паттерн, который используется в низкоуровневом слое GPU-модуля Deckhouse Kubernetes Platform.
DeviceClass для physical H100 c MPS:
apiVersion: resource.k8s.io/v1
kind: DeviceClass
metadata:
name: nvidia-h100-mps
spec:
selectors:
- cel:
expression: |
device.driver == "gpu.deckhouse.io" &&
device.attributes["gpu.deckhouse.io"].vendor == "nvidia" &&
device.attributes["gpu.deckhouse.io"].deviceType == "Physical" &&
device.attributes["gpu.deckhouse.io"].cudaReady == true &&
device.attributes["gpu.deckhouse.io"].productName.lowerAscii().contains("h100")
config:
- opaque:
driver: gpu.deckhouse.io
parameters:
apiVersion: resource.gpu.deckhouse.io/v1alpha1
kind: GpuConfig
sharing:
strategy: MPS
mpsConfig:
defaultActiveThreadPercentage: 25ResourceClaimTemplate на 25 % доли видеокарты:
apiVersion: resource.k8s.io/v1
kind: ResourceClaimTemplate
metadata:
name: rct-h100-mps-25
namespace: inference-team
spec:
spec:
devices:
requests:
- name: gpu
exactly:
deviceClassName: nvidia-h100-mps
count: 1
capacity:
requests:
sharePercent: "25"Pod/Deployment, который потребляет claim:
apiVersion: apps/v1
kind: Deployment
metadata:
name: reranker-mps
namespace: inference-team
spec:
replicas: 4
selector:
matchLabels:
app: reranker-mps
template:
metadata:
labels:
app: reranker-mps
spec:
resourceClaims:
- name: gpu
resourceClaimTemplateName: rct-h100-mps-25
containers:
- name: app
image: ghcr.io/example/reranker:1.2.3
resources:
requests:
cpu: "2"
memory: "8Gi"
limits:
cpu: "4"
memory: "16Gi"
claims:
- name: gpuВ отличие от time-slicing, здесь под запрашивает не «какой-нибудь GPU», а осмысленную долю карты, и планировщик учитывает её как реальный бюджет.
После трёх примеров получается понятный порядок миграции.
Обучение оставляем на целых GPU. Здесь не надо первым делом придумывать шаринг: важнее сохранить предсказуемость и производительность.
Продовый инференс переводим с целой карты на профиль. Например, вместо всей H100 сервис получает MIG 3g.40gb, если этого достаточно по памяти и производительности.
Небольшие AI-сервисы и batch-задачи переводим на доли. Reranker, embeddings, summarizer и похожие сервисы часто не используют весь профиль целиком, поэтому здесь уже появляются MPS и sharePercent.
В итоге миграция выглядит так:
training → full GPU
prod inference → MIG-профиль
small AI / batch → MPS-доляМы не трогаем весь кластер одним движением. Каждый тип нагрузки переезжает отдельно и проверяется по своим метрикам:
training → не просела производительность
prod inference → не ухудшилась latency
small AI / batch → выросла плотность размещенияНа этом базовая схема переезда понятна. Но для продовой эксплуатации нужно больше, чем просто разложить нагрузки по режимам.
Нам нужно видеть:
что именно выделено конкретному claim;
готово ли устройство до запуска пода;
как описывать динамические партиции вроде MIG.
Есть ещё несколько фич DRA, которые напрямую влияют на то, насколько «по-взрослому» вы сможете эксплуатировать GPU. В feature gates GPU-модуля Deckhouse Kubernetes Platform все они фигурируют как единый набор DRA-возможностей.
Фича DRAResourceClaimDeviceStatus позволяет DRA-драйверу писать status в ResourceClaim.status.devices. Это полезно для диагностики: вы видите не только то, что под не стартует, но и то, какое именно устройство ему назначено, в каком оно режиме и готово ли к работе.
Фича DRADeviceBindingConditions добавляет условия привязки устройств к поду. Это особенно интересно для сложных сценариев вроде fabric-attached devices или будущих GPU+RDMA-связок: планировщик может задерживать bind, пока не выполнены условия готовности.
DRAPartitionableDevices — это важный слой под hardware partitioning. Он позволяет драйверу объявлять доступными для планировщика несколько логических устройств, которые потребляют одни и те же физические ресурсы. Для MIG-подобных историй это очень естественная модель.
Выше я коротко упоминал, что мы пишем свой DRA-драйвер. Такие утверждения нередко вызывают у читателей вопрос: «Зачем?»
У NVIDIA есть свой DRA Driver for GPUs, и в документации они честно описывают, что понадобится для его работы:
Kubernetes 1.34.2+;
включённый Container Device Interface;
включённый feature gate DRAExtendedResource, если хотите использовать привычные nvidia.com/gpu;
и, что важно, отключённый NVIDIA Kubernetes Device Plugin, чтобы не было конфликта с DRA Driver for GPUs.
У AMD тоже уже есть свой DRA-драйвер. В обзорной статье они прямо показывают, как DRA переводит GPU в first-class, а также attribute-aware-ресурсы: ResourceSlice, декларативный ResourceClaim, атрибуты вроде partitionProfile, модель/память/PCIe-root и так далее. При этом сам GitHub-репозиторий честно помечен как «experimental/alpha».
То есть вендорский путь существует. Но нам нужно больше, чем просто аллокация GPU через DRA. Мы хотим видеть внутри GPU-модуля Deckhouse Kubernetes Platform:
мультивендорность;
единый инвентарь через PhysicalGPU;
on-demand-режимы — MIG, MPS и другие;
мостик к более простому UX;
перспективу NVLink/RDMA-топологии и расширенного планирования.
Поэтому мы и строим свой слой, где DRA остаётся стандартом Kubernetes, а платформа получает нужные пользователям свойства.
Для хардкорных ребят: зачем мы делаем свой DRA-драйвер на примере динамического Multi-Instance GPUСуществование в Kubernetes фичи DRAPartitionableDevices не просто означает, что теперь можно описать партиции устройства. Смысл сильнее: DRA-драйвер может публиковать несколько логических устройств, которые потребляют общие ресурсы одной физической карты, в том числе перекрывающиеся. Планировщик при этом выбирает совместимые партиции и не позволяет одновременно выдать те, которые конфликтуют за одни физические ресурсы.
Более того, такая модель даёт вендору возможность создавать партиции динамически в момент allocation, а не держать карту заранее нарезанной в отдельном статическом пуле. Это прямо зафиксировано и в KEP по partitionable devices, и в релизных материалах Kubernetes по DRA.
Для NVIDIA Multi-Instance GPU динамическое партицирование особенно важно. MIG не просто делит GPU на несколько одинаковых прямоугольников. В KEP разобран пример A100: профиль MIG потребляет сразу несколько измерений ёмкости — memory slices, multiprocessors, copy engines, decoder/encoder blocks и так далее. При этом разные MIG-профили могут перекрываться по одним ресурсам и не конфликтовать по другим.
Планировщик должен уметь работать не только с количеством профилей, а с общими счётчиками и конфликтующими вариантами разбиения. Именно это и даёт DRAPartitionableDevices: устройство можно представить как «мешок» ресурсов, из которого в момент выделения собирается подходящий профиль.
Именно здесь начинается то, ради чего мы в Deckhouse делаем свой DRA-драйвер.
В нашем дизайне источником истины для публикации офферов является не текущий моментальный срез с узла, а PhysicalGPU.status.capabilities + currentState. Мы строим ResourceSlice из инвентаря PhysicalGPU, а не просто из того, что сейчас уже создано на хосте.
Это даёт очень важное следствие: для MIG-capable карты мы можем публиковать MIG-офферы, даже когда карта работает в full-режиме, если у нас есть валидные профили и placements. Если профиль/placement ещё неполный, запускается bootstrap-обогащение: на свободной карте временно включается MIG-режим, дочитываются профили/placements, которые дальше кешируются и становятся частью инвентаря. Кластер знает не только то, что карта делает сейчас, но и то, какие режимы она вообще умеет предоставить.
На этапе Prepare опубликованные офферы реализуются как настоящий dynamic MIG. В GPU-модуле Deckhouse Kubernetes Platform gpu-handler не просто инжектит CDI-устройство в контейнер, а умеет:
определить, что claim требует MIG-профиль;
при необходимости включить MIG mode на свободной карте;
через auto-MIG planner посчитать, какие GPU Instance/Compute Instance нужно создать;
создать нужный MIG-инстанс под конкретный claim;
и на Unprepare убрать его обратно, если он был создан динамически.
Это очень сильно отличается от статической MIG-модели, где вы заранее режете узел под один профиль и потом распределяете рабочие нагрузки внутри этого решения. У нас другой опыт эксплуатации:
не нужно заранее держать отдельные MIG-пулы под каждый профиль;
не нужно вручную переводить узлы между full и MIG-режимом как отдельную операцию для пользователей;
не нужно заранее угадывать, какой профиль сегодня победит по спросу.
Важно, что я сейчас не обещаю чудес без единого движения на узле. Если для переключения режима нужно обслуживание, вытеснение подов или временная пауза на GPU-узле, наш драйвер делает это контролируемо через maintenance-поток.
Но главное здесь в другом: переход между Physical и MIG становится частью жизненного цикла аллокации, а не отдельным ручным процессом платформенной команды.
Вторая важная вещь: MPS поверх dynamic MIG.
Для нас shared-режим — это не «добавим ещё одну технологию в список», а часть модели controlled sharing. В текущей ветке GPU-модуль уже поддерживает:
MPS на Physical;
MPS на MIG;
sharePercent как пользовательский бюджет для shared-режима;
публикацию sharePercent через DRA capacity при включённом DRAConsumableCapacity.
Именно поэтому DRAPartitionableDevices для нас важен не сам по себе, а как база для связки dynamic MIG + MPS + единый DRA-жизненный цикл.
Пользователь может попросить не подготовленный заранее узел, а подходящий профиль, а уже Deckhouse Kubernetes Platform решает:
создать ли MIG-слайс под запрос;
использовать ли shared-режим;
как это опубликовать, заквотировать и потом корректно освободить.
Уже в этой точке видно, почему нам недостаточно просто взять nvidia-dra-driver-gpu как есть.
У NVIDIA DRA-драйвер действительно уже умеет controlled sharing и dynamic reconfiguration GPU. Однако если посмотреть на dynamic MIG в их текущей реализации, то там есть важные ограничения.
Эта возможность помечена как alpha и в первой релизной итерации ориентирована на H100 и новее, а для видеокарты A100 прямо оговорено, что она пока не поддерживается, потому что не умеет свободно включать/выключать MIG mode. Кроме того, включение DynamicMIG у NVIDIA пока взаимоисключает MPSSupport, NVMLDeviceHealthCheck и PassthroughSupport.
Для нас этого недостаточно по простой причине — нам нужна единая модель, где в одном драйвере и одном инвентаре живут:
dynamic MIG;
MPS на Physical и на MIG;
переключение между full-режимом и MIG-режимом по запросу;
VFIO как режим Physical;
мультивендорный контур с AMD/Intel и будущей топологией.
DRAPartitionableDevices — не просто одна из полезных фич DRA. В GPU-мире это та точка, где DRA начинает давать новое качество платформы. Не просто «мы умеем описать GPU», а «мы умеем публиковать возможные режимы карты, выбирать их под запрос и создавать нужную конфигурацию в момент выделения».
Именно поэтому в нашем модуле эта фича идёт в одном пакете с DRAConsumableCapacity, DRAExtendedResource, DRAResourceClaimDeviceStatus и DRADeviceBindingConditions. Только вместе они складываются в действительно рабочую модель GPU-платформы.
Если собрать весь материал статьи воедино, то Dynamic Resource Allocation — это не дополнительный API поверх device plugin. DRA меняет саму модель работы с шаренными устройствами внутри одного Kubernetes-кластера:
инвентарь устройств становится объектом API (ResourceSlice);
заявка на устройство или его часть становится декларативной (ResourceClaim / Template);
жизненный цикл устройства появляется как нормальный prepare/unprepare;
появляется реальный путь к controlled sharing (consumable capacity) и hardware partitioning.
А ещё DRA даёт очень важную вещь именно для миграции с device plugin: можно начать с привычных extended resources, а уже потом переходить к более выразительной модели.
В финальной статье мы сделаем следующий шаг: покажем, как превратить этот мощный, но местами многословный DRA-слой в зDRAвый пользовательский опыт. Такой, где разработчик просит «класс GPU + память + количество + долю», а платформа берёт на себя всю сложность и эксплуатационные гарантии.