Как мы собрали LLM-шлюз для России: готовый LiteLLM на data-plane, свой биллинг на Go и n8n. Go.. Go. llm.. Go. llm. Open source.. Go. llm. Open source. penAI.. Go. llm. Open source. penAI. Микросервисы.. Go. llm. Open source. penAI. Микросервисы. Платежные системы.. Go. llm. Open source. penAI. Микросервисы. Платежные системы. Проектирование API.
Как мы собрали LLM-шлюз для России: готовый LiteLLM на data-plane, свой биллинг на Go и n8n - 1

Год назад доступ к зарубежным LLM из России превратился в квест. OpenAI и Anthropic не принимают российские карты и блокируют запросы по гео. Обходные пути — VPN, иностранная карта, прокладка через знакомых за рубежом — годятся для пет-проекта, но не для продакшена, где нужен стабильный аккаунт, предсказуемый счёт и возможность объяснить бухгалтерии, за что платим.

Мы сделали apiglue — шлюз, который закрывает эту боль: один OpenAI-совместимый endpoint, за которым прячутся десятки провайдеров, оплата рублёвой картой и баланс в рублях. А заодно — управляемый хостинг n8n, чтобы не только дёргать модели через API, но и собирать на них автоматизации без своего сервера.

Ниже — как это устроено внутри и какие решения оказались неочевидными. Без маркетинга: где замерили — пишу замеры, где спроектировали на запас — так и говорю.

Почему не стали писать шлюз с нуля

Первый инстинкт инженера — написать свой прокси. Маршрутизация, пулы ключей, ретраи, учёт токенов, стриминг — задача понятная. Мы даже начали: в репозитории до сих пор лежит gateway/ на Go с тестами под -race. Но довольно быстро стало ясно, что мы переписываем то, что уже есть в опенсорсе и обкатано на чужом проде.

Этим «уже есть» оказался LiteLLM — прокси на Python с поддержкой сотни с лишним провайдеров, виртуальными ключами, бюджетами, учётом расходов и метриками для Prometheus. Он умеет то, на что у нас ушли бы месяцы: единый формат запроса для OpenAI, Anthropic, Google, DeepSeek, OpenRouter и прочих, балансировку внутри пула ключей, cooldown при rate-limit и автоматический failover.

Отсюда выросла вся архитектура: LiteLLM остаётся как есть на data-plane, а control-plane на Go мы пишем сверху. Собственный gateway оставили как референс и запасной вариант, но горячий путь запросов через него не идёт.

Гибридная архитектура: кто за что отвечает

Получилось два чётко разделённых слоя.

Гибридная архитектура: data-plane (LiteLLM) и control-plane (Go)

Архитектура
Архитектура

Data-plane (LiteLLM) обрабатывает весь трафик /v1/*: chat-completions, embeddings, стриминг. Внутри — пул провайдерских ключей, балансировка, failover, учёт расхода в долларах. Control-plane в этот путь не вмешивается, поэтому латентность шлюза не зависит от того, чем заняты кабинет и биллинг.

Control-plane (Go, в основном stdlib) — это всё остальное: регистрация и сессии, ключи пользователя, рублёвый баланс, пополнение через ЮKassa, кабинет, оркестрация n8n. LiteLLM он дёргает через admin-API: заводит виртуальные ключи (/key/generate), добавляет модели (/model/new), забирает расход (/spend/logs).

Почему это удобно: слои масштабируются независимо. LiteLLM-реплики делят состояние через Redis, control-plane stateless. И если завтра захочется заменить LiteLLM на что-то другое — контракт между слоями узкий и понятный.

Nocode Cloud: n8n в один клик

Вторая половина продукта — управляемый хостинг n8n. Пользователь в кабинете нажимает «создать сервер», выбирает тариф (basic — от 1900 ₽/мес) и через несколько минут получает свой изолированный n8n на поддомене вида n8n-true-carp.nc.домен. Дальше — рекуррентное списание с того же рублёвого баланса.

Под капотом — Kubernetes. Каждый инстанс — это Deployment + PVC + Service + Ingress в собственном namespace, а раскладывает всё это наш Go-оркестратор через client-go. На k8s мы пошли осознанно: планировщик, рестарты упавших Pod’ов, масштабирование и сетевые политики кластер берёт на себя — нам не нужно городить это самим поверх голого демона.

Reconcile-петля вместо императивных вызовов. Оркестратор не делает apply прямо из обработчиков API. Control-plane пишет в Postgres желаемое состояние инстанса, а orchestrator в цикле сверяет его с фактическим состоянием кластера (через client-go, Server-Side Apply) и приводит одно к другому — создаёт манифесты, масштабирует Deployment в ноль и обратно, удаляет. По сути это маленький контроллер поверх собственного стейта в БД. Отсюда восстановляемость (оркестратор упал и поднялся — досверит из Postgres) и устойчивость к гонкам (несколько реплик под leader-lock не подерутся за один инстанс), а сам Kubernetes ещё и держит Pod’ы живыми, пока мы спим.

Изоляция. Каждому инстансу — свой namespace, NetworkPolicy с запретом на чужой трафик и свой PersistentVolumeClaim. Инстансы не видят друг друга по сети и не делят данные. Секрет N8N_ENCRYPTION_KEY шифруется AES-256-GCM и лежит в нашей БД только в виде шифротекста; в кластер он кладётся как Secret и расшифровывается лишь в момент применения манифеста, чтобы попасть в env Pod’а.

Шлюз доступа до онбординга. Свежий n8n до первого /setup открыт — кто первый зашёл, тот и завёл админа. Чтобы посторонний не «угнал» чужой инстанс по угаданному URL, перед инстансами стоит access-proxy с ext-authz (Ingress дергает его до того, как пустить запрос на Pod): до завершения setup он пускает только владельца. Владелец опознаётся по HMAC-токену в cookie, который проверяется субзапросом в control-plane. Встроить проверку в сам образ n8n нельзя — он неизменяемый, поэтому логика живёт в прокси.

Что дальше

Ближайшее — встроенная observability LLM-трафика на базе Langfuse: каждый пользователь видит свои запросы, токены, стоимость и латентность без единой строчки кода у себя, потому что трафик и так идёт через виртуальный ключ. Спека готова, реализация на очереди.

Попробовать

apiglue запущен, идёт набор пользователей. Заводите аккаунт на apiglue.ru, пополняете баланс рублёвой картой, меняете в своём коде base URL и ключ — и дальше работаете с моделями как с обычным OpenAI API. Нужна автоматизация — поднимаете n8n в пару кликов там же.

Вопросы по архитектуре и решениям — задавайте в комментариях, отвечаем.

Автор: codementor

Источник