Обновление моделей без даунтайма: blue-green для inference-сервиса
Обновление модели по природе своей отличается от обновления обычного микросервиса ещё и тем, что «корректность» новой версии сложно проверить формальными тестами так же однозначно, как проверяется, например, что API возвращает правильный код статуса. Качество ответов LLM субъективно и контекстно-зависимо, поэтому весь процесс blue-green для моделей требует не только технической инфраструктуры переключения трафика, но и продуманного набора критериев «новая версия достаточно хороша, чтобы стать боевой».
Рано или поздно наступает момент, когда модель на srv-ai01 нужно обновить — вышла новая версия Qwen с лучшим качеством кода, или квантованная версия Llama стала работать быстрее, или просто накопились патчи безопасности для самого инференс-сервера. Проблема в том, что «просто перезапустить сервис с новой моделью» означает недоступность AI-ассистента для всей компании на те несколько минут, пока модель заново загружается в память GPU — а загрузка модели на 70 миллиардов параметров это не пять секунд. Blue-green деплой решает эту проблему, позволяя переключаться между версиями без единой секунды даунтайма.
Идея blue-green применительно к inference-сервису
Классический blue-green деплой держит два одинаковых окружения — «синее» (текущее, боевое) и «зелёное» (новое, тестируемое) — и переключает трафик между ними одной командой, как только зелёное готово. Для LLM-инференса это выглядит чуть тяжеловеснее из-за GPU-памяти: если на srv-ai01 одна видеокарта, оба окружения одновременно на ней не поместятся. Поэтому у подхода есть два практических варианта:
- Полный blue-green — два инференс-сервера на разных GPU или разных серверах, между которыми переключает трафик балансировщик. Требует запаса по железу, зато самый надёжный.
- Последовательный blue-green с прогревом — новая модель загружается на ту же GPU параллельно со старой (если хватает VRAM для двух копий в момент переключения) или на соседний сервер в кластере, трафик переключается только после прохождения health-check, старая модель выгружается уже после переключения.
Шаг 1. Конфигурация двух инстансов vLLM
Предположим, у нас vLLM как inference-сервер и текущая версия — Qwen2.5-Coder-32B, а обновляем на Qwen2.5-Coder-32B-Instruct-v2 с более свежими весами.
# blue: текущий боевой инстанс на порту 8000
vllm serve /models/qwen2.5-coder-32b-v1 \
--port 8000 \
--gpu-memory-utilization 0.45 \
--served-model-name qwen2.5-coder-32b
# green: новый инстанс на порту 8001, на той же машине,
# с ограничением памяти, чтобы уместились оба
vllm serve /models/qwen2.5-coder-32b-v2 \
--port 8001 \
--gpu-memory-utilization 0.45 \
--served-model-name qwen2.5-coder-32b
Если VRAM не хватает на одновременный запуск двух версий 32B-модели, разумно держать green-инстанс на соседнем сервере кластера или временно выгрузить менее приоритетную модель (например, модель для генерации изображений) на время выкатки.
Шаг 2. Балансировщик и health-check
nginx или LiteLLM Proxy перед моделями отвечает за переключение трафика. Настраиваем upstream с двумя бэкендами и держим весь трафик на blue, пока green не прошёл проверку:
# /etc/nginx/conf.d/llm-upstream.conf
upstream llm_backend {
server 127.0.0.1:8000 weight=100; # blue — активен
server 127.0.0.1:8001 weight=0 backup; # green — пока не участвует
}
server {
listen 8080;
location /v1/ {
proxy_pass http://llm_backend;
proxy_read_timeout 300s;
}
}
Перед переключением обязательно гоняем green через набор проверочных запросов — не только «модель отвечает 200», а именно проверку качества на реальных примерах из продакшена:
#!/bin/bash
# health_check_green.sh
RESPONSES_OK=0
TOTAL=10
for i in $(seq 1 $TOTAL); do
RESULT=$(curl -s -X POST http://127.0.0.1:8001/v1/chat/completions \
-H "Content-Type: application/json" \
-d @test_prompts/prompt_$i.json | jq -r '.choices[0].message.content')
if [ -n "$RESULT" ]; then
RESPONSES_OK=$((RESPONSES_OK+1))
fi
done
echo "OK: $RESPONSES_OK/$TOTAL"
if [ "$RESPONSES_OK" -lt "$TOTAL" ]; then
echo "Green не прошёл проверку, откат"
exit 1
fi
Шаг 3. Постепенное переключение трафика
Вместо резкого переключения 100% трафика с blue на green разумно переводить нагрузку постепенно, наблюдая за метриками latency и error rate:
# шаг 1: 10% трафика на green
upstream llm_backend {
server 127.0.0.1:8000 weight=90;
server 127.0.0.1:8001 weight=10;
}
# шаг 2: после 15 минут без аномалий — 50/50
upstream llm_backend {
server 127.0.0.1:8000 weight=50;
server 127.0.0.1:8001 weight=50;
}
# шаг 3: полное переключение
upstream llm_backend {
server 127.0.0.1:8000 weight=0 backup;
server 127.0.0.1:8001 weight=100;
}
После применения каждой конфигурации — nginx -s reload, который не разрывает уже установленные соединения. На каждом шаге стоит следить за дашбордом latency, error rate и, если есть возможность, за качественной обратной связью от пользователей (например, кнопкой «плохой ответ» в интерфейсе Open WebUI).
Шаг 4. Откат при проблемах
Главное преимущество blue-green — мгновенный откат. Если после переключения на green метрики поползли вниз, возврат на blue — это изменение весов upstream и reload nginx, без переустановки чего бы то ни было:
upstream llm_backend {
server 127.0.0.1:8000 weight=100; # откат на blue
server 127.0.0.1:8001 weight=0 backup;
}
# nginx -s reload — откат занимает секунды
Старый blue-инстанс стоит держать запущенным ещё сутки-двое после полного переключения на green именно ради такой возможности мгновенного отката, и только потом освобождать под него ресурсы.
Автоматизация процесса через CI/CD-пайплайн
Ручное переключение весов удобно на старте, но при регулярных обновлениях моделей имеет смысл описать весь процесс blue-green как пайплайн — например, в GitLab CI или простом shell-скрипте, который вызывается по расписанию или вручную с одной командой.
#!/bin/bash
# deploy_green.sh — оркестрация blue-green деплоя модели на srv-ai01
set -e
NEW_MODEL_PATH=$1
GREEN_PORT=8001
echo "Запуск green-инстанса с моделью $NEW_MODEL_PATH"
vllm serve "$NEW_MODEL_PATH" --port $GREEN_PORT \
--gpu-memory-utilization 0.45 &
GREEN_PID=$!
sleep 60 # ждём загрузки весов в память
echo "Прогон health-check"
if ! ./health_check_green.sh; then
echo "Green не прошёл проверку, останавливаем"
kill $GREEN_PID
exit 1
fi
echo "Постепенное переключение трафика"
for weight in 10 50 100; do
./set_nginx_weights.sh --blue $((100-weight)) --green $weight
echo "Green получает ${weight}% трафика, ждём 15 минут"
sleep 900
if ! ./check_error_rate.sh; then
echo "Error rate вырос, откат на blue"
./set_nginx_weights.sh --blue 100 --green 0
exit 1
fi
done
echo "Деплой завершён успешно"
Такой скрипт делает процесс воспроизводимым и убирает человеческий фактор из самого рискованного момента — постепенного увеличения трафика на новую версию. Важно, чтобы каждый шаг скрипта имел явное условие отката, а не просто продолжал выполнение при любых обстоятельствах.
Особенности переключения для моделей с разным форматом промптов
Отдельная ловушка при blue-green деплое LLM, которой нет в обычном веб-сервисе: новая версия модели может ожидать другой формат системного промпта или chat template. Например, переход с Llama на Qwen или между мажорными версиями одной модельной линейки иногда меняет структуру специальных токенов диалога. Если это не учесть, green-инстанс формально пройдёт health-check по доступности, но будет выдавать деградировавшие по качеству ответы из-за несовместимого промпта.
# проверка chat template перед переключением
python3 -c "
from transformers import AutoTokenizer
tok = AutoTokenizer.from_pretrained('/models/qwen2.5-coder-32b-v2')
print(tok.apply_chat_template(
[{'role': 'system', 'content': 'test'}, {'role': 'user', 'content': 'hi'}],
tokenize=False
))
"
Сверяй вывод такой проверки с тем форматом, который использует твой прокси-слой или Open WebUI при формировании запроса — расхождение здесь одна из самых частых причин «тихой» деградации качества после, казалось бы, успешного переключения.
Коммуникация с пользователями во время обновления
Технически безупречный blue-green деплой всё равно может вызвать вопросы у пользователей, если новая модель отвечает чуть иначе, чем они привыкли — другой стиль, немного другая длина ответов. Стоит заранее предупредить активных пользователей о планируемом обновлении и о том, что можно ожидать изменений в поведении ассистента, особенно если обновление меняет модель на другую линейку, а не просто на новую версию весов той же модели.
# пример уведомления в Open WebUI перед плановым обновлением
ANNOUNCEMENT_BANNER=25 августа с 14:00 до 15:00 обновляем модель
ассистента для улучшения качества ответов. Возможны кратковременные
изменения в стиле ответов. Если заметите проблемы - пишите в
#ai-support.
Это особенно важно для команд, которые встроили промпты, заточенные под особенности конкретной версии модели, — например, ожидающие ответ в строго определённом формате. После обновления стоит явно попросить такие команды перепроверить свои сценарии использования на новой версии.
Частые ошибки и как их избежать
- Переключение сразу на 100% без постепенного наращивания трафика. Проблемы с качеством новой модели, которые не поймал синтетический health-check, обнаружатся сразу у всех пользователей. Всегда веди трафик поэтапно: 10% → 50% → 100%.
- Health-check проверяет только доступность, а не качество ответов. Новая версия модели может отвечать 200 OK, но выдавать заметно худшие по качеству ответы из-за неудачного квантования или регресса весов. Включай в проверку набор реальных промптов с ожидаемым уровнем качества.
- Старый инстанс выключают сразу после переключения. Без возможности мгновенного отката любая проблема, обнаруженная через час после релиза, превращается в полноценный инцидент с даунтаймом. Держи blue живым минимум сутки после полного перехода на green.
- Пользователей не предупреждают о смене модели. Даже технически безупречное переключение может выглядеть как поломка, если ответы ассистента внезапно изменили стиль без объяснения. Заранее анонсируй плановые обновления в канале поддержки.
Blue-green на уровне Kubernetes, если инфраструктура уже там
Если инференс на srv-ai01 упакован в Kubernetes (например, через KServe или собственные Deployment с vLLM), blue-green реализуется через два Deployment с одним Service, переключение между которыми происходит сменой selector — принципиально та же идея, что и с nginx upstream, но на уровне оркестратора.
apiVersion: apps/v1
kind: Deployment
metadata:
name: llm-green
labels:
app: llm-inference
version: green
spec:
replicas: 1
selector:
matchLabels:
app: llm-inference
version: green
template:
metadata:
labels:
app: llm-inference
version: green
spec:
containers:
- name: vllm
image: vllm/vllm-openai:latest
args: ["--model", "/models/qwen2.5-coder-32b-v2"]
resources:
limits:
nvidia.com/gpu: 1
# переключение Service на green одной командой
kubectl patch service llm-inference-svc \
-p '{"spec":{"selector":{"version":"green"}}}'
Преимущество Kubernetes-варианта в том, что readiness-проб и rolling update уже встроены в платформу, и большую часть логики health-check и постепенного переключения можно делегировать стандартным примитивам вроде readinessProbe вместо написания собственных bash-скриптов.
Что дальше
Безопасность и эксплуатация AI-инфраструктуры — вот с чем ещё стоит разобраться: