MLOps в Yubo: Kubernetes, GitOps и KServe

Мы продолжаем серию статей о том, как устроена работа команды инфраструктуры Yubo. Как мы уже рассказывали в предыдущих материалах, мы любим автоматизацию и cloud-native-инструменты. Особенно нам нравится описывать инфраструктуру полностью декларативно и доверять масштабирование автоматизации нашим инструментам. MLOps не стал исключением: мы выстраиваем его по принципам Kubernetes и GitOps!

Архитектура

Архитектура MLOps в Yubo

Наша цель в Yubo — дать специалистам по данным полную свободу экспериментировать, сохраняя при этом те же стандарты операционной надёжности и масштабируемости, что и в наших продакшн-системах.

Наша MLOps-архитектура достигает этого, объединяя набор cloud-native-компонентов с полностью открытым исходным кодом, которые слаженно работают за счёт автоматизации и декларативных описаний.

Вот как складывается эта архитектура:

  • Kubernetes. Мы держим отдельный кластер Kubernetes под задачи data science. Он обеспечивает изоляцию ресурсов, автомасштабирование и удобную оркестрацию обучения и обслуживания моделей.

  • Argo Workflows для оркестрации. Учитывая нашу ставку на Kubernetes-native-автоматизацию и центральную роль Argo Workflows в других сценариях Yubo, он стал естественным выбором для управления пайплайнами обучения моделей. Мы используем шаблоны Argo, чтобы описывать переиспользуемые воркфлоу, которые ML-инженеры запускают, просто задав YAML-конфигурацию.

  • Apache Iceberg + Trino для данных. Исторические данные для обучения хранятся в облачном data lakehouse в формате Apache Iceberg, а запросы к ним выполняются через Trino. Такая связка позволяет масштабируемо обрабатывать огромные наборы данных.

  • KServe + Istio для инференса. KServe унифицирует интерфейс обслуживания моделей для разных ML-фреймворков (TensorFlow, PyTorch и т. д.), что критично в динамичной среде, где важна скорость экспериментов. Благодаря тесной интеграции с Istio KServe позволяет легко проводить A/B-тесты на сетевом уровне.

  • CRD ML Model и собственный оператор Kubernetes. Чтобы связать всё воедино, мы написали собственный оператор Kubernetes и кастомный CRD ML Model. Этот оператор соединяет Argo, MLflow и KServe, автоматизируя валидацию, развёртывание и откат моделей на основе alias-тегов в MLflow.

Наша MLOps-архитектура превращает машинное обучение в полностью декларативную систему, где всё — от обучения до развёртывания — управляется по принципам GitOps и средствами Kubernetes-native-автоматизации.

В следующих разделах мы подробнее разберём каждый компонент.

Шаблоны обучения

Ключевой принцип нашего подхода к MLOps — делать пайплайны воспроизводимыми и самообслуживаемыми. Мы хотели, чтобы ML-инженеры сосредоточились на моделях, а не на инфраструктуре, оркестрации и рутинной настройке.

Для этого мы создали шаблон Argo Workflow, задающий стандартный «скелет» пайплайна обучения. Шаблон инкапсулирует лучшие практики воспроизводимости, кеширования и управления артефактами, оставаясь при этом достаточно гибким для нестандартных экспериментов.

Благодаря этому перенос пайплайна из локальных экспериментов в наше продакшн-окружение Kubernetes сводится к описанию одной YAML-конфигурации.

Вот как выглядит такая конфигурация:

workflows:
- pipeline_name: pipeline-example
  image: us-central1-docker.pkg.dev/yubo-mlops-tools/demo-mlops-training-main:latest
  schedule: "1 * * * *"
  artifacts_caching_enabled: true
  artifacts_cache_max_age: "1h"
  resources:
    cpu: "1"
    memory: "1Gi"
  tasks:
    data_preprocessing:
      - name: data
        command: |
          python create_dataset.py
        secrets:
          - "datascience/training_pipelines/common"
        outputs:
          - name: dataset
            path: /tmp/dataset.csv
    training:
      - name: train
        depends: [data]
        command: |
          python train_and_log_model.py
        resources:
          cpu: "1"
          memory: "1Gi"
        secrets:
          - "datascience/training_pipelines/common"
        inputs:
          - name: dataset
            path: /tmp/dataset.csv
    post_training:
      - name: evaluate
        depends: [train]
        secrets:
          - "datascience/training_pipelines/common"
        command: |
          python model_evaluate.py

Эта конфигурация задаёт все ключевые параметры пайплайна. После коммита наши инструменты рендерят YAML-шаблон с помощью ytt и автоматически создают соответствующие объекты Argo WorkflowTemplate и CronWorkflow.

Если у пайплайна задано расписание, он запускается периодически. Иначе его можно запустить вручную или через Argo Sensors, которые позволяют выполнять пайплайн по событиям на основе внешних сигналов.

Все результаты работы пайплайна автоматически складываются в бакет Google Cloud Storage, который служит нашим реестром артефактов. Например, датасет, полученный на шаге предобработки, загружается туда и затем переиспользуется на шаге обучения.

Параметр artifacts_caching_enabled включает механизм мемоизации Argo: тяжёлые шаги вроде предобработки данных можно пропустить и переиспользовать их результат, если входные данные не изменились, — это резко сокращает затраты на вычисления и время выполнения.

Кроме того, все артефакты и метрики, порождённые прогонами обучения, логируются в MLflow, который мы используем для отслеживания экспериментов и версионирования моделей. Это обеспечивает сквозную прослеживаемость.

Поскольку весь пайплайн описан через Kubernetes-native-компоненты, интеграция с другими ML-инструментами делается просто. Например, наши шаблоны Argo легко стыкуются с Katib для подбора гиперпараметров или с Kubeflow Training Operator для распределённого обучения моделей — все они предоставляют CRD Kubernetes.

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

Коротко говоря, наша система шаблонов на базе Argo позволяет ML-инженерам описывать сквозные пайплайны декларативно, получая автоматизацию, воспроизводимость и нативную масштабируемость Kubernetes.

Инфраструктура обслуживания моделей

Наша инфраструктура обслуживания использует KServe как единый интерфейс для развёртывания ML-моделей, обученных на разных фреймворках.

KServe вводит два Custom Resource Definition (CRD) в Kubernetes:

  • ServingRuntime задаёт «чертёж» развёртывания модели — например, среду выполнения TensorFlow Serving или MLServer.

  • InferenceService описывает конкретное развёртывание модели, которое ссылается на подходящий ServingRuntime в зависимости от формата модели и создаёт соответствующие поды в кластере Kubernetes.

Такая схема даёт единообразный паттерн развёртывания для разных фреймворков и источников моделей, охватывая как предобученные внешние модели, так и обученные внутри компании.

Ещё одна мощная возможность KServe — ModelMesh.

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

Чтобы эффективно справляться с такой неравномерностью, мы используем KServe ModelMesh — он отлично нам подошёл.

ModelMesh динамически загружает и выгружает модели на пуле GPU-подов в зависимости от характера трафика. Неиспользуемые модели вытесняются из памяти, а часто запрашиваемые остаются «горячими» и масштабируются по числу запросов.

Это позволило нам существенно снизить затраты на GPU в наших кластерах.

Для моделей же, которые стабильно держат большой объём запросов, мы по-прежнему можем выделять отдельные развёртывания — достаточно отключить ModelMesh флагом modelMesh в нашем CRD модели.

Обслуживание моделей в KServe с ModelMesh

CRD модели и оператор Kubernetes

Наша цель — превратить весь жизненный цикл модели в полностью автоматизированный пайплайн.

Для этого нужно было связать наш реестр моделей с кластером Argo Workflows и со слоем развёртывания KServe, и мы решили сделать это привычным нам способом — через собственный CRD.

Мы разработали собственный оператор Kubernetes и новый CRD под названием MLFlowModel.

Когда в MLflow появляется новая версия модели, оператор автоматически:

  1. Обнаруживает новую версию.

  2. Запускает валидацию и CI-джобы в Argo.

  3. После успешной валидации обновляет теги в MLflow (например, prod, staging).

  4. Соответствующим образом развёртывает модель в KServe или откатывает её.

Вот пример такого CRD:

apiVersion: mlmanager.live.yubo/v1alpha1
kind: MLFlowModel
metadata:
  name: demo-mlops
spec:
  modelName: demo_mlops
  deployersList:
    - KSERVE
  kserve:
    modelFormat: tensorflow
    modelMesh: false
    targetAlias: prod
    runtime: kserve-tensorflow-serving
    predictorScaling:
      minReplicas: 1
      maxReplicas: 10
      scaleTarget: 10
      scaleMetric: concurrency
    grpc:
      enabled: true
      port: 9000
    env:
      - name: TF_SERVING_BATCHING_PARAMETERS
        value: |
          max_batch_size { value: 8 }
          batch_timeout_micros { value: 10000 }
          max_enqueued_batches { value: 6 }
          num_batch_threads { value: 3 }
  validatorsList:
    - ARGOWF
  buildersList: []
  argoValidator:
    validationTestImage: us-central1-docker.pkg.dev/yubo-mlops-tools/demo-validation:latest
    validationTestCommand: python validate_model.py
    validationTestArgs: |
      {"validation_args1": "args1"}
    modelAliasAfterValidation: prod
    pauseForHumanConfirmation: true

Этот CRD описывает всё, что нужно оператору для управления моделью — от валидации до развёртывания.

Например, в этой конфигурации:

  • Валидатор Argo прогоняет тесты и после успешной валидации присваивает модели alias prod в MLflow.

  • Деплойер KServe создаёт или обновляет соответствующий InferenceService.

  • InferenceService нацелен на среду выполнения TensorFlow Serving, и модель получит отдельное развёртывание, поскольку флаг modelMesh выставлен в false.

Оператор непрерывно следит за моделями в MLflow на предмет новых версий или изменения alias-ов. Когда задача обучения публикует новую модель (например, версию 2), оператор обнаруживает это и запускает CI-воркфлоу Argo, описанный в CRD модели. CI-пайплайн может включать такие шаги, как тестирование, сканирование безопасности или сборку кастомного окружения для развёртывания.

После успешной валидации оператор проставляет тег новой версии модели в MLflow (например, повышает её до prod), что, в свою очередь, запускает обновление соответствующего InferenceService в KServe.

Такой подход превращает жизненный цикл модели в полностью декларативный процесс, где развернуть или откатить модель так же просто, как обновить тег в MLflow.

Оператор устроен модульно, поэтому в него легко подключать разные инструменты валидации или цели развёртывания. Сегодня мы используем Argo Workflows для валидации и KServe для развёртывания, но в будущем тот же оператор мог бы поддерживать и другие валидаторы или бэкенды обслуживания.

Диаграмма последовательности ниже иллюстрирует работу оператора:

Диаграмма последовательности работы оператора

Эксперименты и мониторинг

Использование Istio позволяет нам легко ставить эксперименты и проводить A/B-тесты между разными версиями моделей через VirtualService. Настроив простое разделение трафика в VirtualService, мы можем постепенно перераспределять трафик между моделями: начинаем с небольшого процента и плавно увеличиваем его, непрерывно наблюдая за продакшн-метриками. Это делает эксперименты безопасными, управляемыми и полностью автоматизированными на сетевом уровне.

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

Чтобы это обеспечить, мы логируем предсказания моделей (входы и выходы) в Kafka. Затем эти логи синхронизируются в наше озеро данных, где их можно анализировать кастомными пайплайнами мониторинга. Такая схема позволяет непрерывно выявлять аномалии, деградацию качества или дрейф данных, поддерживая модели здоровыми и надёжными.

Заключение

В Yubo автоматизация инфраструктуры остаётся одной из ключевых ценностей нашей инженерной культуры. Распространив этот принцип на системы машинного обучения, мы получили больше контроля, масштабируемости и экономии, а компания смогла уверенно выводить новые ML-сценарии в продакшн.

Более того, опираясь на cloud-native-технологии — Kubernetes, Argo Workflows и собственные CRD, — мы сблизили машинное обучение с остальной экосистемой нашей инфраструктуры. Такой единый подход упрощает сопровождение и позволяет каждому участнику команды инфраструктуры вносить вклад привычными инструментами и рабочими процессами.

Наша MLOps-архитектура не только ускоряет инновации, но и позволяет небольшой команде уверенно и гибко управлять платформой, обслуживающей миллионы пользователей.

© 2026 meganuke