Настроить GPU scheduling для multi-tenant

ТЕХНИЧЕСКОЕ ЗАДАНИЕ: Настроить GPU scheduling для multi-tenant

1. Цель задачи

Научиться настраивать справедливое распределение GPU-ресурсов между несколькими командами (тенантами) в Kubernetes-кластере, обеспечивая изоляцию и предсказуемую производительность. Требуется реализовать либо аппаратный (NVIDIA MIG), либо программный (Kueue с fair-share) механизм, а затем проверить, что одна команда не может «отобрать» ресурсы у другой.

Ключевой результат Воспроизводимый пайплайн, гарантирующий каждой команде минимальную долю GPU и предотвращающий перегрузки (noisy neighbour) при совместном использовании кластера.


2. Исходные данные

Что нужноОткуда взять
Kubernetes-кластер (minikube / kind / bare-metal)Установить самостоятельно или использовать предоставленный dev-кластер
Node с NVIDIA GPU (минимум 1 GPU, желательно A100, V100 или A10)Облачный инстанс (AWS p3.2xlarge, GCP a2-highgpu-1g) или локальный сервер
Установленный NVIDIA GPU OperatorHelm-чарт из репозитория NVIDIA
Kueue (или Volcano) для программного планированияУстановка через Helm или YAML-манифесты
Инструмент для генерации тестовой нагрузкиPython-скрипт с использованием torch или cuda-samples
Метрики использования GPUnvidia-smi, Prometheus + GPU-экспортёр, Grafana

Если нет реального инструмента — симулируем:

  1. GPU нет – используйте эмулятор GPU (например, nvidia-gpu-device-plugin в режиме --mode=fake).
    • Установите NVIDIA GPU Operator с параметром devicePlugin.fake= true.
    • Убедитесь, что NodeResources считает, что GPU доступны.
  2. MIG не поддерживается (например, на Tesla T4) – сосредоточьтесь только на Kueue.
  3. Kueue не установлен – следуйте официальной документации:
    helm repo add kueue https://kubernetes-sigs.github.io/kueue
    helm install kueue kueue/kueue
    
  4. Python / CUDA не доступны – используйте nvidia-smi stress-тесты (например, nvidia-smi -i 0 -l 1 -pm ENABLED и cuda_memtest).

3. Технологический стек

КомпонентИнструментыНазначение
КонтейнеризацияDocker, Kubernetes (kind/minikube)Оркестрация подов
GPU-управлениеNVIDIA GPU Operator, NVIDIA Container ToolkitПоддержка GPU в контейнерах
Аппаратная изоляцияNVIDIA MIG (Multi-Instance GPU)Разделение одного GPU на изолированные инстансы
Программный schedulingKueue (v0.6+) + batch/v1 JobОчереди, fair-share, квоты по командам
Метрики и мониторингPrometheus + GPU-метрики, GrafanaСбор и отображение использования GPU
Тестовая нагрузкаPython + PyTorch (или cuda_memtest), sleeper-подыЭмуляция задач двух команд

4. Этапы выполнения

Этап 1: Подготовка окружения (оценка 60 мин)

Действия

  1. Разверните Kubernetes кластер (если ещё нет).

    kind create cluster --name gpu-cluster --config kind-config.yaml
    

    В kind-config.yaml обязательно укажите extraMounts для NVIDIA драйверов, если используете реальный GPU.

  2. Установите NVIDIA GPU Operator.

    helm repo add nvidia https://nvidia.github.io/gpu-operator
    helm install gpu-operator nvidia/gpu-operator –set migManager.enabled=true
    
  3. Проверьте, что GPU виден в кластере:

    kubectl get nodes -o json | jq '.items[].status.capacity."nvidia.com/gpu"'
    

    Ожидаемое значение: количество физических GPU (≥1).

  4. Установите Kueue (если выбран программный путь):

    helm repo add kueue https://kubernetes-sigs.github.io/kueue
    helm install kueue kueue/kueue --create-namespace -n kueue-system
    
  5. Установите Prometheus и GPU exporter:

    helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
    helm install prometheus prometheus-community/kube-prometheus-stack -n monitoring
    kubectl apply -f https://raw.githubusercontent.com/NVIDIA/gpu-operator/master/deploy/gpu-operator/charts/gpu-operator/templates/prometheusrule.yaml
    

Ожидаемый результат этапа

  • GPU ресурсы доступны в нодах (kubectl describe node показывает nvidia.com/gpu).
  • Prometheus собирает GPU-метрики (DCGM).
  • Kueue установлен и Ready.

Этап 2: Настройка изоляции GPU (MIG) (оценка 45 мин) – альтернативный путь: Kueue

Выберите один путь. Ниже приведён MIG, для Kueue смотрите Этап 2-бис.

Действия (MIG):

  1. Проверьте поддержку MIG на GPU:

    nvidia-smi mig –gi –gi-profile
    

    (должны появиться профили, например 3g.40gb для A100)

  2. Создайте MIG-профили через ConfigMap. Пример для одного GPU с двумя инстансами по 20 ГБ:

    apiVersion: v1
    kind: ConfigMap
    metadata:
      name: mig-config
      namespace: nvidia-gpu-operator
    data:
      config: |
        - devices: all
          mig-devices:
            0:
               gi-profile: 2g.20gb
            1:
               gi-profile: 2g.20gb
    
  3. Примените ConfigMap и дождитесь перезапуска MIG Manager:

    kubectl apply -f mig-config.yaml
    kubectl rollout status -n nvidia-gpu-operator deployment/mig-manager
    
  4. Проверьте MIG устройства:

    nvidia-smi mig -li
    

    Должно отобразиться два MIG-устройства (например, MIG 3g.40gb/0, MIG 3g.40gb/1).

  5. Назначьте каждой команде (tenant) по одному MIG-ресурсу через ResourceQuota или LimitRange.

    • Создайте два namespace: team-a, team-b.
    • В каждом namespace укажите requests.nvidia.com/mig-2g.20gb: 1.

Ожидаемый результат этапа

  • Каждый под может запрашивать только целый MIG-срез.
  • Одна команда не может использовать GPU другого MIG-среза.

Этап 2-бис: Настройка Kueue с fair-share (если MIG не используется) (оценка 60 мин)

Действия

  1. Создайте ResourceFlavor, описывающий доступные GPU:

    apiVersion: kueue.x-k8s.io/v1beta1
    kind: ResourceFlavor
    metadata:
      name: gpu-flavor
    spec:
      nodeLabels:
        nvidia.com/gpu.product: Tesla-A100
    
  2. Создайте ClusterQueue с fair-share и минимальными квотами для двух команд:

    apiVersion: kueue.x-k8s.io/v1beta1
    kind: ClusterQueue
    metadata:
      name: cluster-queue
    spec:
      namespaceSelector: {}
      resourceGroups:
        - coveredResources: ["nvidia.com/gpu"]
          flavors:
            - name: gpu-flavor
              resources:
                - name: nvidia.com/gpu
                  minCount: 2    # минимум 2 GPU одной команде
    

    (настройка fair-share задаётся через spec.cohort или spec.fairSharing.weight – смотри документацию)

  3. Создайте LocalQueue в каждом namespace:

    apiVersion: kueue.x-k8s.io/v1beta1
    kind: LocalQueue
    metadata:
      namespace: team-a
      name: team-a-queue
    spec:
      clusterQueue: cluster-queue
    
  4. Настройте quotas (ResourceQuota) для команд, чтобы ограничить максимальное количество GPU.

Ожидаемый результат этапа

  • Поды команды A и B используют общий пул GPU, но с приоритетами и fair-share.
  • При перегрузке одной команды её поды ждут, а другой получают гарантированную долю.

Этап 3: Генерация нагрузки и тестирование изоляции (оценка 30 мин)

Действия

  1. Напишите Python-скрипт gpu_load.py, который занимает 80% памяти GPU на 60 секунд:

    import torch, time, os
    tenant = os.environ.get("TENANT", "unknown")
    x = torch.randn(8000, 8000).cuda()
    time.sleep(60)
    
  2. Запустите по 3 таких пода в каждом namespace (team-a, team-b).

    kubectl run --namespace=team-a load-1 --image=pytorch/pytorch:latest -- python gpu_load.py
    
  3. Наблюдайте в реальном времени использование GPU через nvidia-smi dmon -d 1 или Grafana.

  4. Проверьте изоляцию:

    • Для MIG: каждый под привязан к своему MIG-устройству; загрузка не влияет на другое.
    • Для Kueue: при превышении суммарных ресурсов выше minCount, поды одной команды должны ждать; проведите A/B тест, запуская сначала много подов одной команды, затем другой.

Ожидаемый результат этапа

  • Выявлена разница в поведении: для MIG – полная изоляция, для Kueue – fair-share с ожиданием.

Этап 4: Мониторинг и валидация (оценка 30 мин)

Действия

  1. Создайте дашборд Grafana для GPU-метрик с панелями:

    • Использование GPU memory per pod (kube_pod_annotations + DCGM).
    • GPU utilization per MIG slice или per job.
    • События вытеснения/backlog в Kueue (metrics kueue_admitted_workloads).
  2. Настройте оповещение при превышении доли (например, если команда использует >90% своего minCount более 5 минут).

  3. Задокументируйте конфигурацию в Git-репозитории (Helm-чарт или набор манифестов).

Ожидаемый результат этапа

  • Рабочий дашборд, отображающий изоляцию.
  • Залитая конфигурация в Git.

5. Критерии приемки (Definition of Done)

  • Кластер Kubernetes готов, GPU доступен и виден через kubectl describe node.
  • MIG-профили созданы (или Kueue настроен с fair-share).
  • В каждом namespace (team-a, team-b) созданы LocalQueue и ResourceQuota (для Kueue).
  • Запущена тестовая нагрузка от двух команд; при этом одна команда не может деградировать производительность другой более чем на 20% (для MIG – 0%).
  • В Grafana или nvidia-smi видно, что каждый под привязан к своему GPU/MIG-ресурсу.
  • Написана документация (README.md) с командами развёртывания и описанием архитектуры.
  • Пройдён сценарий «перегрузка одной команды» — поды другой команды получают минимальную гарантию.
  • Все манифесты хранятся в Git и воспроизводимы kubectl apply -k.
  • Оповещения (alerts) настроены и проверены.

6. Ожидаемый результат

Основной артефакт

  • Git-репозиторий со всеми манифестами:
    mig-config.yaml или kueue-clusterqueue.yaml, localqueue.yaml, namespace YAML, тестовый Job, описание дашборда Grafana.

Содержание

  • Полная инструкция по развёртыванию (README).
  • Скрипты нагрузки.
  • Скриншоты дашборда или вывод nvidia-smi, подтверждающие изоляцию.

Опционально

  • Python-скрипт для автоматического тестирования fair-share (проверка, что время ожидания подов одной команды при дозагрузке GPU соответствует настройкам).
  • Видео/скринкаст прогона тестов.

7. Возможные сложности и их решение

СложностьРешение
MIG не поддерживается GPU (например, Tesla T4, RTX 3090)Переключиться исключительно на Kueue без MIG. Для изоляции достаточно программного fair-share.
GPU Operator не видит GPU из-за драйверовПроверить версию драйвера (>=450.80.02), установить драйвер через NVIDIA Container Toolkit. Для kind – включить --feature-gates="ExperimentalGPUNode=true".
Kueue не назначает поды (Pending)Проверить, что ResourceFlavor совпадает с node labels; что ClusterQueue имеет minCount <= доступных GPU; что поды содержат метку kueue.x-k8s.io/queue-name.
Метрики в Prometheus не отображаютсяУбедиться, что DCGM-экспортёр запущен (kubectl get pods -n nvidia-gpu-operator -l app=gpu-operator). Добавить ServiceMonitor для него.
Под одной команды вытесняется Kueue при высокой нагрузке другойНастроить preemptionPolicy в ClusterQueue: PreemptWithinCohort.
Разные команды пишут в одно MIG-устройствоИсправить ResourceQuota: на каждый namespace должен быть выделен уникальный mig-id через nodeSelector или limitranges.
Нет реального GPU для тестаИспользовать фейковый device plugin (см. Этап 1), снизить ожидания – изоляция будет проверена на уровне k8s, без реальных вычислений.

8. Бюджет времени (оценка)

ЭтапВремя (минут)
1. Подготовка окружения60
2. Настройка MIG / Kueue60
3. Генерация нагрузки и тестирование30
4. Мониторинг и валидация30
Итого (без учёта изучения)180 (3 часа)
Примечание: для первого раза заложите дополнительно 1,5–2 часа на решение неожиданных проблем.

9. Связанные вопросы из базы знаний

ВопросТема
42Настройка NVIDIA GPU Operator в Kubernetes
105Управление ресурсами GPU через Device Plugin
203Multi-Instance GPU (MIG) – концепции и конфигурация
312Kueue: установка и базовые объекты (ClusterQueue, LocalQueue)
456Fair-share scheduling в batch-системах
578Мониторинг GPU с Prometheus и DCGM
612Изоляция workload в multi-tenant кластере
723Политики вытеснения в Kueue (preemption)
845Helm-чарты для GPU-оператора и Kueue
891A/B-тестирование GPU-нагрузок в изолированных namespace

10. Чек-лист самопроверки

  • Я проверил, что GPU виден в кластере до начала настройки.
  • Я выбрал один из двух путей (MIG или Kueue) и строго ему следовал; второй указан как альтернатива.
  • Я создал как минимум два namespace для двух команд.
  • Я выполнил тест с одновременной нагрузкой от обеих команд и убедился, что изоляция работает.
  • Я задокументировал все шаги и конфигурацию в Git, чтобы их можно было воспроизвести.
  • Я настроил хотя бы одно оповещение на перегрузку GPU (DCGM).
  • Я проверил, что поды, превышающие квоту, остаются в состоянии Pending, а не вытесняются без предупреждения (для Kueue).
  • Я удостоверился, что в случае MIG каждое MIG-устройство закреплено за конкретным namespace.