Деплой LLM на Kubernetes: где ломается прод при нагрузке
Кластер спокойно держит демонстрационную нагрузку и рассыпается на первом же всплеске: вызовы копируются в очередь запросов к модели, время ответа растёт нелинейно, часть обращений отваливается по превышению ожидания. Причина обычно не в самой модели — при деплое LLM на Kubernetes вскрывается нехватка ресурсов, которых в контуре либо нет, либо они уже заняты другими задачами.
Ускорители — дефицитный и неделимый ресурс. Если устройства сосредоточены на одном узле, обучение, дообучение и вывод конкурируют за них: планировщик держит реплики сервиса в ожидании, очередь растёт, а деградация времени отклика проявляется уже при умеренном трафике. Масштабирование здесь не спасает — добавить реплики без свободных ускорителей нельзя.
Отдельная точка отказа — конфликт обновлений контура. Выкатка новой версии модели и сопутствующих сервисов идёт в тот же кластер, что и рабочий трафик, и на время прогрева пропускная способность падает. Без изоляции сред тестовые и продуктовые нагрузки делят одни и те же устройства, поэтому эксперимент одного инженера становится инцидентом для клиента.
Все эти сбои предсказуемы, и каждый закрывается конфигурацией, а не героизмом дежурного: раздельные пулы узлов, приоритеты очередей, стратегия выката с прогревом и быстрым откатом. Дальше разбираем, как это закрывается на практике — от планирования ёмкости до регламента обновлений.
Что даёт управляемый инференс под пиковой нагрузкой
Управляемый инференс — это когда реплики модели живут на GPU-узлах под управлением оркестратора, а трафик приходит через единую точку входа. Планировщик сам находит свободные ускорители, поднимает новую реплику рядом с той, что уже отдаёт ответы, и снимает её, когда очередь пустеет. Развёртывание перестаёт быть ручной операцией на конкретной машине и становится декларативным описанием желаемого состояния.
Стабильное время отклика держится не средним значением, а хвостом распределения. Масштабирование идёт по глубине очереди и занятости ускорителя, а не по загрузке процессора: реплика добавляется до того, как запросы начнут ждать в буфере. Поэтому 95-й перцентиль остаётся в заданном коридоре и на всплеске, и в спокойные часы.
Доля успешных запросов растёт потому, что отказ перестаёт быть тотальным. Пробы готовности не пускают трафик в ещё не прогретую реплику, нездоровые экземпляры выводятся из балансировки, а таймауты и повторы настроены так, чтобы клиент получил ответ, а не ошибку.
Автоматический выкат версии модели идёт канареечным сценарием: новая сборка получает малую долю трафика, метрики качества и задержек сравниваются с текущей, и только затем эта доля растёт. Откат — возврат на предыдущий образ, без пересборки окружения и ручных правок.
Обратная сторона той же механики — снижение ручных операций эксплуатации. Ночные перезапуски, ручное добавление узлов под нагрузку и правка конфигов уходят в декларативные политики, а инженер занимается моделью, а не дежурством.
Варианты развёртывания моделей: сравнение режимов
Режим развёртывания задаёт, как кластер делит ускорители между моделями и как система ведёт себя на пике. Выбор зависит от числа моделей, требований к изоляции нагрузок и предсказуемости трафика: переезд между вариантами после запуска обходится больнее, чем сразу подобрать подходящий.
Сравнение режимов
| Вариант | Когда подходит | Ограничения |
|---|---|---|
| Одиночный узел с ускорителями | Одна-две модели, внутренний контур, обкатка гипотезы | Изоляции нагрузок нет: тяжёлый запрос к одной модели просаживает соседнюю; масштабирование только вверх, потолок — ресурс узла |
| Разделяемый пул ускорителей | Несколько моделей с разным профилем нагрузки и общим пиком по времени | Нужны квоты, приоритеты вытеснения и лимиты на модель; шумный сосед остаётся риском, отказ одного узла задевает всех |
| Выделенная группа под модель | Продовые модели с жёсткими требованиями к отклику и чувствительными данными | На низкой нагрузке ускорители простаивают; дороже в обслуживании, зато отказ локализован одной моделью |
| Модель, разнесённая по нескольким узлам | Вес модели не помещается в один узел или нужен запас по одновременным запросам | Обмен между узлами добавляет задержку; отказоустойчивость и обновления сложнее планировать |
На практике начинают с разделяемого пула: он даёт гибкость и позволяет измерить реальный профиль запросов. Выделенную группу заводят под одну-две модели, где цена простоя выше стоимости простаивающих ускорителей, а изоляцию нагрузок подкрепляют отдельными пространствами имён, квотами и приоритетами. Ограничения масштабирования стоит зафиксировать заранее — по какому признаку нагрузка переезжает в отдельную группу и что происходит при отказе узла.
Этапы внедрения: от аудита контура до передачи
Развёртывание модели в кластер — это не установка одного сервиса, а последовательность, где каждый шаг опирается на предыдущий. Если пропустить аудит и сразу перейти к настройке, схема разъедется на первом же изменении профиля нагрузки: очереди растут, ускорители простаивают, а откат собирают под давлением. Ниже — порядок работ, который мы повторяем на каждом проекте.
- Аудит контура вывода моделей — инвентаризация: какие модели и веса используются, где они работают сейчас, профиль запросов, ограничения по памяти ускорителей, список потребителей. На выходе — карта текущего состояния и перечень узких мест.
- Проектирование схемы развёртывания — топология пулов узлов, распределение реплик, квоты, автомасштабирование, изоляция окружений. Результат — конфигурация, воспроизводимая из репозитория, а не собранная руками.
- Настройка и базовая проверка — манифесты, кэш весов, лимиты ресурсов, сетевые политики, сбор метрик и логов. Убеждаемся, что сервис отвечает и корректно переживает перезапуск.
- Проверка под нагрузкой — профиль, близкий к боевому: параллельные запросы, длинные контексты, пиковые всплески, отказ отдельного узла. Смотрим задержку первого токена и пропускную способность, фиксируем порог деградации.
- План отката версии — как вернуть предыдущий образ модели и конфигурацию одной операцией, проверено на стенде. Без отработанного отката релиз не выпускаем.
- Передача в эксплуатацию — доступы, регламенты, алерты, документация, обучение дежурной смены. Дальше систему ведёт команда сопровождения.
Этапы идут строго по порядку: пропущенный аудит возвращается переделкой схемы, а непроверенный откат — простоем в самый неудобный момент.
Что остаётся у вас: конфигурации, схемы, регламенты
Всё, что мы настраиваем при деплое, остаётся в вашем репозитории и кластере — без привязки к нашей команде. Передаём не «папку с файлами», а рабочий контур: ваш инженер поднимает стенд с нуля по документации, не задавая нам вопросов.
- Манифесты развёртывания. Развёртывания, сервисы, точки входа, квоты на GPU-узлы, автомасштабирование по длине очереди запросов. Применение на чистом кластере воспроизводит окружение один в один.
- Схема отказоустойчивости. Размещение реплик по зонам, лимиты ресурсов, пороги проб готовности, поведение при падении узла. Видно, какая деградация допустима и что происходит с очередью.
- Регламент обновления модели. Канареечная доля трафика, метрики сравнения качества ответов, критерии отката, точка невозврата. Обновление перестаёт быть остановкой сервиса.
- Политики доступа команд. Роли в кластере, разделение пространств имён, доступы к секретам с ключами, лимиты ресурсов на команду.
- Документация по эксплуатации. Схема архитектуры, точки отказа, типовые инциденты и порядок действий, метрики и логи, ориентиры при деградации.
- Скрипты сборки образов. Воспроизводимая сборка, тегирование, проверки перед выкатом.
Дальше вы ведёте сервис сами: меняете модель, добавляете команды, масштабируете пул узлов. Мы остаёмся на связи по новым задачам, но не как единственные, кто умеет трогать конфигурацию.
Кейс: стабильный инференс при пиковой нагрузке
Заказчик — сервис генерации текстов на потоке обращений. Средняя нагрузка держалась ровно, но в утренние часы число запросов в минуту вырастало втрое, и ломалась именно эта картина: реплики инференса не успевали прогреваться до включения в балансировку, часть обращений закрывалась ошибкой, а выкат новой версии модели останавливал приём трафика на всё время пересборки окружения.
Метрики сняли перед вмешательством и через месяц после:
| Показатель | До | После |
|---|---|---|
| Доля отказов запросов в пиковые окна | 11% | 0,4% |
| Время отклика на 95-м процентиле | 4,8 с | 1,3 с |
| Время выката новой версии модели | 40 мин с остановкой приёма | 5 мин без остановки |
| Готовность реплики после старта | 6–9 мин | 25–40 с |
Изменения касались трёх вещей: прогрева, распределения и выката. Прогретый вес модели теперь лежит в образе, новая реплика не попадает в балансировку, пока не готова принимать запросы, а версия выезжает на часть трафика с постепенным расширением доли.
При росте очереди планировщик поднимает дополнительные реплики, при спаде — снимает их, поэтому постоянный резерв под пик не держится, а парк ускорителей NVIDIA не вырос. Откат на предыдущую версию — одна команда, трафик возвращается за минуты, и это отдельно проверили на учебном отказе.
Чем отличается деплой инференса от дообучения модели?
Это два разных контура с противоположным профилем нагрузки, и смешивать их в одной среде — источник постоянных сбоев. Деплой инференса обслуживает запросы: трафик приходит неравномерно, ответ нужен мгновенно, простой виден пользователю сразу. Контур дообучения модели работает пакетно: длинные прогоны, полная загрузка ускорителей, и остановка на середине обходится в потерянные сутки расчёта.
Разделение ресурсов диктует топологию. Инференсу нужен гарантированный резерв ускорителей и запас памяти под очередь запросов — иначе на пике пойдут отказы. Дообучению выгоднее забирать свободное железо целиком и отпускать его по завершении. Поэтому мы разводим контуры по отдельным пулам узлов и по расписанию: обучение стартует в окно низкой нагрузки и не отнимает производительность у обслуживания.
Частота обновлений тоже разная. Веса меняются раз в несколько недель, а обвязка вокруг них — маршрутизация, лимиты, кэш, метрики — обновляется постоянно; в контуре дообучения цикл свой: эксперимент, проверка качества, выкладка новой версии.
Требования к отказам зеркальны. Инференсу нужны плавная деградация и быстрый откат на предыдущую версию: несколько копий, пробы живости, снятие трафика с нездорового узла. Дообучению важнее сохранность прогресса — регулярные контрольные точки, чтобы падение узла стоило перезапуска последнего отрезка, а не всего прогона.
Оба контура мы поднимаем на Kubernetes как на общей платформе, но с разными политиками: отдельные пулы, свои правила масштабирования, разные приоритеты планировщика. Такое разделение снимает главный класс отказов — когда обучение вытесняет обслуживание и продукт ложится целиком.
Технические сомнения перед запуском инференса
Перед запуском модели в кластер команды задают почти одни и те же вопросы — и правильно делают: ответы на них определяют, как сервис поведёт себя при обновлении и в аварии. Ниже — короткие ответы по существу, без общих слов.
Как понять, что узкое место — не ускоритель?
Смотрим цепочку целиком: очередь запросов, размер пакета, подготовка входных данных, передача между сервисами. Часто ускоритель простаивает, а время уходит на предобработку и ожидание в очереди. Если доля простоя устойчиво велика, оптимизировать нужно не вычисления.
Как обновить модель без простоя?
Новая версия поднимается рядом со старой и получает сначала небольшую долю трафика — сравниваем качество ответов и задержку на живых запросах. Веса хранятся как отдельный артефакт с версией, поэтому откат — это переключение трафика обратно, а не повторная сборка. Логи и ответы всегда привязаны к версии модели.
Как дать командам доступ к контуру?
Через пространства имён и роли: разработчик видит логи и метрики своего сервиса, но не управляет кластером и чужими нагрузками. Учётные данные — только в штатном хранилище секретов, не в конфигурациях и не в переписке. Доступ выдаётся роли, а не человеку — при смене состава команды ничего не нужно отзывать вручную.
Что происходит при отказе ускорителя?
Узел выводится из балансировки, а его нагрузка переезжает на свободные. Если резерва нет, запросы встают в очередь с ограничением по времени — сервис замедляется, но не отвечает ошибкой. Одновременно срабатывает план деградации: сокращаем параллелизм и длину входных данных, а критичные сценарии обслуживаются компактной моделью.
Уровни деградации, их пороги и порядок переключения мы описываем заранее и проверяем на учениях — тогда в аварии решение принимает автоматика, а не дежурный.
Обсудим задачу: что прислать для оценки контура
Чтобы вернуться с оценкой и планом работ, а не с общими соображениями, нам нужен короткий срез по вашему контуру: по нему видно, где узкие места, сколько ускорителей реально потребуется и какие решения придётся принять до первой выкатки. Пришлите то, что есть — часть вводных часто отсутствует, и это нормально, доберём вместе на созвоне.
- Модель и формат весов: открытая или дообученная, размер, допустимая точность (полная или квантованная), нужен ли доступ к приватным весам и откуда их забирать.
- Профиль нагрузки: запросов в секунду в пике и в среднем, длина контекста, доля потоковой отдачи ответа, есть ли длинные пакетные задачи отдельно от интерактива.
- Текущий контур развёртывания: кластер и его сеть, наличие узлов с ускорителями, чем управляете ресурсами, где хранятся веса и как собираются образы.
- Требования к доступности: целевая доля успешных запросов, допустимое время простоя при обновлении, нужен ли прогрев перед вводом нового экземпляра в работу.
- Доступы и границы: кто владеет кластером, какие внешние сервисы должны быть доступны, есть ли требования к изоляции запросов между командами.
- Наблюдаемость: что уже собирается — загрузка ускорителей, очередь запросов, задержки, логи — и куда это складывается.
- План развития: рост нагрузки на горизонте, появление новых моделей, необходимость тонкой настройки под свои данные.
Этого достаточно, чтобы предложить схему размещения, политику обновлений и откатов, а также план работ с этапами и критериями приёмки. Опишите задачу в удобном виде — вернёмся с оценкой и планом.







