Стадия жизненного цикла модуля: Experimental
У модуля есть требования для установки

Руководство описывает административные действия: включить модуль, подключить S3-совместимое хранилище, выбрать способ доставки моделей, подготовить локальный кэш на узлах, настроить публичный каталог и проверить рабочее состояние.

Требования

  • Deckhouse Kubernetes Platform >= 1.74.
  • Kubernetes >= 1.30.
  • S3-совместимое объектное хранилище и bucket для данных DMCR и временных данных подготовки моделей.
  • Secret в d8-system с ключами accessKey и secretKey.
  • RWX StorageClass для режима SharedPVC.
  • Модули sds-node-configurator и sds-local-volume для режима NodeCache.

Включение

Создайте Secret с доступом к объектному хранилищу:

apiVersion: v1
kind: Secret
metadata:
  name: ai-models-artifacts
  namespace: d8-system
type: Opaque
stringData:
  accessKey: "<access-key>"
  secretKey: "<secret-key>"

Включите модуль:

apiVersion: deckhouse.io/v1alpha1
kind: ModuleConfig
metadata:
  name: ai-models
spec:
  enabled: true
  version: 1
  settings:
    logLevel: Info
    artifacts:
      bucket: ai-models
      endpoint: https://s3.example.com
      region: us-east-1
      credentialsSecretName: ai-models-artifacts
      usePathStyle: true

Если объектное хранилище использует частный центр сертификации, добавьте ca.crt в отдельный Secret в d8-system и укажите artifacts.caSecretName. Также можно положить ca.crt в Secret с учётными данными: модуль будет использовать его как доверенный сертификат.

Служебный Secret в d8-ai-models создаётся Helm’ом из данных, которые подготовил hook синхронизации. Администратор управляет только исходным Secret в d8-system.

Доставка моделей

delivery.type выбирает способ подключения моделей в фазе Ready к рабочей нагрузке. Эта настройка действует внутри одного кластера; она не является источником данных модели и не описывает внешний каталог. Если блок delivery не задан, используется SharedPVC.

SharedPVC

SharedPVC подходит для кластеров с хранилищем, которое поддерживает ReadWriteMany:

spec:
  settings:
    delivery:
      type: SharedPVC
      sharedPVCStorageClassName: rwx-storage-class

Если sharedPVCStorageClassName пустой, класс хранения выбирается в таком порядке:

  1. global.modules.storageClass;
  2. global.defaultClusterStorageClass;
  3. default StorageClass Kubernetes.

Выбранный класс должен существовать. После этого провайдер хранилища должен создать PVC с ReadWriteMany. Если класс не найден, контроллер оставляет рабочую нагрузку в ожидании с причиной SharedPVCStorageClassMissing. Если в кластере несколько default StorageClass, контроллер оставляет рабочую нагрузку в ожидании с причиной SharedPVCStorageClassAmbiguous; задайте класс в настройках модуля или в глобальных настройках Deckhouse, чтобы выбор был однозначным. Если PVC не создаётся или не привязывается, причину нужно смотреть в событиях PVC.

Локальный RWO PVC не является отдельным режимом доставки. Если нужно хранить модель рядом с приложениями на выбранных узлах, используйте NodeCache: модуль создаёт кэш на узле и передаёт модель в рабочую нагрузку через CSI-монтирование только для чтения.

NodeCache

NodeCache подходит для больших моделей и повторного использования модели несколькими рабочими нагрузками на одной ноде.

  1. Включите sds-node-configurator и sds-local-volume.

  2. Пометьте ноды для кэша:

    d8 k label node <node-name> ai.deckhouse.io/model-cache=true
  3. Пометьте свободные BlockDevice:

    d8 k label blockdevice <block-device-name> ai.deckhouse.io/model-cache=true
  4. Включите режим доставки:

    spec:
      settings:
        delivery:
          type: NodeCache
          nodeCacheSize: 200Gi

По умолчанию ноды и диски выбираются по label ai.deckhouse.io/model-cache=true. Если в кластере уже есть своя схема меток, задайте delivery.nodeCacheNodeSelector и delivery.nodeCacheBlockDeviceSelector.

Проверка подготовленных ресурсов:

d8 k get blockdevices.storage.deckhouse.io -o wide
d8 k get lvmvolumegroupsets.storage.deckhouse.io
d8 k get lvmvolumegroups.storage.deckhouse.io
d8 k get localstorageclasses.storage.deckhouse.io
d8 k -n d8-ai-models get pods,pvc -l app=ai-models-node-cache-runtime -o wide

Диск должен быть свободным и иметь consumable=true.

Лимит хранилища

artifacts.capacityLimit задаёт общий лимит для моделей, которыми управляет модуль:

spec:
  settings:
    artifacts:
      capacityLimit: 500Gi

При включённом лимите шлюз загрузки принимает файл только когда известен размер данных. Обычный curl -T передаёт Content-Length, multipart-клиент передаёт размер на /probe.

Объектное хранилище и DMCR

Bucket из artifacts.bucket принадлежит модулю. Не размещайте в нём сторонние данные и не удаляйте объекты вручную: контроллер и DMCR используют собственные связи между объектами, а ручное удаление может сломать локальную копию модели или повторную доставку в рабочую нагрузку.

DMCR (Deckhouse Model Container Registry) — служебный OCI-реестр модуля. Он хранит подготовленные модели как OCI-артефакты поверх настроенного S3-compatible bucket. Администратор настраивает bucket и доступ к нему, но не управляет OCI-путями, тегами, служебными ссылками и объектами DMCR вручную.

Модель в DMCR не хранится как один файл. Контроллер упаковывает исходные файлы модели во внутренний OCI-артефакт ModelPack, не меняя формат весов модели. Поэтому в интерфейсе объектного хранилища одна модель может выглядеть как десятки или сотни объектов. Часть объектов — это манифесты, конфигурации, слои и ссылки реестра; часть — промежуточные данные загрузки или зеркалирования источника; часть — служебные маркеры, которые позволяют безопасно возобновлять подготовку и удалять только неиспользуемые данные.

В интерфейсе S3-совместимого хранилища можно ориентироваться на такие группы. Имена префиксов являются внутренней структурой и не считаются стабильным API.

Группа объектов Для чего нужна Когда удаляется
docker/registry/... Данные и метаданные внутреннего OCI-реестра: манифесты, конфигурации, ссылки реестра и слои моделей. После удаления владельца и успешной сборки мусора. Общие слои остаются, пока нужны другой модели.
raw/... Промежуточные данные подготовки: загруженный файл, снимок источника HuggingFace/Ollama или данные для повторного запуска подготовки. После удаления модели или после завершения связанной процедуры очистки.
_ai_models/direct-upload/... Физические объекты прямой загрузки и multipart-сессии, которые затем привязываются к OCI-артефакту. После успешной финализации или как устаревшие сиротские данные.
Открытые multipart-загрузки Незавершённые части загрузки, которые не всегда видны как обычные объекты в интерфейсе. Сборка мусора прерывает устаревшие multipart-загрузки отдельно от удаления объектов.

Количество объектов не равно количеству моделей. Один маленький объект может быть служебной ссылкой, а один слой большой модели может занимать гигабайты. Общий слой может использоваться несколькими артефактами, поэтому удаление одной модели не всегда сразу уменьшает занятое место на размер этой модели.

Удаление Model или ClusterModel запускает асинхронную очистку:

  1. Контроллер убирает ссылку модели из каталога и ставит запрос на очистку.
  2. Служебный процесс DMCR объединяет запросы, включает режим обслуживания и ждёт подтверждения от реплик.
  3. Затем удаляются устаревшие префиксы промежуточных данных, прерываются старые multipart-загрузки и запускается сборка мусора OCI-реестра.
  4. Результат очистки публикуется в метриках и логах.

Поэтому сразу после удаления модели в bucket ещё могут оставаться объекты. Это нормально, пока нет алертов D8AIModelsPublicationCleanupBacklogStale или D8AIModelsPublicationCleanupFailed, а дашборд показывает завершённые циклы очистки.

Безопасные проверки:

d8 k get models.ai.deckhouse.io -A
d8 k get clustermodels.ai.deckhouse.io
d8 k -n d8-ai-models get secrets -l ai.deckhouse.io/dmcr-gc-request=true
d8 k -n d8-ai-models logs deploy/dmcr -c dmcr-garbage-collection --since=2h

В логах ищите сообщения dmcr garbage collection completed: в них есть количество удалённых объектов, освобождённые байты и число удалённых слоёв DMCR. Если объекты в bucket растут, а запросы очистки зависли или падают, сначала исправьте причину по алерту и логам. Ручное удаление объектов из bucket допустимо только как отдельная аварийная процедура с подтверждённым списком префиксов.

Как проходят данные модели

У модуля один путь подготовки модели и два способа доставки в рабочие нагрузки.

Подготовка читает источник модели, проверяет данные и упаковывает исходные файлы во внутренний OCI-артефакт ModelPack. Это не конвертация весов модели: GGUF остаётся GGUF, Safetensors остаётся Safetensors. DMCR сохраняет полученный артефакт как локальную проверенную копию. В мониторинге видно исходный размер модели и фактически занятое место, поэтому оператор понимает, дают ли разбиение на части и сжатие экономию в bucket.

SharedPVC переносит модель из DMCR в управляемый контроллером RWX PVC в пространстве имён рабочей нагрузки. Служебная Job не использует Kubernetes API для чтения модели. Прогресс считается в DMCR по подписанному разрешению на чтение, выданному именно этой Job, поэтому дашборд показывает ожидаемый объём, уже прочитанный объём и скорость по каждой Job.

NodeCache переносит модель из DMCR в локальный кэш на узле. Долгоживущий служебный компонент node-cache отдаёт ожидаемый и загруженный объём по узлу и модели, а также занятое место кэша и задержку CSI-запросов.

Раздача каталога использует отдельную передачу данных. Потребляющий кластер сначала читает понятный каталог, затем импортирует выбранные OCI-артефакты как локальные копии. DMCR логирует чтение с идентификатором потребителя и отдаёт метрики скорости и объёма, сгруппированные по назначению передачи.

Раздача каталога между контурами

Раздача каталога отвечает за обмен ClusterModel в фазе Ready между сетевыми зонами. Она не меняет способ подключения модели к рабочей нагрузке.

Типовой сценарий:

  1. В DMZ работает раздающий кластер, который отдаёт ClusterModel в фазе Ready.
  2. Во внутреннем периметре потребляющий кластер импортирует выбранные модели как локальные копии.
  3. Доставка в рабочие нагрузки во внутреннем кластере остаётся SharedPVC или NodeCache.

Такой сценарий полезен, когда кластер в DMZ только раздаёт каталог: он подготавливает и отдаёт модели, но в нём нет рабочих нагрузок с аннотациями. Поэтому раздача каталога не должна превращаться в третье значение delivery.type; это отдельная ось конфигурации.

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

spec:
  settings:
    distribution:
      mode: PublicCatalog

После применения настройки модуль готовит публичный адрес и маршруты:

https://ai-models.example.com/api/distribution/v1/models
https://ai-models.example.com/v2

/api/distribution/v1/models отдаёт понятный каталог: только ClusterModel в фазе Ready, без внутренних имён реестра и служебных UID. /v2 остаётся OCI-путём для управляемого контроллером копирования и импорта.

Доступ потребителей

Доступ к публичному каталогу проверяется через аутентификацию и авторизацию Kubernetes в раздающем кластере. Модуль не создаёт отдельную CRD для потребителей.

Создайте ServiceAccount для каждого потребляющего кластера, организации или периметра и привяжите его к роли чтения каталога:

apiVersion: v1
kind: ServiceAccount
metadata:
  name: perimeter-a
  namespace: d8-ai-models
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: ai-models-distribution-reader-perimeter-a
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: d8:ai-models:distribution:reader
subjects:
  - kind: ServiceAccount
    name: perimeter-a
    namespace: d8-ai-models

Выпустите токен в раздающем кластере и передайте администратору потребляющего кластера только его значение по защищённому внешнему каналу:

d8 k -n d8-ai-models create token perimeter-a --duration=720h

Для долгоживущих эксплуатационных учётных данных можно создать Kubernetes service-account-token Secret в раздающем кластере и прочитать ключ token после заполнения Kubernetes:

apiVersion: v1
kind: Secret
metadata:
  name: perimeter-a-token
  namespace: d8-ai-models
  annotations:
    kubernetes.io/service-account.name: perimeter-a
type: kubernetes.io/service-account-token

Модуль не выпускает и не переносит этот токен автоматически между кластерами: для этого нужен внешний доверенный канал или secret-manager. Ротация выполняется выпуском нового токена в раздающем кластере и обновлением Secret в потребляющем кластере. После обновления Secret контроллер перечитает ModelCatalogSource и продолжит работу с новым токеном.

Отзыв доступа выполняется удалением RoleBinding или ServiceAccount. Это закрывает новые запросы каталога и новые разрешения на чтение; уже выданные короткоживущие разрешения на чтение истекают по TTL.

Подключение потребляющего кластера

Потребляющий кластер описывает внешние каталоги через ModelCatalogSource:

apiVersion: ai.deckhouse.io/v1alpha1
kind: ModelCatalogSource
metadata:
  name: dmz
spec:
  url: https://ai-models.dmz.example.com
  credentialsSecretName: ai-models-dmz-read
  caSecretName: ai-models-dmz-ca

Secret из credentialsSecretName и caSecretName находятся в d8-system. Контроллер читает их напрямую для обновления каталога и выдачи разрешений на чтение. Secret из credentialsSecretName содержит токен ServiceAccount, выпущенный раздающим кластером; caSecretName содержит ca.crt и нужен только для частного CA внешнего каталога. Эти Secret не копируются в пространства имён приложений.

Модели выбираются не в ModuleConfig. Раздающий кластер экспортирует все ClusterModel в фазе Ready, а пользователи потребляющего кластера импортируют нужные модели через spec.source.catalog.name.

Представление внешнего каталога является кластерным. Оно показывает удалённые записи и локальные копии как ссылки на Model или ClusterModel, включая пространство имён для Model. Это представление доступно только ролям управления модулем и не содержит URL источника, имена Secret, токены, OCI-репозитории, теги или списки blob.

Аудит

API публичного каталога проверяет токен через TokenReview и авторизует запросы через SubjectAccessReview: список каталога требует list на clustermodels.ai.deckhouse.io, просмотр модели и выдача разрешения на чтение требуют get на выбранный ClusterModel.

Контроллер пишет структурированные события аудита для catalog_list, catalog_get, pull_grant_issued, catalog_auth_denied, а также для конфликтов и ошибок. В события попадают имя пользователя Kubernetes, UID, группы, IP-адрес клиента, имя модели, контрольная сумма и результат проверки. DMCR пишет события чтения манифеста и слоёв с той же идентичностью. Сырой токен и его хэш в аудит не попадают.

RBAC

Модуль использует модель уровней доступа Deckhouse:

Уровень Доступ
User чтение Model, ClusterModel и статусов;
Editor управление Model в пространстве имён;
ClusterEditor управление ClusterModel;
ClusterAdmin управление ModelCatalogSource, привязками доступа к публичному каталогу и представлением импортов;
rbacv2/use использование Model в пространстве имён;
rbacv2/manage управление Model, ClusterModel, ModelCatalogSource, ModuleConfig и представлением импортов.

Доступ к загрузке выдаётся отдельной Role на один Secret из status.upload.secretName. Role создаётся в пространстве имён модели и называется ai-model-upload-reader-<model-name> либо получает стабильный хэш для длинных имён.

Восстановление импорта из внешнего каталога

Импорт хранит зафиксированное происхождение: выбранный внешний каталог, имя модели, ревизию каталога и удалённую контрольную сумму. Это защищает рабочую нагрузку от незаметного переезда на другую версию модели при следующем согласовании.

Следующие ошибки восстанавливаются после исправления причины:

  • CatalogAuthFailed — истёк токен, Secret обновлён или исправлен RBAC на раздающем кластере;
  • CatalogTLSInvalid — исправлен caSecretName или содержимое ca.crt;
  • CatalogSourceNotReady — внешний каталог снова перешёл в фазу Ready.

После восстановления ModelCatalogSource контроллер повторяет импорт той же зафиксированной модели. Ошибки ManifestInvalid, InsufficientStorage и нарушенный контракт каталога не повторяются автоматически: сначала нужно исправить исходный артефакт, лимит хранилища или спецификацию каталога.

Проверка:

d8 k get modelcatalogsources.ai.deckhouse.io
d8 k describe modelcatalogsource <name>
d8 k -n <namespace> describe model <name>

Мониторинг

Проверьте ресурсы мониторинга:

d8 k -n d8-ai-models get podmonitor,prometheusrule

Основные разделы дашбордов:

  • Обзор кластера. Показывает общий инвентарь Model и ClusterModel, количество объектов в фазах Publishing, Ready и Failed, общий размер подготовленных локальных копий, число управляемых рабочих нагрузок и ссылки на модели, которые контроллер не смог разрешить. Начинайте диагностику с этого раздела: ненулевые Failed и unresolved references означают, что нужно перейти к конкретной модели или рабочей нагрузке.
  • Состояние каталога. Отдельные дашборды для namespace-scoped Model и cluster-scoped ClusterModel помогают понять, где находится проблема: в конкретном namespace, в общем каталоге кластера или в отдельной модели. Смотрите фазу, готовность, условия, источник, формат, размер локальной копии, потребителей модели и таблицу рабочих нагрузок с неразрешённой доставкой.
  • Подготовка моделей. Раздел показывает текущие объекты в подготовке, прогресс загрузки и упаковки, скорость передачи данных, ошибки завершения или проверки, а также повторные попытки. Если прогресс долго не меняется, сравните скорость передачи с состоянием bucket/DMCR и проверьте события соответствующего Model или ClusterModel.
  • DMCR и bucket. Панели ёмкости показывают настроенный лимит, занятое, зарезервированное и доступное место для подготовленных локальных копий. Отдельно отображается эффективность хранения ModelPack: логический размер модели и фактически занятое место могут отличаться из-за разбиения на слои, архивирования и переиспользования данных. Очередь очистки показывает pending, active и failed cleanup requests после удаления моделей.
  • Доставка в рабочие нагрузки. Раздел показывает, какие workloads управляются модулем, сколько Pod’ов готово, какой способ доставки выбран и почему. Для SharedPVC смотрите состояние PVC, очередь копирования и throughput materialize Job. Для неизвестного способа доставки или неразрешённой ссылки проверьте имя модели, namespace и права на её использование.
  • Кэш на узлах. Раздел нужен для режима NodeCache: он показывает служебные Pod’ы, привязанные PVC, занятое и доступное место на каждом узле, число записей в кэше, скорость копирования, параллелизм подготовки и задержки CSI mount/unmount-запросов. Рост задержек или нехватка эффективного свободного места обычно указывает на проблему локального диска, PVC или node-cache runtime.
  • Раздача каталога. Если включена раздача между контурами, смотрите частоту запросов к публичному каталогу, выдачу разрешений на чтение, задержки API и throughput импортов. Ошибки авторизации или рост задержек нужно сопоставлять с RBAC потребителей, состоянием API-сервера и аудитом раздающего кластера.

Эксплуатационные проверки

Проверить компоненты:

d8 k -n d8-ai-models get pods -o wide
d8 k get models.ai.deckhouse.io -A
d8 k get clustermodels.ai.deckhouse.io

Проверить модель:

d8 k -n <namespace> describe model <name>
d8 k get clustermodel <name> -o yaml

Ключевые поля:

  • status.phase;
  • status.conditions;
  • status.artifact.digest;
  • status.artifact.sizeBytes;
  • status.resolved.format;
  • status.resolved.supportedEndpointTypes;
  • status.resolved.supportedFeatures.

Выключение

При spec.enabled=false удаляются временные служебные ресурсы, которыми владеет модуль: node-cache runtime Pod/PVC, CSIDriver, LocalStorageClass, LVMVolumeGroupSet, managed LVMVolumeGroup и StorageClass ai-models-node-cache.

Model, ClusterModel и уже подготовленные локальные копии моделей остаются. Удаление модели выполняется через удаление соответствующего Model или ClusterModel; контроллер завершает очистку через finalizer и GC-request.