Привет! Я Дмитрий, инженер и руководитель направления MLOps в Совкомбанке. Специализируюсь на разработке и эксплуатации ML-платформ на базе Kubernetes и GPU.
С 2010 года в ИТ: строю инфраструктуру для машинного обучения, внедряю Kubeflow и GPU-оператор, настраиваю MIG на H100 в корпоративных средах с повышенными требованиями к безопасности и надежности. В последние годы фокусируюсь на оптимизации ML-пайплайнов, повышении утилизации GPU (включая MIG-профили) и интеграции MLOps-практик в процессы продуктовых команд.
В 2022 году в некоторых командах разработки «Совкомбанка» уже существовали проекты с применением искусственного интеллекта. Это были отдельные компоненты, которые хорошо справлялись с конкретными задачами приложения. Но не хватало единой платформы управления.
По мере роста количества и сложности бизнес-задач возникла необходимость в создании ML-платформы как сервиса с едиными стандартами авторизации, размещения моделей на платформе для устранения проблем контроля, унификации и безопасности ML-моделей.
Единая ML-платформа с гибким RBAC позволила бы:
Защитить модели и данные — с контролем доступа на уровне команд и проектов
Отслежи��ать, кто и когда использует модель — для аудита и безопасности
Дать бизнесу самостоятельность — без ожидания инженеров, можно быстро тестировать гипотезы
Мы изучили доступные инструменты, попытались объединить их в одном Kubernetes-кластере, столкнулись с рядом ограничений — и в итоге пришли к архитектуре на базе Kubeflow и GPU-оператора.
В статье рассказываем, какие сложности были в ходе проекта, как выстроили работу с Kubeflow, настраивали H100 с MIG-разделением и что важно учесть, если вы планируете строить ML-платформу на bare-metal-GPU в корпоративной среде.
Что внутри
На рынке в то время были доступны две ML-платформы: проприетарная Caila от Just AI и open-source-решение Kubeflow.
Оба варианта предъявляли схожие требования к инфраструктуре:
кластер Kubernetes последних версий
bare-metal-серверы с поддержкой GPU
административные привилегии в кластере
Для управления кластерами Kubernetes в банке используется платформа «Штурвал», обладающая модульной и расширяемой архитектурой. Большая часть необходимых модулей работала в платформе «из коробки» — и то, что нам потребовалось доработать, никак не повлияло на поддержку решения. Для вычислительных ресурсов выбрали доступные на тот момент bare-metal-серверы с GPU H100.
Сначала мы развернули коммунальный Kubernetes-кластер для обеих ML-платформ. Разделение микросервисов планировалось осуществлять по стандартной схеме с использованием labels и taints для изоляции компонентов разных платформ.
Однако этот подход привел к ряду критических проблем:
Конфликты компонентов. Несмотря на использование labels и taints, компоненты Caila и Kubeflow конфликтовали при работе в одном кластере. ML-платформы конкурировали за ресурсы одних и тех же серверов. Распределить их компоненты между разными серверами не удалось, так как у производителей нет такой возможности. Любое обновление компонента платформы приводило бы к изменению конфигурации и откату назад. Из-за этого было сложно применить единую схему маркировки ко всем Kubernetes YAML-манифестам, которые генерировали платформы.
Угрозы информационной безопасности. Обе платформы имели административные привилегии в кластере и, следовательно, получали доступ ко всем компонентам друг друга. Это создавало потенциальные риски для безопасности и изоляции данных.
В результате анализа этих проблем мы решили разделить проекты на два независимых кластера.
Создание и настройку кластера Kubernetes в данной статье опустим, в «Штурвале» это делается просто и быстро. Сфокусируемся на самом интересном — установке Kubeflow и настройке видеокарт.
Установка Kubeflow в кластере
Необходимые компоненты:
Три виртуальных мастера на RedOS с ядром 6.12.21
Три железных рабочих ноды на RedOS с ядром 6.12.21 и видеокартами H100
Собранный кластер на «Штурвале»
Ниже описываем, как пошагово установить и настроить драйверы в любом используемом дистрибутиве Kubernetes.
Закомментируем в /etc/dnf/dnf.conf
exclude=kernel* *kernelСтавим пакет для ядра.
yum install kernel-lt-devel-6.12.21.red80.x86_64Проверяем, что в параметрах grub установлены эти значения:
module.sig_enforce=0rd.driver.blacklist=nouveauДобавляем драйвер nouveau в blacklist-модулей.
Создаем файл
/etc/modprobe.d/blacklist-nouveau.conf:Скрытый текст
blacklist nouveau blacklist lbm-nouveau options nouveau modeset=0 alias nouveau off alias lbm-nouveau offПосле перезагрузки вывод следующей команды должен быть пустым:
lsmod | grep nouveauЕсли это не так, выполняем следующую команду и перезагружаемся:
dracut –forceОтключаем /etc/dnf/plugins/subscription-manager.conf
Далее выставляем флаг
disable_system_repos=0Включаем прокси.
export https_proxy=http://proxy.company.io:3128/ export http_proxy=http://proxy.company.io:3128/Подключаем два репозитория:
Создаем в
/etc/yum.repos.d/cuda-rhel8.repo:Скрытый текст
[cuda-rhel8-x86_64] name=cuda-rhel8-x86_64 baseurl=https://developer.download.nvidia.com/compute/cuda/repos/rhel8/x86_64 enabled=1 gpgcheck=1 gpgkey=https://developer.download.nvidia.com/compute/cuda/repos/rhel8/x86_64/D42D0685.pubnvidia-container-toolkit.repo:Скрытый текст
[nvidia-container-toolkit] name=nvidia-container-toolkit baseurl=https://nvidia.github.io/libnvidia-container/stable/rpm/$basearch repo_gpgcheck=1 gpgcheck=0 enabled=1 gpgkey=https://nvidia.github.io/libnvidia-container/gpgkey sslverify=1 sslcacert=/etc/pki/tls/certs/ca-bundle.crtОбязательно включаем репозитории: enabled=0 меняем на enabled=1
Устанавливаем драйверы.
dnf install dkms dnf -y module install nvidia-driver:latest-dkmsПроверяем наличие драйвера
modprobe -vv nvidia lsmod | grep nvidiaили
nvidia-smiВключаем службу.
systemctl enable --now nvidia-persistenced.serviceСтавим дополнительные пакеты.
yum install nvidia-docker2.noarch yum install nvidia-fabric-manager-580.95.05.x86_64Перезагружаемся и проверяем, что все работает корректно.
reboot nvidia-smi
Подводные камни
Во время установки пользователи могут столкнуться с рядом ошибок, которые мы решали совместно с инженерами «Штурвала». Ниже разберем самые распространенные и возможные пути их решения.
После установки драйвера, модуль ядра не подгружается
При попытке выполнить
modprobe nvidia, система выдает вот такую ошибку:Required key not availableРешение:
Скрытый текст
grubby --update-kernel=ALL --args="module.sig_enforce=0" grubby --update-kernel=ALL --remove-args="lockdown" grub2-mkconfig -o /boot/grub2/grub.cfg rebootDKMS не видит драйверы
При наборе команды:
dkms status
вместоinstalled:nvidia/580.95.05: addedСобираем и устанавливаем драйвер с помощью dkms:
sudo dkms install -m nvidia -v 580.95.05Если система выдает подобную ошибку:
Failed command: 'make' -j128 KERNEL_UNAME=6.12.21.red80.x86_64 modules Error! Bad return status for module build on kernel: 6.12.21.red80.x86_64 (x86_64)Смотрим логи:
cat /var/lib/dkms/nvidia/580.95.05/build/make.logВидим ошибку прав доступа у системного линкера:
/bin/sh: line 1: /bin/ld: Permission deniedДалее смотрим права линкера:
ls -l /usr/bin/ldУдаляем файл и создаем символическую ссылку:
sudo rm -f /usr/bin/ld sudo ln -s /usr/bin/ld.bfd /usr/bin/ldПовторно собираем и устанавливаем драйвер:
sudo dkms install -m nvidia -v 580.95.05 dkms statusЕсли под nvidia-operator-validator не стартует с версией драйверов 580.95.05 из официального репозитория nvidia, то читаем логи пода.
Если видим подобную ошибку:
Скрытый текст
Error: failed to create containerd task: failed to create shim task: OCI runtime create failed: runc create failed: unable to start container process: error during container init: error running prestart hook #0: exit status 1, stdout: , stderr: Auto-detected mode as 'legacy' nvidia-container-cli: mount error: failed to add device rules: unable to generate new device filter program from existing programs: unable to create new device filters program: load program: invalid argument: last insn is not an exit or jmp processed 0 insns (limit 1000000) max_states_per_insn 0 total_states 0 peak_states 0 mark_read 0: unknownТо решаем ее путем изменения параметров ядра:
с net.core.bpf_jit_harden=2наnet.core.bpf_jit_harden=1Настраиваем nvidia container toolkit:
Скрытый текст
dnf install -y nvidia-docker2 dnf install -y nvidia-container-toolkit nvidia-ctk runtime configure --runtime=containerd --set-as-default systemctl restart containerdНастраиваем конфиг containerd в
/etc/containerd/config.tomlВ секции
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes]меняем значения для runtime в подсекциях:Скрытый текст
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.nvidia] [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.nvidia.options] BinaryName = "/usr/bin/nvidia-container-runtime"В подсекции
[plugins."io.containerd.runtime.v1.linux"]меняем значение наruntime = "nvidia"
Разворачиваем gpu-operator в кластере
Перед деплоем gpu-operator необходимо добавить namespace (gpu-operator) в исключение kyverno
Далее качаем helm chart и добавляем в проект кластера. Описание helm values доступно здесь.
Отключаем лишнее в values (драйвер ставим самостоятельно):
Скрытый текст
driver: enabled: false vgpuDeviceManager: enabled: false vfioManager: enabled: false sandboxDevicePlugin: enabled: falseУказываем mig strategy в mixed:
mig: strategy: mixedДобавляем (fix bug) в validator:
Скрытый текст
driver: env: name: DISABLE_DEV_CHAR_SYMLINK_CREATION value: "true"Заносим параметры в tolerations:
Скрытый текст
tolerations: - key: nvidia.com/gpu operator: Exists effect: NoScheduleЗапускаем pod для теста:
Смотрим логи:
kubectl apply -f cuda-vectoradd.yaml kubectl logs pod/cuda-vectoraddВот так выглядит корректный ответ:
Скрытый текст
log [Vector addition of 50000 elements] Copy input data from the host memory to the CUDA device CUDA kernel launch with 196 blocks of 256 threads Copy output data from the CUDA device to the host memory Test PASSED DoneПосле удаляем pod:
kubectl delete -f cuda-vectoradd.yaml
Делим GPU с помощью MIG
MIG (Multi-Instance GPU) в Kubernetes — это функция разделения графического процессора (GPU) на аппаратном уровне. MIG позволяет разделить один GPU на несколько изолированных экземпляров, каждый из которых имеет выделенные вычислительные ядра, память и кэш.
Эта функция полезна, когда у рабочих нагрузок разные требования к ресурсам и нужно обеспечить строгую изоляцию между ними.
Сейчас пошагово расскажем, как с этим работать.
Разбиваем GPU на отдельные виртуальные части mig, чтобы получить:
Изоляцию ресурсов
Каждая MIG-инстанция работает независимо — как отдельная GPU. Нет «шумных соседей» — если один под грузит GPU — другие не страдают. Изоляция на уровне железа, а не софта (как в контейнерах).
Максимальную утилизацию GPU
Вместо одной тяжелой задачи — можно запустить несколько легких. Например: A100 80GB → 7 MIG 1g.10gb → 7 задач inference.
Гарантированную производительность
Каждая MIG получает фиксированный объем памяти и вычислительных ядер. Идеально для inference-задач, где нужна предсказуемая задержка.
Безопасность
Изоляция на уровне железа — даже если один под «сломается», другие продолжат работать. Это отлично подходит для мультитенантных сред, где разные команды/проекты используют одну GPU.
Поддержку ML/DL/Inference
Идеально для inference, batch processing, ML pipelines. Не подходит для heavy training — там лучше использовать целую GPU.
Работа с MIG
Так как уже развернут gpu-operator со стратегией "mig strategy: mixed" и точно известно, что карты поддерживают разделение на MIG, важно выполнить несколько шагов, чтобы разбить имеющиеся у нас карты.
Первым делом смотрим доступные GPU на нодах:
kubectl get node -o json | jq '.items[].metadata.labels'Меняем профиль MIG на ноде (all-1g.10gb или all-1g.20gb — профиль, необходимо выбрать подходящий):
kubectl label nodes $NODE nvidia.com/mig.config=all-1g.10gb --overwriteМожем изменить профиль на сбалансированный, то есть разбить всю карту на части разных размеров:
kubectl label nodes <node-name> nvidia.com/mig.config=all-balanced --overwriteПри желании отключить MIG на ноде вводим эту команду:
kubectl label nodes $NODE nvidia.com/mig.config=all-disabled --overwriteДля того чтобы посмотреть список профилей MIG'ов, достаточно на ноде с GPU выполнить:
nvidia-smi mig -lgipЗапускаем поды с MIG — для этого указываем в манифесте следующее:
Скрытый текст
resources: limits: nvidia.com/mig-1g.10gb: 1Если нужен конкретный MIG-инстанс, даем больше информации:
Скрытый текст
resources: limits: nvidia.com/mig-1g.10gb: 1 requests: nvidia.com/mig-1g.10gb: 1Далее проверяем корректность работы с помощью команды.
kubectl get pods -o jsonpath='{range .items[]}{.metadata.name}{"\t"}{.spec.containers[].resources.limits}{"\n"}{end}'Если вы все сделали правильно, то на выходе получаете такой ответ:
my-pod-1 map[nvidia.com/mig-1g.10gb:1]Принципы и примеры разбиения на MIG.
Существует несколько подходов:
По нагрузке — меняем объем GB: MIG 1g.10gb или MIG 2g.20gb.
По количеству задач — меняем цифру в начале: 4 MIG 1g.10gb. или 2 MIG 2g.20gb.
По вычислительной мощности — изменяем количество ядер: 1g → 1/7 ядер, 2g → 2/7 ядер
По типу задач:
Inference — 1g.10gb
Training — 7g.40gb
Batch processing — 2g.20gbПо изоляции — при работе с критичными задачами важно выделить отдельную MIG.
По стоимости: MIG дешевле, чем целая GPU.
Примеры разбиения:
H100 80 GB:
14 MIG 1g.10gb (для inference)
4 MIG 2g.20gb (для training)
2 MIG 4g.40gb (для heavy training)
1 MIG 7g.80gb (для super-heavy training)
4. Недоступность MIG
Поды не могут переопределить MIG, если одна MIG становится недоступна, так как это физическая изоляция.
Что же происходит, если MIG недоступна:
Под не запускается — то есть Kubernetes не может выделить MIG.
Pod переходит в состояние Pending, пока не освободится MIG.
Восстановление реализуется через livenessProbe, readinessProbe, HorizontalPodAutoscaler.
Есть несколько доступных механизмов, позволяющих обойти это:
Failover. Можно переключиться на другую GPU, например, на GPU 1.
Circuit Breaker. Не нужно работать с MIG, если она недоступна. Если inference-сервис не отвечает, стоит переключиться на резервный.
Retry с экспоненциальной задержкой через 1s, 2s, 4s, 8s.
5. Планирование разбиения на MIG.
Для начала необходимо определить, какие задачи будут запускаться, и ответить себе на несколько вопросов:
Сколько нужно памяти и вычислительных ресурсов?
Какие MIG-инстанции доступны и сколько MIG можно создать?
Сколько задач могут запускать одновременно?
Нужна ли изоляция между задачами? А гарантированная производительность?
Какие метрики, логи, трейсы, дашборды важны для MIG?
Что важно учесть в CI/CD?
Деплой kubeflow 1.10
Для работы kubeflow необходимы cert-manager и istio, которые есть в репе с Kubeflow.
А для создания деплоя на джамп-хост необходимо установить kustomize и yq.
Первым делом клонируем проект:
Скрытый текст
git clone https://github.com/kubeflow/manifests.git cd manifests/ git checkout tags/v1.10 git branchУстанавливаем kubeflow в кластер по инструкции на странице проекта по адресу https://github.com/kubeflow/manifests с помощью kustomize.
Пропускаем установку cert-manager, так как он уже присутствует в кластере «Штурвала».
Поэтому устанавливаем только cert-manager-kubeflow-Issuer.
Istio устанавливаем, как указано для oauth-proxy, а oauth-proxy устанавливаем для dex.
Создаем файл ingress с типом сервиса istio-ingressgateway и названием ingress.yaml:
Пример манифеста
ingress.yamlпредставлен ниже (вместо переменных$clusternameи$envподставьте свои значения):Скрытый текст
apiVersion: networking.k8s.io/v1 kind: Ingress metadata: annotations: name: ingress-kubeflow namespace: istio-system spec: ingressClassName: nginx rules: - host: kubeflow.apps.k8s-$clustername-$env.company.io http: paths: - backend: service: name: istio-ingressgateway port: number: 80 path: / pathType: Prefix tls: - hosts: - kubeflow.apps.k8s-$clustername-$env.company.ioПрименяем манифест командой
kubectl apply -f ingress.yamlKubeflow состоит из множества компонентов и разворачивается довольно долго. В процессе деплоя возможно появление различных ошибок, во всяком случае у нас они постоянно появлялись, которые необходимо будет дебажить по мере их появления.
После деплоя Kubeflow выполняем вход по адресу (вместо переменных подставьте свои значения):
kubeflow.apps.k8s-$clustername-$env.company.io/ruи авторизуемся с профилем по умолчанию —
user@example.comи паролем12341234.Первоначальная установка Kubeflow завершена.
А что дальше?
Изначально в банке использовались две системы, которые не могли корректно работать вместе. Мы задеплоили их в два разных кластера, которые синхронно управляются платформой «Штурвал» — и благодаря этому они больше не конфликтуют.
В результате мы получили управляемую, безопасную и надежную ML-платформу. Теперь бизнес-заказчики могут быстро разворачивать модели для тестирования своих идей, а инженеры — заказывать и получать необходимые ресурсы.
Гибкое управление видеокартами, включая их разбиение, позволило равномерно использовать GPU-ресурсы. Сейчас они используются оптимально и могут быть при необходимости переконфигурированы. Решение успешно работает на отечественном софте в закрытом окружении.
В планах — автоматизировать процесс подготовки новых worker-нод для ML-платформы. Также мы хотим реализовать управление MIG'ами через подход «Инфраструктура как код». А команда «Штурвала» добавит больше «автоматики» в ближайших релизах 2.13 и 2.14.
Что вы думаете об этом опыте? Какие сложности вы видите при построении ML-платформ на Kubernetes? Какие вопросы остались без ответа в статье? Делитесь своим мнением или задавайте мне вопросы в комментариях.
