Драматургия GPU в Kubernetes: зачем нужен DRA, если Device Plugin работает?

Драматургия GPU в Kubernetes: зачем нужен DRA, если Device Plugin работает?

Device Plugin честно отработал свою эпоху: он научил Kubernetes видеть GPU и выдавать их подам. Но с бурным ростом AI-нагрузок GPU превратились в общий ресурс для десятков команд. Теперь недостаточно просто запустить задачу — нужны жёсткая изоляция, топология и высокая утилизация. Старая модель достигла предела. Чтобы обойти её ограничения, команды вынуждены создавать поверх Kubernetes «второй Kubernetes». Это дорого, нестабильно и плохо масштабируется.

Именно поэтому индустрия обращает внимание на DRA (Dynamic Resource Allocation) — встроенный в Kubernetes механизм, доступный по умолчанию с версии 1.35. DRA переносит сложную логику выделения и управления устройствами в ядро системы, заменяя конструктор из сторонних инструментов единым, предсказуемым API.

Разберём, почему старые подходы перестали справляться с современными AI-нагрузками, на какие проблемы уже наступили команды в продакшене и зачем изучать DRA, даже если пока не планируете переходить.

Как всё начинается: «дай мне один GPU, и поехали»

Типичный сценарий интеграции GPU в Kubernetes:

  1. Устанавливается NVIDIA Device Plugin — вручную или через GPU Operator.
  2. В манифесте пода указывается resources.limits: nvidia.com/gpu: 1.
  3. Pod размещается на GPU-узле, и контейнер получает доступ к устройству.

На этом этапе кажется, что интеграция завершена. И для многих сценариев этого действительно достаточно.

Но в реальной эксплуатации всё усложняется: появляется больше команд, разнородных нагрузок, требований к изоляции, стабильности и предсказуемости. Возникает вопрос: «Почему именно эта GPU, а не любая другая?».

Умножим на продакшен: коммунальный H100-кластер

Рассмотрим типичный AI-кластер, характерный для многих реальных инфраструктур:

  • 8 GPU-узлов;
  • по 8 NVIDIA H100 на каждом;
  • всего 64 GPU;
  • общие ресурсы для нескольких команд и типов нагрузок.

В таком кластере обычно сосуществуют три типа рабочих нагрузок.

1. Продовый инференс

Сервисы, обрабатывающие реальный трафик: vLLM, Triton, LLM-API, embedding/rerank, мультимодальные модели. Для крупных моделей типичный паттерн — одна реплика на один узел целиком: 8 GPU, tensor-parallel-size=8, предсказуемая латентность. vLLM, например, рекомендует использовать количество GPU в узле как размер tensor parallel.

Здесь инференс — не эксперимент, а сервис, который не терпит сюрпризов.

2. ML-обучение и эксперименты

Второй сегмент — обучение моделей. Здесь типичный запрос: «нужно 32 GPU на четырёх узлах, и все должны стартовать одновременно».

Без gang scheduling и специализированных batch-шедулеров такие задачи легко приводят к фрагментации: часть подов запускается, часть зависает в ожидании. Поэтому в экосистеме часто появляются инструменты вроде Volcano — нативный batch-шедулер для Kubernetes, ориентированный на AI/ML и HPC.

3. «Мелочь», которая съедает половину кластера

Третий слой — задачи, которые в отдельности кажутся незначительными, но в сумме создают серьёзную нагрузку на инфраструктуру:

  • короткие CUDA-тесты и CI-задачи;
  • профилировщики и утилиты;
  • небольшие инференс-сервисы (embeddings, STT/TTS), которым не нужен целый H100;
  • спорадические нагрузки: «нужно на 5 минут».

Если выделять под них целые GPU, утилизация падает. А это прямое влияние на бюджет, SLO и нервы инженеров. Вопрос перехода на DRA перестаёт быть теоретическим — он становится экономическим и операционным.

Device Plugin: контракт между kubelet и драйвером

Device Plugin — это фреймворк расширения kubelet, позволяющий подключить внешний процесс (обычно в виде DaemonSet) для:

  • обнаружения устройств;
  • сообщения kubelet о доступных устройствах;
  • обработки запросов на выделение устройств (Allocate).

Ключевой момент: Device Plugin работает на уровне kubelet, а не scheduler. Планировщик видит только абстракцию: «на узле есть nvidia.com/gpu: 8». Реальное распределение происходит уже на узле.

Работа по шагам:

  • PodSpec запрашивает nvidia.com/gpu: 1;
  • планировщик выбирает узел по целочисленному ресурсу;
  • kubelet вызывает Allocate у Device Plugin;
  • плагин возвращает параметры: какие устройства примонтировать, переменные окружения, девайсы;
  • runtime (containerd или CRI-O) запускает контейнер.

Где это живёт на узле

Kubelet использует директорию /var/lib/kubelet/device-plugins/. Туда Device Plugin помещает свой Unix-сокет и регистрируется через kubelet.sock.

Работа по gRPC:

  1. Device Plugin запускает gRPC-сервис и создаёт сокет в /var/lib/kubelet/device-plugins/;
  2. Регистрируется в kubelet через /var/lib/kubelet/device-plugins/kubelet.sock.

Какие gRPC-методы есть у Device Plugin

Интерфейс включает:

  • GetDevicePluginOptions() — возможности плагина;
  • ListAndWatch() — потоковое обновление списка устройств;
  • Allocate() — выделение устройств при создании контейнера;
  • опционально GetPreferredAllocation() и PreStartContainer().

Kubelet также предоставляет Registration-сервис с методом Register(). Важно: плагин должен начать обслуживать запросы до регистрации.

Что происходит при рестарте kubelet

При старте kubelet удаляет все сокеты в /var/lib/kubelet/device-plugins/. Device Plugin обязан пережить это и перерегистрироваться. Это ожидаемое поведение, описанное в документации.

На практике это объясняет частые проблемы: «обновили kubelet — и GPU пропали». Причина — не в мистике, а в жизненном цикле сокетов и совместимости компонентов.

Почему Device Plugin перестаёт справляться

Device Plugin не плох. Он эффективно решает задачу эксклюзивного выделения целых устройств.

Но с ростом AI-нагрузок ожидания изменились:

  • GPU больше не редкий ресурс для одного эксперимента;
  • они стали частью продуктовых платформ;
  • появилась потребность в разделении, изоляции, топологии и обслуживании без простоев.

Главная проблема: в Kubernetes с Device Plugin GPU остаётся extended resource — внешней сущностью. Платформа знает только количество, но не понимает внутреннюю структуру устройства.

Ограничения extended resources

Для nvidia.com/gpu действуют строгие правила:

  • если указан limits, он считается и за requests;
  • если указаны оба — они должны совпадать;
  • requests без limits запрещены.

Ресурс — целочисленный, неделимый и не может быть выделен нескольким контейнерам.

В коммунальном кластере это приводит к проблемам:

  • инференс забирает узлы целиком;
  • обучение требует пачки GPU одновременно;
  • мелкие задачи вынуждены занимать целые GPU, снижая утилизацию.

Квоты тоже работают поштучно. Это ограничивает гибкость управления ресурсами.

Когда нужен не целый GPU, а его часть, индустрия прибегает к костылям: time-slicing, MPS, MIG, vGPU, SR-IOV, кастомные шедулеры.

Популярные практики разделения GPU

Практика 1. GPU Operator: device plugin — это только верхушка

Установка Device Plugin — лишь часть инфраструктуры. Полноценный GPU-контур включает драйверы, container runtime, мониторинг, MIG-менеджер, NFD-лейблы.

GPU Operator объединяет эти компоненты, но имеет ограничения: требует интернета, ограниченная кастомизация. Некоторые команды предпочитают ручную сборку.

Device Plugin решает выделение GPU, но не покрывает жизненный цикл инфраструктуры на узле.

Практика 2. Time-slicing: видимость большего количества GPU

Реализуется через конфигурацию Device Plugin: он публикует увеличенное количество nvidia.com/gpu. Несколько подов делят один GPU по времени.

Проверка:

  • kubectl describe node <gpu-node> — смотрим Capacity/Allocatable;
  • запускаем несколько подов с nvidia.com/gpu: 1 — они стартуют на одном узле.

Грабли:

  • нет изоляции памяти и сбоев;
  • нет гарантии пропорциональной производительности;
  • метрики DCGM не разделяются по контейнерам;
  • изменения конфигурации не всегда подхватываются автоматически — может потребоваться перезапуск DaemonSet.

Практика 3. MIG: аппаратная нарезка с высокой стоимостью эксплуатации

MIG (Multi-Instance GPU) даёт аппаратную изоляцию. Но требует сложного управления:

  • конфигурация применяется на узле;
  • требуется остановка подов и иногда перезагрузка;
  • нужно синхронизировать с Kubernetes-ресурсами.

В GPU Operator за это отвечает MIG Manager — привилегированный DaemonSet, который:

  • следит за лейблом nvidia.com/mig.config;
  • останавливает поды и хостовые процессы;
  • применяет новую конфигурацию через mig-parted;
  • может требовать перезагрузки.

Проблемы:

  • логика управления — в shell-скрипте из ConfigMap;
  • в образе MIG Manager есть kubectl — нестандартно для контроллера;
  • обновление ConfigMap не всегда отражается в поде из-за кэширования файла.

Грабли:

  • переконфигурация требует остановки нагрузок;
  • баги драйверов (например, зависание задач при смешанном использовании MIG и целых GPU);
  • ошибки в топологии — высокая цена при динамическом управлении.

Практика 4. vGPU: два мира

4.1 NVIDIA vGPU — решение для виртуализации, ориентированное на виртуальные машины (например, KubeVirt). Требует лицензирования и не является Kubernetes-native.

4.2 vGPU для контейнеров — реализуется через Volcano + HAMi, где:

  • HAMi-core — программная виртуализация через hijacking CUDA API;
  • Dynamic MIG — динамическое создание MIG-инстансов.

Появляются:

  • отдельный планировщик (Volcano);
  • новые ресурсы (например, volcano.sh/vgpu-);
  • свой механизм распределения.

Грабли:

  • это отдельная экосистема, а не пара YAML;
  • настройка через конфиг-файл не работает — device plugin читает только аргументы командной строки;
  • конфликты плагинов на узле — рекомендуется один GPU device plugin.

Практика 5. Топология: не любой GPU подойдёт

Теперь важно не только количество, но и качество GPU:

  • NVLink между картами;
  • одна NUMA-область;
  • конкретный SKU;
  • связь с NIC для RDMA;
  • избежание конфликтов по PCIe.

Device Plugin может передавать topology hints (например, NUMA-аффинити), и Kubernetes поддерживает это через Topology Manager. Но это не решает проблему полностью.

На практике команды используют:

  • лейблы и taints;
  • nodeAffinity;
  • отдельные пулы узлов;
  • кастомные шедулеры;
  • ручные проверки NVLink.

Это второй аргумент в пользу DRA: хочется описывать требования через атрибуты устройства, а не угадывать подходящие узлы.

Эксплуатация: GPU внезапно исчезают

Операционные проблемы — частая боль. Device Plugin должен переживать рестарты kubelet, перерегистрироваться, следить за сокетами. Но обновления идут непрерывно:

  • kubelet;
  • containerd/CRI-O;
  • ядро;
  • драйверы NVIDIA;
  • container toolkit;
  • операторы;
  • политики безопасности.

Несовместимости, баги или неправильный порядок обновления могут привести к исчезновению GPU.

Требования к платформе растут:

  • устройства должны быть проверены до начала работы;
  • выделение — детерминированное;
  • состояние драйверов и железа — полностью наблюдаемо;
  • должен быть понятный механизм вывода узлов в обслуживание;
  • обновления не должны быть лотереей.

Всё это выходит за рамки изначальной концепции Device Plugin.

Итоги: где Device Plugin хорош, а где начинаются разDRAжения

Device Plugin отлично справляется с:

  • эксклюзивной выдачей целых устройств;
  • вендор-специфичной подготовкой устройств в Allocate;
  • сигнализацией о состоянии устройств и их видимостью для kubelet.

Device Plugin упирается в потолок, когда нужны:

  • гибкое и предсказуемое разделение устройств;
  • описание запросов через атрибуты (SKU, память, топология, fabric и т.д.);
  • управление жизненным циклом устройства как полноценного объекта платформы;
  • унифицированная модель для GPU, сетевых адаптеров, FPGA и разных вендоров.

Ощущение, что Device Plugin перестал быть достаточным, возникает не из-за его неисправности, а потому что индустрия вышла за рамки его изначального назначения.

Именно поэтому появляется DRA — следующий шаг в эволюции управления устройствами в Kubernetes.

Читать оригинал