Перевёз ИИ-агентов на российский сервер. Оказалось, полмира с ним разговаривать не хочет

от автора

Полгода мои агенты на Claude жили на зарубежном PaaS (Railway): пуш в репозиторий, автосборка, работает. В июле я перевёз всё на московский VPS. Сам переезд занял день. Разгребание последствий — неделю.

Ниже — что именно отваливается, как я это диагностировал и чем чинил. Часть граблей описана в интернете разрозненно, часть я не нашёл нигде и выяснял логами.

Главный вывод, к которому я пришёл не сразу, стоит вынести в начало: переезд в Россию — это не «сменить хостинг». Это обнаружить, что половина внешнего мира общается с вашим сервером односторонне. Вы к ним ходите — пожалуйста. Они к вам — нет. И всё, что построено на «внешний сервис постучится к нам», приходится переворачивать.

Зачем вообще переезжать

Два повода, оба скучные и практические.

Первый — персональные данные. Мои агенты разговаривают с клиентами компаний: отвечают на вопросы, считают стоимость, собирают заявки. В диалог неизбежно попадают имя, телефон, почта. Как только это уходит в модель, начинается обработка персональных данных со всеми вытекающими. Хранить их надо в России.

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

Задача формулировалась так: агенты работают в России, персональные данные не покидают страну, модель при этом остаётся внешней и никуда не денется.

Часть 1. Мелочи, которые ломаются сразу

Docker Hub отдаёт 429

Первая же сборка на новом сервере упала. Анонимные пуллы образов Docker Hub с российских адресов упираются в rate-limit — не «медленно», а просто отказ.

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

Дамп базы не забрать с нового сервера

Переносил базы со старой платформы. Логика простая: подключиться с нового сервера к публичному прокси старой базы и снять дамп. Не вышло — соединение просто виснет, без ошибки, как и запросы к модели (об этом ниже).

Пришлось снимать дамп с машины, у которой доступ есть, и заливать на сервер через пайп по ssh.

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

Свежий российский IP не знают geoip-базы

Хостер выдал новый адрес. Формально российский, фактически — его ещё нет в базах геолокации, которыми пользуются VPN-клиенты и правила маршрутизации.

Следствие: правило «российское — напрямую, остальное — через туннель» этот адрес не опознаёт и гонит трафик не туда. Сайт то открывается, то нет, в зависимости от того, с какого устройства смотришь.

Лечится явным правилом по домену. И важная деталь: после добавления правила надо сбросить DNS-кэш устройства, иначе оно продолжает держать старый маршрут и вы решите, что правило не сработало. Я на этом потерял вечер, добавляя правило второй и третий раз.

Часть 2. Главная стена

Дальше начинается то, ради чего я пишу эту статью.

Модель недоступна с российского адреса

Агент — это по большей части прокси к языковой модели. У меня это Claude, и первый же запрос с нового сервера вернул 403. Не «неверный ключ», не таймаут — отказ по стране, мгновенный, ещё до проверки авторизации.

Здесь есть диагностический приём, который экономит время. Смотрите, каким кодом вам отказывают. Если приходит 401 с жалобой на отсутствующий ключ — вас пустили, дело в конфигурации. Если 403 без упоминания ключа — вас отвергли раньше, по адресу. Разница принципиальная: в первом случае чините конфиг, во втором — чинить нечего, надо менять точку выхода.

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

Из полезного и безопасного: менять код приложения почти наверняка не придётся. Стандартные HTTP-клиенты уважают переменные окружения для прокси, так что вопрос решается на уровне окружения контейнера. Исключение — библиотеки, которые эти переменные игнорируют (мне попались две, обе про Telegram); там пришлось передавать настройку явно при создании клиента.

Вебхуки Telegram не доходят до российского сервера

Самое неочевидное и самое дорогое по времени.

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

Первая мысль была — я где-то ошибся: путь, сертификат, секрет. Проверил трижды, всё верно. Тогда я сделал то, что стоило сделать сразу — эксперимент с контролем:

  • поднял тот же самый обработчик на сервере за пределами страны, зарегистрировал вебхук туда → обновления приходят, 200;

  • вернул на московский → не приходит ни одного, таймаут на стороне отправителя;

  • прогнал внешними сервисами проверки доступности со всего мира → мой сервер открывается отовсюду нормально.

Вывод: сервер доступен всем, кроме отправителя. Трафик дата-центров Telegram к российским адресам фильтруется на входе.

Решение — перевернуть направление. Вместо вебхуков (они стучатся к вам) — длинный опрос (вы стучитесь к ним). Исходящие соединения никто не режет.

Грабли, которые встретились по дороге:

  • Два опрашивающих процесса на один токен дают конфликт. Пока старый экземпляр на прежней платформе жив, новый получает ошибку. Гасить старое надо до, а не после.

  • Опрос и вебхук взаимоисключающи. Если платформа автоматически передеплоит старую версию, она заново зарегистрирует вебхук и молча сломает вам опрос. Я на это напоролся дважды, пока не отвязал автодеплой совсем.

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

То же самое, но с картинками

Это случилось на днях и стало последним кусочком мозаики.

В Telegram обновился формат постов — появились карусели, длинный форматированный текст. Я переписал свой публикатор под новый API и упёрся: часть картинок не подтягивается. Ошибка «медиа не найдено». Причём одни картинки проходят, другие нет — при том, что файлы побайтово однотипные, лежат в одной папке и отдаются одним и тем же веб-сервером.

Что я проверил и отмёл, прежде чем понял:

  • формат, вес и параметры файлов — идентичны у рабочих и нерабочих;

  • блокировка ботов на веб-сервере — её нет, конфиг чистый;

  • user-agent — сервер отдаёт 200 всем, включая пустой и подставной;

  • цепочка сертификатов — полная и валидная;

  • повторные попытки — десять штук за минуту, без изменений;

  • обход кэша уникальным адресом — не помогает;

  • «прогрев» через отправку ссылки, чтобы сервис сам скачал картинку для превью — не помогает.

Разгадка нашлась в логах веб-сервера. За одними картинками загрузчик приходил и получал 200. За другими не приходил ни разу. Это не ошибка загрузки. Это отсутствие попытки.

Чтобы исключить версию «кривой хостер», я повторил эксперимент с контролем — одна и та же картинка, уникальные адреса, три источника подряд:

Откуда отдаём

Первая попытка

Вторая

Российский хостинг, провайдер А

не забрал

не забрал

Российский хостинг, провайдер Б (другая сеть, другой регион)

не забрал

не забрал

Заграничное хранилище

забрал

забрал

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

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

Решение: отдавать медиа не со своего сервера, а с внешнего хранилища. У меня это оказался публичный репозиторий — картинки и так лежат в git, ссылка на файл в нём работает как обычный URL, и до него загрузчик дотягивается с первой попытки.

Часть 3. Ради чего всё затевалось: персональные данные

Тут главная мысль, которую легко упустить: сервер в России — это половина решения. Данные всё равно уходят в модель, а модель живёт снаружи. Значит, до модели они должны перестать быть персональными.

Как это устроено у меня

Перед отправкой в модель текст проходит маскировку: телефоны, адреса почты, имена, номера документов заменяются плейсхолдерами вида [PHONE], [NAME]. Реальные значения остаются на сервере в словаре соответствия — только в памяти обработки одного запроса.

Модель отвечает, оперируя плейсхолдерами. Перед показом пользователю ответ разворачивается обратно. Клиент видит своё имя и свой номер, модель их не видела.

Контакт для заявки я вытаскиваю локально, разбором текста на своей стороне, а не прошу модель «найди телефон в диалоге». Так надёжнее и дешевле: телефон — это структура, для неё не нужен интеллект.

Что оказалось неочевидным

Порядок замен важнее самих замен. Номера документов, записанные слитно, надо маскировать раньше телефонов. Иначе маска телефона срабатывает на фрагменте документа, съедает часть цифр, а хвост остаётся в тексте — и в модель уходит кусок того, что вы старались скрыть. Общее правило: сначала длинные и специфичные форматы, потом короткие и общие.

Маска должна быть обратимой ровно один раз. Если в одном сообщении два разных телефона, а плейсхолдер общий, обратно вы развернёте не то. Нумерация плейсхолдеров решает это, но её надо не забыть.

Не всё, что похоже на данные, ими является. Мой первый вариант жадно маскировал всё, что выглядит как последовательность цифр. В результате агент, который считает стоимость по размерам, начал получать на вход [NUMBER] на [NUMBER] метров — и, разумеется, перестал считать. Размеры, суммы, даты, артикулы должны оставаться нетронутыми. Это ограничивает жадность масок сильнее, чем кажется на старте.

Проверять надо круговым тестом. Берёте фразу с данными, маскируете, разворачиваете обратно — и сравниваете с исходником побайтово. Если не совпало, где-то маска съела лишнее. Этот тест поймал у меня три ошибки, которых я не видел глазами.

С голосом сложнее, и у меня это пока не закрыто. В расшифровке речи телефон приходит словами: «восемь девятьсот пять…». Никакая цифровая маска его не поймает. Нужна нормализация числительных до маскировки, и это отдельная задача, которую я честно ещё не сделал.

Что даёт такая схема

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

Это не юридическая консультация, и я не юрист. Но архитектурно связка «хранение внутри + обезличивание на выходе» выглядит куда спокойнее, чем «пусть модель видит всё, зато у нас есть галочка согласия».

Часть 4. Что в итоге получилось, кроме соответствия закону

Задержка упала. Неожиданный бонус. Голосовой агент использует Яндекс SpeechKit для распознавания речи. Когда сервер стоял за океаном, каждая реплика делала два трансатлантических рейса: пользователь → сервер → распознавание → сервер. Теперь и сервер, и распознавание в одной стране, разговор стал заметно живее. Если у вас связка «зарубежный хостинг + российский сервис», посчитайте, сколько вы платите километрами.

Расходы стали предсказуемыми. Фиксированная стоимость сервера против почасовой оплаты, где счёт зависит от трафика и настроения планировщика.

Зависимость от чужих решений уменьшилась. Не исчезла — модель по-прежнему внешняя, — но точек отказа стало меньше.

Чек-лист, если собираетесь переезжать

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

  1. Ходят ли ваши внешние API на российские адреса? Проверяется одним curl с любого российского сервера. Смотрите на код отказа: 401 — вас пустили, 403 — нет.

  2. Есть ли у вас что-то, что работает через вебхуки или обратные вызовы? Всё это придётся переворачивать на опрос.

  3. Забирает ли кто-то файлы с вашего сервера по ссылке? Проверьте заранее, доходят ли до вас за ними.

  4. Собирается ли ваш образ с российского адреса? Если тянете публичные образы — заранее настройте зеркала.

  5. Что вы отправляете в модель? Если там персональные данные — маскировку лучше написать до переезда, а не после.

Мораль простая. Исходящие соединения работают почти всегда. Входящие — как повезёт. Проектируйте так, чтобы инициатором связи были вы.


Я делаю ИИ-агентов для малого бизнеса под брендом «Первый ИИ». Разборы вроде этого — про грабли, на которые наступаю по дороге, — пишу в телеграм-канале @pervyyii.

ссылка на оригинал статьи https://habr.com/ru/articles/1060430/