
В Ситидрайве Kubernetes обновляют регулярно — инфраструктура большая, и актуальность версий критически важна. После апгрейда до версии 1.29.15 один из GPU-узлов внезапно «забыл» о своей видеокарте, и нам пришлось срочно искать решение. В этой статье я расскажу, в чём была причина бага и как Time-Slicing помог повысить утилизацию GPU. Статья будет полезна всем, кто работает с GPU в Kubernetes и хочет избежать подобных сюрпризов в продакшене.
Что находится под капотом GPU-кластера в Ситидрайве
Часть нашей инфраструктуры работает на GPU — в основном для задач ML-инженеров, которые обучают и тестируют AI-модели. Отдельный кластер с видеокартами помогает командам запускать обучение и inference без простоев, а backend-сервисы используют те же GPU для расчётов и бизнес-логики, где важна скорость.
Мы используем Tesla T4, так как из всех моделей, доступных у нашего облачного провайдера, она оказалась золотой серединой: достаточно производительная, энергоэффективная и не требует жертвоприношений при настройке CUDA-драйверов.
Процесс добавления нового GPU-узла выглядит просто, если смотреть со стороны — и не очень просто, если быть тем, кто его автоматизирует:
получаем сервер,
ставим всё необходимое для работы с видеокартой,
запускаем тестовые контейнеры для бенчмарков,
и позволяем Ansible сделать магию, конфигурируя всё под ключ.
Чтобы kubelet понимал, что рядом живёт видеокарта, в кластере работает DaemonSet nvidia-device-plugin — по сути, это прослойка между Kubernetes и самим GPU. Она сообщает kubelet, какие ресурсы доступны, и регистрирует их как nvidia.com/gpu.
После подготовки узла мы добавляем его в кластер с лейблом — чтобы сервисы, которым нужны вычислительные мощности, знали, куда идти. В Helm values указываем nodeSelector, после чего узел дружит с видеокартой, kubelet — с узлом, сервис — с GPU. Так мы жили долго и счастливо… до Kubernetes 1.29.15 ?
Но что-то пошло не по плану
В рамках задачи по обновлению кластера с версии 1.28.15 на 1.29.15 случился интересный кейс. Перед каждым апгрейдом мы в Ситидрайве традиционно проходим ритуал DevOps-инженера: читаем release notes, сверяем матрицы совместимости, составляем пошаговый план, и только потом трогаем kubeadm upgrade. Всё по документации, без самодеятельности — как завещал официальный гайд.
Но стоило обновить один из GPU-узлов — и Kubernetes внезапно «забыл», что у него вообще есть видеокарта.

Вот что было:
kubectl describe no gpu-node.tech
Capacity:
cpu: 8
ephemeral-storage: 103126288Ki
hugepages-1Gi: 0
hugepages-2Mi: 0
memory: 32870700Ki
nvidia.com/gpu: 0
pods: 110
А вот, что должно было быть:
Capacity:
cpu: 8
ephemeral-storage: 103123028Ki
hugepages-1Gi: 0
hugepages-2Mi: 0
memory: 32870700Ki
nvidia.com/gpu: 1
pods: 110
И сервер уверенно показывал обратное:
# lspci | grep -i nvidia
00:06.0 3D controller: NVIDIA Corporation TU104GL [Tesla T4] (rev a1)
# lsmod | grep nvidia
nvidia_uvm 4476928 2
nvidia_drm 81920 0
nvidia_modeset 1339392 1 nvidia_drm
nvidia 53993472 10 nvidia_uvm,nvidia_modeset
drm_kms_helper 184320 2 virtio_gpu,nvidia_drm
drm 495616 6
drm_kms_helper,nvidia,virtio_gpu,nvidia_drm,ttm
Смотрю, что с подами nvdp-nvidia-device-plugin, а там такая картина:
NAME READY STATUS RESTARTS AGE IP NODE
nvdp-nvidia-device-plugin-5srhl 0/1 CreateContainerError 0 24s 29.64.143.233 gpu-node.tech

Событие у пода выглядело не менее ободряюще:
Error: container create failed: error executing hook /usr/bin/nvidia-container-toolkit
Результат — GPU-ресурс пропал из кластера, и все сервисы, завязанные на видеокарте, остались без вычислительных мощностей.

Начинаю расследование
Версий было несколько: новый Kubernetes, новая версия CRI-O, старый nvidia-device-plugin, или что-то совсем экзотическое из глубин зависимостей. Ведь с каждым минорным апдейтом Kubernetes «под капотом» подтягивается целый ворох изменений, и угадай потом, что именно пошло не так. Нашёл такие интересные материалы по теме:
Не буду здесь подробно разбирать каждое — в ссылках достаточно информации для тех, кто столкнётся с похожей историей. Оставлю их скорее как карту клада для инженера, который будет искать «почему GPU не видно».
Мне помог вот этот комментарий в обсуждении Podman:
?https://github.com/containers/podman/discussions/16101#discussioncomment-6977446
Попробовал повторить предложенное решение — и о чудо: под nvdp-nvidia-device-plugin начал «подниматься». Правда, ненадолго. Через пару секунд он уходил в CrashLoopBackOff, потому что hook oci-nvidia-hook.json не отрабатывал.
В логах красовалось следующее:
I0917 14:25:25.703510 1 main.go:256] Retreiving plugins.
W0917 14:25:25.703841 1 factory.go:31] No valid resources detected, creating a null CDI handler
I0917 14:25:25.703899 1 factory.go:107] Detected non-NVML platform: could not load NVML library: libnvidia-ml.so.1: cannot open shared object file: No such file or directory
I0917 14:25:25.703935 1 factory.go:107] Detected non-Tegra platform: /sys/devices/soc0/family file not found
E0917 14:25:25.703948 1 factory.go:115] Incompatible platform detected
E0917 14:25:25.703952 1 factory.go:116] If this is a GPU node, did you configure the NVIDIA Container Toolkit?
То есть плагин внутри контейнера попросту не видел драйвер и/или CDI-спеку. GPU физически была, но с точки зрения Kubernetes — её не существовало.

План Б: GPU Operator
Продолжил копать. На GitHub наткнулся на issue, где в самом конце кто-то написал: «Перешёл на NVIDIA GPU Operator — и всё заработало».
Чтобы убедиться, что это не единичный случай, я пошёл искать подтверждения и полез в русскоязычный телеграм-чат Kubernetes RU. Нашёл сразу пару похожих кейсов (в одном даже версия Kubernetes совпадала — 1.29). Коллеги советовали: «Поставь NVIDIA GPU Operator и не грей голову». — Георг Гаал, источник
Так GPU Operator из запасного плана Б стал вполне серьёзным кандидатом.
Тем временем я обратил внимание на строчки из логов про CDI. Оказалось, что это новый механизм, появившийся в Kubernetes 1.26, как раз заточен под работу с устройствами вроде GPU:
https://kubernetes.io/docs/concepts/extend-kubernetes/compute-storage-net/device-plugins/
И вот тут всё сошлось. Решил попробовать GPU Operator — и... всё действительно заработало! Буквально одной командой оператор делает все для работы с видеокартами:
helm install --wait --generate-name -n gpu-operator --create-namespace nvidia/gpu-operator --version=v25.3.3
В процессе очень помогла статья на Хабре от Selectel — натыкался на неё несколько раз в разных обсуждениях, и теперь понял, почему.
После того, как все компоненты GPU-оператора заработали, для kubelet вновь стал доступен ресурс GPU:
Capacity:
cpu: 8
ephemeral-storage: 103123028Ki
hugepages-1Gi: 0
hugepages-2Mi: 0
memory: 32870700Ki
nvidia.com/gpu: 1
pods: 110
Для активации CDI достаточно прописать в helm values:
cdi:
enabled: true
default: true
Снимаем головную боль DevOps’а
Погрузившись в документацию GPU Operator, я понял, насколько далеко ушёл Kubernetes в плане работы с GPU:
NVIDIA Device Plugin — основная задача этого плагина — регистрировать GPU как ресурсы в kubernetes. Никаких драйверов, библиотек CUDA или мониторинга он не ставит. Это необходимо делать самостоятельно, как мы и делали раньше через ansible. Вариант подходит, например, если на узлах кластера уже стоят драйверы NVIDIA и CUDA.
NVIDIA GPU Operator — это более комплексное решение. Он управляет установкой всем GPU-стеком в кластере: разворачивает CUDA Toolkit, разворачивает сам Device Plugin, добавляет мониторинг GPU (DCGM, Exporter), может устанавливать NVIDIA Container Toolkit, CDI, следит за состоянием всех компонентов. Штука мощная. И действительно разгружает администратора кластера по многим пунктам.
Благодаря операторам не нужно добавлять лейбл на узел вручную. Также теперь в values сервиса можно оперировать GPU как стандартными ресурсами:
resources:
limits:
cpu: 300m
memory: 3Gi
nvidia.com/gpu: "1"
Делим GPU между командами: как мы пришли к Time-Slicing
Сам по себе ресурс видеокарты «из коробки» не позволяет использовать какую-то её часть. Как например с CPU и Memory, где мы можем указать 0.1 CPU или 50 Mi. В видеокартах вы указываете целое значение: одна видеокарта, две, три и так далее. Бывают приложения, которым не нужны мощности целой видеокарты. Поэтому инженеры придумали разные способы «порезать» GPU между задачами. Хороший обзор этой темы — в разборе от Selectel.
Как я упоминал ранее, в кластерах Ситидрайва используются Tesla T4. Эти карты не поддерживают технологию MIG (Multi-Instance GPU), которая позволяет делить GPU на аппаратные «слайсы» — как это делают A100 или H100.
Зато у T4 есть поддержка Time-Slicing — мы пошли этим путём. Он реализует «виртуальное разделение» GPU по времени: приложения используют одну и ту же видеокарту, но по очереди, а оператор управляет планированием и балансом нагрузки.
Time-Slicing достаточно просто настраивается: создаём ConfigMap с нужным количеством квот (например мы установили на одном из кластеров 4, а на другом 5), патчим ресурс clusterpolicies.nvidia.com/cluster-policy и всё. Если потребуется что-то изменить, просто обновляем ConfigMap и перезапускаем daemonset/nvidia-device-plugin-daemonset. Оператор самостоятельно всё настраивает и оркестирует.
Видим такой результат:
Capacity:
cpu: 16
ephemeral-storage: 103112428Ki
hugepages-1Gi: 0
hugepages-2Mi: 0
memory: 131910100Ki
nvidia.com/gpu: 4
pods: 110
(До этого у параметра nvidia.com/gpu было значение 1).
Но перед тем как выбрать способ шеринга, стоит внимательно изучить детали. У MIG и Time-Slicing свои плюсы и минусы. Например, у Time-Slicing могут быть:
долгие context switch между задачами,
общая память у приложений, что может привести к Out Of Memory, если одно приложение решит «съесть» чуть больше, чем положено.
Есть и альтернативные решения — например, проект HAMi, который динамически выделяет независимые GPU-квоты и обещает более гибкое управление.

Результат одного обновления
Time-Slicing позволил нам повысить утилизацию GPU и масштабировать задачи ML-команд без закупки дополнительных видеокарт. Теперь несколько инженеров (а иногда и целые команды) могут спокойно работать параллельно на одном узле.
Вывод простой: если вы только начинаете строить GPU-инфраструктуру в Kubernetes — ставьте GPU Operator сразу. Он избавит вас от десятков мелких настроек и головной боли, а заодно даст мощную основу для гибкого «шеринга» GPU.