Привет, Хабр! Меня зовут Кирилл Кулаков, я занимаюсь развитием MLOps-платформы в Uzum Fintech.
Недавно у нас в команде разгорелся спор о том, как правильно разворачивать платформы. Причем разгорелся он уже после того, как мы совместно всё спроектировали, двигались какое-то время в одном инфополе, и я уже развернул довольно большую часть.
И тут коллеги начали задавать вопросы «Почему мы не разворачиваем каждый компонент последовательно, настраивая все досконально?». Для меня это звучало как: «Ты сделал неправильно, сейчас будем разбирать твою работу и от половины откажемся».
Мы строим MLOps-платформу под широкий спектр AI-задач:
тут и задачи классического ML (кредитный скоринг, антифрод);
инференс — классические ML-алгоритмы (бустинги), LLM/VLM модели;
пайплайны на основе LLM (Мультиагентные системы / RAG).
И по ходу работы постоянно сталкиваемся с архитектурными вызовами и альтернативными решениями.
В какой-то момент мы поняли: для одной и той же задачи мы можем собрать множество архитектурных вызовов, а потом тестировать такое же множество разных подходов. А время всё ещё не резиновое, как и силы команды. Так что мы сели и серьёзно обсудили наши методы развёртывания ИТ-платформ.
И потом я задумался, а на самом ли деле в разных компаниях и командах бывают настолько разные подходы к этому делу?
В статье предлагаю поисследовать эту тему вместе со мной.
Борьба подходов
Перед нами два подхода:
1. Сначала развернуть все запроектированные компоненты (FluxCD, Vault, External-Secret, MLflow, Airflow, JupyterHub, Keycloak и прочее), и уже потом их донастраивать, интегрировать между собой и тестировать — «горизонтальный взрыв».
2. Сразу разворачивать каждый компонент, протестированный, и только потом переходить к следующему — «вертикальный срез».
Я выбрал первый подход, так как это тестовый кластер (будущий dev-контур), и продуктовый подход — когда мы делаем MVP какой-либо системы, который должен содержать минимально необходимый набор нужных компонентов системы для демонстрации всей идеи на конвейере на котором можно создавать модели и их использовать. В компании такого не строили, сроки начала использования всей платформы тоже пока не очень понятны.
Набор компонентов и взаимодействие между ними, равно как и подводные камни с интеграцией между сервисами, вызывали у меня очевидные опасения.
Работая 13 лет в ИТ, я уже много раз видел, как вылизанный красивый компонент системы при внедрении нового вдруг становился ненужным и отправлялся в топку, вместе с затраченным на него временем и усилиями.
Поэтому я для себя выработал принцип достаточности:
Если требования к завершенности компонента системы четко не предъявлены, то минимально достаточным Definition of Done будет запущенный и работающий компонент системы без ошибок в логах, со стандартным минимальным набором зависимостей.
Дальше тестируем весь pipeline, доводя весь конечный результат «от данных до использования модели».
Но, как мы знаем, истина всегда где-то посередине. И об этом я расскажу в конце статьи.
Рассмотрим оба подхода подробнее.
Подход А
Разворачиваем всё (FluxCD, Vault, External-Secret, Ray, Keycloak, Redis, Postgres, Feast, MLflow, …), потом отлаживаем и донастраиваем — MVP платформы.
Плюсы
Параллелизация работы инженеров по широкому фронту сервисов при условии использования GitOps (один раскатывает MLflow, другой в это время катит JupyterHub с обязательным сохранением в git).
Быстро видим сразу весь продукт (весь стек и конечный результат MLOps-платформы).
Доказательство, что все вообще соберется в один кластер.
Ждем не 6-12 месяцев, а 2-3 (с учетом того, что не со всеми компонентами работали, но helm и kubectl мы умеем пользоваться).
Минусы
Нет короткой обратной связи «деплой → проверка → откат»: нарушается дух маленьких партий изменений, с которым коррелирует производительность поставки в исследованиях DORA (см. также книгу Accelerate — Nicole Forsgren, Jez Humble, Gene Kim).
Чем больше компонентов поднимается одновременно, тем сложнее диагностика. Количество потенциальных точек отказа и связей между сервисами быстро растёт, поэтому поиск причин проблем занимает больше времени.
Выше вероятность комбинаторных отказов по зависимостям в сервисах (External Secrets еще не отдает секрет для Postgres, Postgres лежит, и Airflow не может подняться).
Операционный долг накапливается до первой рабочей ценности для потребителя платформы (ML-инженер всё ещё не может залогиниться и зарегистрировать модель).
Риск неправильного порядка и нарушение идемпотентности (повторяемости с одинаковым ожидаемым результатом) секретов/БД/миграций без единого источника правды, что ломает повторяемость окружений (антипаттерн для 12-Factor App-подхода и GitOps-дисциплину).
В мировой практике это считается нормой в ограниченном виде: короткий proof-of-concept / hackathon cluster / dev / staging, который можно выкинуть/пересобрать лабораторию под конкретный RFP (стенд, который собирают под требования конкретного тендера/запроса клиента). Это не эталон для production-платформы без жестких волн и критериев готовности.
Подход Б
Каждый компонент последовательно: полная настройка + тесты, затем следующий компонент.
Плюсы
Малый радиус изменений: проще отслеживать и онбордить (сейчас на платформе только Vault).
Явный Definition of Done на сервис: healthchecks, backup/restore где нужно, runbook, SLO-заготовка:такой подход хорошо сочетается с практиками SRE и эксплуатационной зрелостью Google SRE Book.
Естественный порядок зависимостей: сначала базовая инфраструктура (сеть, storage, ingress, базы данных), потом приложения, которые от них зависят.
Минусы
Интеграционные сюрпризы всплывают в конце: по отдельности сервисы работают, но в связке возникают конфликты , (MTLS политики, OIDC редиректы).
Риск перепроектирования: можно идеально настроить MLflow, а потом выяснить, что Feast требует другой модели доступа к фичам).
Время до сквозного сценария (deploy model → inference → observability) может сдвинуться на квартал, если нет явных промежуточных интеграционных вех.
На практике зрелые платформенные команды редко используют крайние подходы в чистом виде. Обычно применяют поэтапное развитие платформы:
с критериями готовностями;
интеграционными вехами;
и регулярной проверкой сквозных сценариев.
На это же указывают исследования DORA и CNCF Platform Engineering Maturity Model: маленькие изменения и частая интеграция почти всегда устойчивее больших взрывных релизов.
На практике же лучше всего работает гибридный подход: что-то похожее на выбор методов слияния моделей.

Мы пришли к модели волн: когда платформа развивается слоями, а каждый слой даёт системе новую полезность.
№ волны |
Что появляется |
Что это нам даёт |
0 |
Kubernetes, GitOps, ingress, observability |
Базовый фундамент |
1 |
Keycloak, Vault, RBAC |
Auth и secrets |
2 |
PostgreSQL, Redis, S3 |
Data layer |
3 |
MLflow, Feast, Airflow |
ML pipeline |
А теперь подробнее про каждую волну.
Wave 0 — фундамент платформы
Тут у нас железо, terraform, GitOps, ingress, tls, observability.
Собрать требования, спроектировать систему
Развернуть/получить серверные мощности, доступы
Подготовить виртуальные машины, сети, хранилища (IaC Terraform + Ansible)
Поднять Kubernetes-кластер, CNI, CSI
Развернуть ingress-контроллер + certs
Развернуть GitOps control plane (fluxcd + git + reconcile policy).
Развернуть базовое observability (fluentbit, loki, grafana, Victoria/Prometheus)
OpenTelemetry/Jaeger — в целом зависит от задач, но тоже можно сразу добавить.
Что считаем результатом:
Фундамент платформы создан
Flux исключает ручное управление типа kubectl apply.
Ingress, обмазанный TLS, проксирует запросы к UI какого-то helloworld сервиса.
Сертификаты автоматически выпускаются
Видны базовые метрики работы кластера и компонентов
Есть runbook, базовая документация «как откатить неудачный релиз через GitOps»
Wave 1 — идентичность
На этом уровне — Keycloak и Vault
Развернут Keycloak (или ваш IdP:identity provider)
Vault/External Secrets (или эквивалент секретного слоя)
Service accounts / RBAC / минимальная модель прав между сервисами
Базовые OIDC/OAuth-интеграции для платформенных сервисов (пока хотя бы 1-2 эталонных)
Эти сервисы относят ко второму эшелону, потому что ML-сервисы и data plane требуют стабильной auth+secrets-models. Если это отложить, позже обычно начинается массовый рефакторинг конфигураций и доступов.
Что считаем результатом:
Работает хотя бы один machine-to-machine сценарий (service account/token).
Доступен SSO для пользователей.
Секреты приезжают в pod’ы из источника автоматически (не нужно копипастить).
Проверена ротация секретов
Понятно, кто и откуда получает доступ к токенам и секретам
Wave 2 — Data plane
Состав волны:
Развернут HA-кластер PostgreSQL
Развернут HA-кластер Redis
Развернуто S3-совместимое хранилище (Minio, Rook Ceph)
Backup/restore политики для statefull-компонентов
Готовы базовые ресурсы (storage class, retention)
Что считаем результатом:
Stateful-сервисы стабильны после restart/rollout
Backup и restore протестированы хотя бы на тестовом наборе
Секреты доступны не хардкодом, а через Wave 1
Latency и throughput остаются приемлемыми для dev-сценариев.
Есть базовые smoke-тесты записи и чтения данных.
Wave 3 — ML stack
Что сюда входит:
Развернут реестр моделей (MLflow, например)
Развернут компонент работы с фичами (Feast)
Развернут компонент сервинга моделей (Ray)
Развернут компонент оркестрации (типа Airflow)
Развернут компонент работы с экспериментами (типа JupyterHub)
Что считаем результатом:
-
Появляется минимальный сквозной ML-сценарий:
Аутентификация пользователя/сервиса
Регистрация эксперимента/артефакта
Чтение/запись фичи
Выполнение вычисления/jobs
Видимость логов/метрик/трейсов
Есть rollback-сценарий для каждого ключевого ML-компонента.
Совместимые версии зафиксированы (chart/app/SDK/docker).
Как не превратить wave в бюрократию
Как обычно, любой хороший подход можно взять и превратить во что-то неповоротливое и бюрократизированное. И wave — не исключение.
Три правила, которым стоит следовать, чтобы этого не произошло:
В каждой волне держите небольшой набор обязательных smoke-тестов
Перед переходом к следующей волне фиксируйте минимальный DoD, достаточный для текущего этапа
Любой ручной hotfix в кластере сразу оформляйте в Git, иначе со временем теряется смысл Wave+GitOps
Если совсем упрощать — придерживайтесь «принципа достаточности». И всё.

Итог простыми словами
Если речь идёт не о production-кластере, то первый подход (сначала поднимаем весь стек, потом дорабатываем) вполне может быть оправдан. Потому что здесь куда дешевле поймать конфликты чартов/операторов/CRD и реальную топологию, а ещё нет давления prod-SLO и внешних пользователей.
Второй же подход в чистом виде на dev зачастую избыточен: не всегда есть смысл доводить каждый компонент до production-ready-состояния, достаточно уровня «команда может стабильно работать с сервисом».
Интеграционный риск всё равно остаётся: даже хорошо настроенные по отдельности компоненты могут неожиданно начать конфликтовать уже в составе общей системы.
На практике лучше всего у нас сработал подход Wave + GitOps: платформа развивается слоями, каждый слой даёт работающий результат, а изменения фиксируются в Git.
При этом в отдельных случаях всё равно приходится делать вертикальные срезы.
Например: если ML-инженерам нужен JupytherHub, задача меняется: важно определить минимальный набор зависимостей, который позволит начать работу уже сейчас. Именно баланс между «достаточно хорошо» и «не переусложнять раньше времени» оказался для нас самым практичным подходом.
Интересно, как подобные платформы разворачиваются у вас: через вертикальные срезы, полноценные платформенные волны или как-то иначе?
Буду рад, если поделитесь опытом и советами в комментариях.
Полезные ссылки
DORA — исследования и метрики поставки ПО; опора для мысли про «короткую обратную связь» и частые интеграции vs редкие крупные выкаты.
Accelerate (Forsgren, Humble, Kim) — книга, с которой исторически связана линия DORA; практики высокоэффективных команд доставки.
OpenGitOps — принципы GitOps (Git как источник правды, декларативность и т.д.); контекст к упоминаниям Flux и дисциплины изменений через репозиторий.
Flux — GitOps-контроллер для Kubernetes; соответствует стеку в статье (FluxCD).
CNCF Platform Engineering Maturity Model — зрелость платформы и поэтапное наращивание возможностей (волны, не «всё с первого дня»).
Google SRE Book — культура SRE и идея осмысленного Definition of Done (наблюдаемость, снижение рутины toil, runbook-подход к эксплуатации).
DolG
Спасибо уважаемый Кирилл за классную статью.
Очень интересно узнать,
1) как бизнес заказчик относиться к "мукам выбора" реализации.
2) а как у Вас запланировано только один продукт овнер на систему или будет деление внутри системы?
KulakovK Автор
Благодарю за отзыв.
1) Для бизнес заказчика, насколько мне известно, это прошло незаметно. В моей картине мира сложилось впечатление, что в целом все были готовы что первый этап построения будет начинаться с некоторого R&D.
2) Так как платформа планируется под широкий спектр финтех задач, думаю что продукт овнеров лучше сделать не одного на всю платформу, а больше по направлениям, так как их в банке много. И так как делать "комбайн" для всего - это плохая идея (как архитектурная, продуктовая так и для дальнейшего сопровождения), думаю будут из этой платформы отпачковываться свои отдельные микро платформы заточенные под определенную специфику. Это мое видение. А как будет - поглядим :-)
DolG
Спасибо за ответ.