Обновление модели по природе своей отличается от обновления обычного микросервиса ещё и тем, что «корректность» новой версии сложно проверить формальными тестами так же однозначно, как проверяется, например, что 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-инфраструктуры — вот с чем ещё стоит разобраться: