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 узлов × 8 видеокарт H100
Представим, что у нас есть общий кластер для нескольких команд:
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.
Короткая и полезная мысль про DRA
Dynamic Resource Allocation делает с устройствами примерно то же, что Kubernetes когда-то сделал с хранилищем:
раньше диск «как-то» подключали;
потом появились StorageClass и PersistentVolumeClaim;
и дальше стало проще строить платформу, а не систему договорённостей.
В документации Kubernetes DRA прямо сравнивают с динамическим выделением томов. Device drivers и администраторы описывают классы устройств, DRA-драйвер публикует инвентарь, а Kubernetes подбирает устройство под конкретную заявку пользователя и размещает под там, где оно доступно.
Главное изменение модели работы с ресурсами здесь простое:
GPU перестаёт быть числом в
NodeStatusи становится сущностью, для которой есть инвентарь, атрибуты и правила выбора.
Четыре сущности, без которых DRA не понять
Если убрать из DRA всю «идеологию» и оставить только прикладную механику, то есть четыре ключевые сущности.
1. ResourceSlice: инвентарь, который публикует DRA-драйвер
В DRA именно драйвер публикует в кластер ResourceSlice — инвентарь доступных устройств с атрибутами и capacity. Это то место, где GPU впервые перестаёт быть просто «ещё одним счётчиком на узле».
Для GPU это принципиально, потому что теперь планировщик Kubernetes может видеть:
тип устройства;
память;
профиль MIG;
готовность для CUDA/VFIO/DRM;
топологию, interconnect и близость к сетевым устройствам.

В DRA планировщик выбирает устройство не только по числу GPU на узле. Он сопоставляет ResourceSlice, DeviceClass и ResourceClaim, после чего записывает результат allocation в ResourceClaim.
2. DeviceClass: категория устройств и правила выбора
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-объекте, а не в договорённостях между людьми.
3. ResourceClaim / ResourceClaimTemplate: заявка на устройство
ResourceClaim — это заявка «мне нужен вот такой девайс (или его часть), с такими свойствами, в таком режиме».
ResourceClaimTemplate — это удобный путь для рабочих нагрузок: Pod/Deployment/StatefulSet создаёт заявки автоматически из шаблона. Документация Kubernetes прямо рекомендует использовать Template для per-Pod claims.
И вот здесь DRA становится по-настоящему интересным: в заявке можно не только запросить устройство, но и задать capacity request — например, долю shared-устройства.
4. Pod: связка spec.resourceClaims и resources.claims
Чтобы под реально получил устройство, у него появляются две части (подробности смотрите в документации):
spec.resourceClaims— откуда берётся claim;resources.claimsу контейнера — какой claim использовать.
Впервые прочитав про все сущности, кто-то может сказать: «Это слишком сложно!» И такая реакция будет честной. Но есть и хорошие новости — можно переезжать на DRA, не заставляя всех пользователей изучать DRA API.
Где появляется ответ на вопрос, как переезжать на DRA и не плакать
Итак, посмотрев на необходимость разбираться с 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 не существует без драйвера: от контракта к реализации
Вот место, которое важно проговорить отдельно.
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
CDI: как устройство попадает в контейнер
Современная экосистема DRA обычно опирается на CDI (Container Device Interface) как стандарт для описания устройства для контейнерного рантайма. CDI не решает вопрос, кому дать GPU, — он стандартизирует только способ инъекции устройства в контейнер.
И это важный практический момент: если у вас неправильно включён CDI в containerd/CRI-O, то DRA-allocation может выглядеть корректно в API, но контейнер устройства не увидит.
Как устроен DRA-драйвер в GPU-модуле Deckhouse Kubernetes Platform
Описанные выше контракт 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.

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

Переезд на DRA не обязан быть одним большим переключением. Training, prod inference и небольшие AI-сервисы переносятся разными слоями и проверяются по разным метрикам.
Слой 1: обучение оставляем максимально похожим на старый мир
Обучение — не лучший кандидат на первую волну «умного шаринга». Там важнее сохранить предсказуемость.
Поэтому для 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 уже даёт вам полезную миграцию: путь выделения устройства новый, пользовательский контракт почти старый.
Слой 2: продовый инференс начинает жить профилями, а не целыми видеокартами
С инференсом всё интереснее. Там чаще нужен не «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.

Consumable capacity: как Kubernetes считает доли
В DRA есть фича consumable capacity: одно и то же физическое устройство может выделяться нескольким независимым claims, а планировщик следит, чтобы суммарная потреблённая ёмкость не превышала capacity устройства. Это именно та модель, которая нужна для шаринга с контролем ресурсов.
На практике это значит, что если у GPU или MIG-профиля есть sharePercent: 100, то нескольким подам можно выдать 25 + 25 + 50, и Kubernetes будет понимать, что весь бюджет уже занят.
Это принципиально отличается от «просто размножили ресурс» в time-slicing. Здесь планировщик видит не набор условных одинаковых слотов, а потребление ёмкости конкретного устройства.
capacity: requests: sharePercent: "25"
Но consumable capacity сам по себе не заставляет CUDA-процессы эффективнее жить на одной карте. Он отвечает за учёт бюджета в Kubernetes.
Теперь нужен механизм на стороне GPU.
MPS: как GPU исполняет несколько CUDA-клиентов
Про 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 позволяет нескольким процессам одновременно работать на одной видеокарте. Но без ограничений один клиент может «выжрать» все вычислительные ресурсы или всю память. Чтобы этого не произошло, предусмотрены два предохранителя.
1. Что такое active thread percentage в MPS
В MPS есть официальный механизм active thread percentage. Это настройка, которая ограничивает долю вычислительных ресурсов, доступных клиенту MPS server. NVIDIA отдельно описывает команды set_default_active_thread_percentage / set_active_thread_percentage и объясняет, что эта настройка используется для управления справедливым распределением ресурсов между клиентами.
Это та техническая ручка, которая делает осмысленным sharePercent в DRA-модели.
2. Что такое pinned memory limit и зачем он нужен
У 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 + consumable capacity: как шаринг выражается через DRA
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: 25
ResourceClaimTemplate на 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-платформы
Есть ещё несколько фич DRA, которые напрямую влияют на то, насколько «по-взрослому» вы сможете эксплуатировать GPU. В feature gates GPU-модуля Deckhouse Kubernetes Platform все они фигурируют как единый набор DRA-возможностей.
ResourceClaim device status
Фича DRAResourceClaimDeviceStatus позволяет DRA-драйверу писать status в ResourceClaim.status.devices. Это полезно для диагностики: вы видите не только то, что под не стартует, но и то, какое именно устройство ему назначено, в каком оно режиме и готово ли к работе.
Device Binding Conditions
Фича DRADeviceBindingConditions добавляет условия привязки устройств к поду. Это особенно интересно для сложных сценариев вроде fabric-attached devices или будущих GPU+RDMA-связок: планировщик может задерживать bind, пока не выполнены условия готовности.
Partitionable devices
DRAPartitionableDevices — это важный слой под hardware partitioning. Он позволяет драйверу объявлять доступными для планировщика несколько логических устройств, которые потребляют одни и те же физические ресурсы. Для MIG-подобных историй это очень естественная модель.
Почему мы в Deckhouse делаем свой DRA-драйвер, а не просто берём vendor reference
Выше я коротко упоминал, что мы пишем свой 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-платформы.
Итоги: что DRA меняет в управлении устройствами
Если собрать весь материал статьи воедино, то 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 + память + количество + долю», а платформа берёт на себя всю сложность и эксплуатационные гарантии.
ishlykov
Отличная статья, но вот только количество ллм-ых клише переходит все границы. Очень сложно отбиться от ощущения, что читаешь нейрослоп, хотя содержание годное.