Добавить в корзинуПозвонить
Найти в Дзене
НИР-Центр

От франкенштейна костылей к единой AI-платформе: как мы объединили три сервиса в один K3s-кластер

Обычно в статьях про архитектуру авторы с умным видом показывают красивые схемы, рассказывая, как они с первого дня всё спроектировали по канонам чистого кода и микросервисов. Мы будем честны: наша архитектура родилась из боли, хаоса и горящих дедлайнов. Это история о том, как у нас исторически сложились три совершенно разных AI-сервиса, каждый из которых жил в своей параллельной вселенной. И о том, как одна безобидная просьба бизнеса заставила нас снести всё до основания, чтобы пересобрать этот «франкенштейн» в единую, монолитную по духу, но микросервисную по факту платформу на одном сервере с K3s и двумя NVIDIA H100. Своим опытом разработки и интеграции AI-сервисов поделился Богдан Александрук — специалист по обработке данных отдела разработки интеллектуальных систем. Усаживайтесь поудобнее, это будет одна большая история великого слияния. Всё началось с того, что ИИ стал модным. Бизнес приносил новые и новые идеи каждые пару месяцев. Так у нас родились три MVP: Поскольку делали их в
Оглавление

Обычно в статьях про архитектуру авторы с умным видом показывают красивые схемы, рассказывая, как они с первого дня всё спроектировали по канонам чистого кода и микросервисов.

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

Это история о том, как у нас исторически сложились три совершенно разных AI-сервиса, каждый из которых жил в своей параллельной вселенной. И о том, как одна безобидная просьба бизнеса заставила нас снести всё до основания, чтобы пересобрать этот «франкенштейн» в единую, монолитную по духу, но микросервисную по факту платформу на одном сервере с K3s и двумя NVIDIA H100.

Своим опытом разработки и интеграции AI-сервисов поделился Богдан Александрук — специалист по обработке данных отдела разработки интеллектуальных систем.

Усаживайтесь поудобнее, это будет одна большая история великого слияния.

Эпоха удельных княжеств

Всё началось с того, что ИИ стал модным. Бизнес приносил новые и новые идеи каждые пару месяцев. Так у нас родились три MVP:

  1. agent-kp — генератор коммерческих предложений.
  2. vks-api — транскрибатор созвонов (загружаешь аудио, получаешь протокол встречи).
  3. docpro — умный обработчик и анализатор документов.

Поскольку делали их в разное время (и иногда разные люди), каждый сервис представлял собой суверенное государство:

  • Хранение файлов: agent-kp хранил сгенерированные файлы прямо на локальном диске в Docker-контейнере (да, при рестарте пода всё превращалось в тыкву). vks-api складывал аудио в свой отдельный S3, а docpro вообще держал всё в памяти и базе данных.
  • Авторизация: В одном сервисе был хардкод-токен, во втором прикрутили базовый JWT, в третьем — Basic Auth.
  • Уведомления: agent-kp слал письма на почту, когда КП было готово. vks-api пытался держать WebSocket, который отваливался каждые пять минут.

Все жили счастливо, пока сервисами пользовались разные отделы. Но затем настал день X.

«Простая фича», сломавшая хребет архитектуре

К нам пришел продакт-менеджер и сказал:

«Ребята, у нас же всё готово! Давайте сделаем так: менеджер загружает запись встречи с клиентом в vks-api, мы делаем транскрипцию, и на основе этого текста agent-kp АВТОМАТИЧЕСКИ генерирует коммерческое предложение и присылает уведомление!»

Звучит логично? Для бизнеса — да. Для нас это означало катастрофу.

Чтобы реализовать этот флоу, vks-api должен был как-то авторизоваться в agent-kp, скачать свой файл из S3, перегнать его по HTTP в agent-kp, чтобы тот сохранил его на свой локальный диск. А потом agent-kp должен был как-то уведомить фронтенд vks-api, что всё готово.

Мы попытались написать скрипт-интеграцию (point-to-point). Получился спагетти-монстр. Сервисы начали дергать API друг друга, падать по таймаутам, токены протухали, а если в процессе падала сеть — генерация КП зависала навсегда, но GPU при этом продолжала молотить на 100%, сжигая ресурсы.

Стало ясно: у нас нет трёх сервисов. У нас есть три кучи легаси, которые дерутся за две карточки H100. Нам нужна была Платформа.

Великое слияние (Хирургия наживую)

Мы остановили продуктовую разработку и начали резать. Концепция была простой: бизнес-сервисы должны стать «глупыми». Они не должны знать, как хранить файлы, как проверять токены и как слать уведомления. Всё это должно стать общим ядром — platform-core.

Шаг 1: Убиваем локальные диски (Workspace Service)

Первым делом мы вырвали с корнем работу с файлами из всех трёх сервисов.

Мы создали Workspace Service — единую точку работы с документами (поверх нормального MinIO и PostgreSQL).

Теперь, когда vks-api загружает аудио, он просто отдаёт его в ядро по gRPC и получает document_id (UUID).

Когда нужно сгенерировать КП, он больше не пересылает мегабайты текста по HTTP. Он просто отправляет в agent-kp сообщение: "Сделай КП на основе документа uuid-1234". agent-kp сам идёт по gRPC в Workspace Service и забирает нужные данные.

Файлы перестали дублироваться. Исчезли проблемы с дисками. Появились единые рабочие пространства (воркспейсы) для пользователей.

Шаг 2: Единая шина событий вместо франкенштейна нотификаций

Помните, как каждый сервис слал уведомления по-своему? Мы снесли всё это и написали единый Event Service.

Мы заставили все бизнес-сервисы общаться с миром через один унифицированный gRPC-клиент (lazy singleton, чтобы не падать при рестартах).

Теперь, когда agent-kp заканчивает работу, его код выглядит так:

# Один вызов, который делает всё
await EventClient.get().emit(
user_id=user_id,
action="workflow_complete",
message=f"КП «{doc_name}» сформировано",
category="result" # ⬅ Вся магия здесь
)

Смотрите, что происходит под капотом, когда ядро видит category="result":

  1. Оно сохраняет лог в PostgreSQL для аналитики.
  2. Оно АВТОМАТИЧЕСКИ генерирует WebSocket-ивент agent-kp.completed.
  3. Оно пушит этот ивент в единый Redis Pub/Sub.
  4. Единственный Notification Service ловит его и зажигает красный бейдж 🔴 в UI пользователя.

Больше никаких отваливающихся сокетов в каждом MVP. Один сокет на всю платформу.

Шаг 3: Выгоняем авторизацию за дверь кластера

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

Мы снесли хардкод-токены и JWT-валидаторы из бизнес-кода. Поставили на вход Traefik и OAuth2-Proxy.

Теперь разработчику docpro вообще без разницы на авторизацию. Если запрос дошел до его FastAPI-ручки, значит, Traefik уже проверил cookie, сходил в Keycloak, убедился, что права есть, и проксировал запрос, добавив безопасный заголовок:

X-Company-User-Id: ivan.petrov

Внутри кластера сервисы ходят друг к другу по внутреннему DNS K8s вообще без токенов. Доверенный периметр.

Шаг 4: Разнимаем драку за GPU (Temporal)

Когда все сервисы объединились и начали активно делиться файлами, они стали запускать AI-задачи одновременно. И наши H100 предсказуемо захлебнулись по памяти (CUDA Out of Memory).

Чтобы они не передрались, мы ввели диктатуру — оркестратор Temporal.

Мы запретили vks-api и agent-kp напрямую стучаться в vLLM. Теперь они просто кладут задачи в единую gpu-queue. Temporal Worker, зная физические лимиты видеокарты, берет задачи строго по очереди.

Да, пользователю иногда приходится подождать лишнюю минуту. Но зато сервер работает со 100% стабильностью, а если внутри инференса случается сбой, Temporal молча перезапускает Activity, и пользователь даже не замечает, что что-то шло не так.

Прекрасный лебедь (Итоги)

Процесс слияния был болезненным. Пришлось переписать сотни строк кода, сбросить старые базы и перевести пользователей на новый единый UI (который, к слову, собирается динамически через наш App Registry, опрашивающий K8s-аннотации).

Но результат стоил каждой пролитой капли пота. Что мы имеем сейчас:

  1. Из трёх сервисов получился один организм. Это больше не разрозненные утилиты. Менеджер может загрузить договор в воркспейс, docpro вытащит из него суть, а agent-kp нажмет на эту суть и сделает коммерческое. Всё это — через единый интерфейс, с единой авторизацией и единым хранилищем.
  2. Скорость разработки взлетела в космос. Когда бизнесу понадобился четвертый AI-сервис, мы написали его за 2 дня. Разработчику нужно было написать только промпт и бизнес-логику. Файлы лежат в ядре, квоты считает ядро, уведомления шлет ядро.
  3. Стабильность H100. Благодаря Temporal мы выжимаем из наших 160 ГБ видеопамяти максимум, не боясь поймать OOM.

Иногда, чтобы сделать шаг вперед, нужно признать, что твои MVP превратились в костыльного монстра, взять топор и снести их до фундамента. Зато теперь наш кластер на K3s — это настоящая enterprise-платформа, которая готова к любым фантазиям бизнеса.

Если у вас есть вопросы по тому, как мы выносили файлы в единый Workspace, настраивали связку Traefik + OAuth2-Proxy или мирили сервисы в очередях Temporal — задавайте в комментариях, будем рады обсудить!