Настроить autoscaling для LLM сервера

ТЕХНИЧЕСКОЕ ЗАДАНИЕ: Настроить autoscaling для LLM сервера

1. Цель задачи

Научиться проектировать и внедрять автоматическое масштабирование (автоскейлинг) для сервера инференса большой языковой модели (LLM) на основе реальной нагрузки. Основная метрика для масштабирования — загрузка GPU (GPU utilization) и длина очереди запросов (queue length). Цель — обеспечить такое количество реплик сервера, которое минимизирует задержки при пиковой нагрузке и не создаёт избыточных затрат (оверпровижен) в периоды простоя.

Ключевой результат Настроенный и протестированный HPA (Horizontal Pod Autoscaler) в Kubernetes, который автоматически увеличивает/уменьшает количество подов LLM-сервера в зависимости от текущей загрузки GPU и глубины очереди, без ручного вмешательства и с экономией ресурсов.


2. Исходные данные

Что нужноОткуда взять
Kubernetes-кластер (minikube, kind, managed K8s)Локальный или облачный (например, GKE, EKS, AKS)
LLM-сервер в контейнере (vLLM, TGI, или собственный FastAPI)Docker image, готовый к деплою
GPU-ускоритель (NVIDIA)Node с GPU в кластере (минимум 1 GPU)
Prometheus для сбора метрикУстановить через Helm или оператор
Grafana для визуализацииУстановить через Helm
NVIDIA DCGM Exporter для метрик GPUHelm-чарт или DaemonSet
KEDA или custom-metrics-apiserver для работы с очередьюУстановить через Helm
Инструмент для генерации нагрузки (locust, hey, k6)pip install или бинарник

Если нет реального GPU-кластера — симулируем:

  1. Использовать инстанс с GPU в облаке (например, 1x T4 в GCP/GKE, или AWS g4dn.xlarge) или локальный компьютер с NVIDIA GPU.
  2. Если GPU нет — развернуть CPU-only LLM-сервер (малую модель, например, distilgpt2) и настроить HPA по queue length как основной метрике; GPU utilization в этом случае эмулировать не нужно, но в задании требуется именно GPU utilization — поэтому без GPU не обойтись. Рекомендуется взять облачный GPU за $0.5-1/час.
  3. Для очереди: LLM-сервер должен иметь endpoint /generate с внутренней очередью (например, vLLM уже имеет очередь запросов). Если нет — добавить Redis-очередь через sidecar контейнер.

3. Технологический стек

КомпонентИнструментыНазначение
ОркестрацияKubernetes (minikube / kind / managed)Деплой и управление подами
LLM серверvLLM / HuggingFace TGI / FastAPI + transformersИнференс LLM
МониторингPrometheus + GrafanaСбор и визуализация метрик
Метрики GPUNVIDIA DCGM ExporterGPU utilization, memory, temperature
Custom Metrics APIPrometheus Adapter / KEDAПреобразует Prometheus метрики в HPA
АвтоскейлингKubernetes HPA (горизонтальный)Автоматическое изменение числа подов
Генерация нагрузкиLocust / hey / k6Создание запросов к LLM-серверу
Инфраструктура as CodeHelm / YAML манифестыВоспроизводимость конфигурации

4. Этапы выполнения

Этап 1: Развёртывание LLM-сервера и базового мониторинга (2–3 часа)

Действия

  1. Установить Kubernetes (например, minikube --driver=nvidia или kind с GPU поддержкой).
  2. Установить NVIDIA GPU Operator через Helm:
    helm repo add nvidia https://nvidia.github.io/gpu-operator
    helm install gpu-operator nvidia/gpu-operator --create-namespace --namespace nvidia-gpu-operator
    
  3. Развернуть LLM-сервер (vLLM) через Deployment. Пример конфигурации:
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: llm-server
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: llm-server
      template:
        metadata:
          labels:
            app: llm-server
        spec:
          containers:
            - name: llm
              image: vllm/vllm-openai:latest
              args: ["--model", "mistralai/Mistral-7B-v0.1", "--port", "8000"]
              resources:
                limits:
                  nvidia.com/gpu: 1
              ports:
                - containerPort: 8000
    
  4. Установить Prometheus и Grafana через Helm (kube-prometheus-stack).
  5. Установить NVIDIA DCGM Exporter:
    helm install dcgm-exporter nvidia/dcgm-exporter --namespace monitoring
    
  6. Проверить, что метрики GPU (DCGM_FI_DEV_GPU_UTIL, DCGM_FI_DEV_MEM_COPY_UTIL) появляются в Prometheus.
  7. Развернуть Service для LLM-сервера и Ingress (опционально).

Ожидаемый результат этапа Работающий LLM-сервер в кластере, доступный по эндпоинту, и Prometheus собирает метрики GPU utilization.

Этап 2: Настройка метрики queue length (1–2 часа)

Действия

  1. Если LLM-сервер не экспортирует длину очереди, добавить sidecar-контейнер с Prometheus exporter, который мониторит очередь. Проще всего — использовать KEDA с Prometheus скалером, который может определять очередь из внешних источников (например, RabbitMQ, Redis).
    В задании предполагаем, что queue length — это количество ожидающих запросов в эндпоинте LLM. vLLM предоставляет метрику vllm:num_requests_waiting в /metrics.
  2. Включить эндпоинт /metrics у vLLM (по умолчанию включён на порту 8000).
  3. Проверить наличие метрики vllm:num_requests_waiting в Prometheus.
  4. (Альтернатива) Если нет встроенной очереди, развернуть Redis Queue (RQ) и создать консьюмер, который забирает задачи для LLM. Redis exporter предоставит метрику длины очереди.

Ожидаемый результат этапа Prometheus содержит метрику llm_queue_length (или аналогичную), которая изменяется при отправке запросов.

Этап 3: Настройка Prometheus Adapter и HPA (2–3 часа)

Действия

  1. Установить Prometheus Adapter для преобразования Prometheus метрик в Custom Metrics API:
    helm install prometheus-adapter prometheus-community/prometheus-adapter \
      --set prometheus.url=http://prometheus-operated.monitoring.svc:9090
    
  2. Настроить конфигурацию адаптера (values.yaml) для двух метрик:
    • gpu_utilization (average за 1 минуту по всем GPU)
    • queue_length (average за 1 минуту) Пример rules:
    rules:
      - seriesQuery: 'DCGM_FI_DEV_GPU_UTIL{gpu=""}'
        resources:
          template: ".*"
        name:
          matches: "DCGM_FI_DEV_GPU_UTIL"
          as: "gpu_utilization"
        metricsQuery: "avg(<<.Series>>) by (<<.GroupBy>>)"
      - seriesQuery: 'vllm:num_requests_waiting'
        resources:
          template: ".*"
        name:
          as: "queue_length"
        metricsQuery: "sum(<<.Series>>)"
    
  3. Проверить, что Custom Metrics API доступен:
    kubectl get --raw /apis/custom.metrics.k8s.io/v1beta1
    
  4. Создать HPA, который использует обе метрики:
    apiVersion: autoscaling/v2
    kind: HorizontalPodAutoscaler
    metadata:
      name: llm-server-hpa
    spec:
      scaleTargetRef:
        apiVersion: apps/v1
        kind: Deployment
        name: llm-server
      minReplicas: 1
      maxReplicas: 4
      metrics:
        - type: Object
          metric:
            name: gpu_utilization
          target:
            type: AverageValue
            averageValue: 70  # scale up if avg GPU util > 70%
        - type: Object
          metric:
            name: queue_length
          target:
            type: AverageValue
            averageValue: 5   # scale up if avg queue > 5 requests
    
  5. (Важно) Настроить behavior в HPA, чтобы избежать тревоги при кратковременных скачках:
    behavior:
      scaleDown:
        stabilizationWindowSeconds: 300
        policies:
          - type: Percent
            value: 50
            periodSeconds: 60
      scaleUp:
        stabilizationWindowSeconds: 60
        policies:
          - type: Percent
            value: 100
            periodSeconds: 60
    

Ожидаемый результат этапа HPA создан и отображается в kubectl get hpa. При отсутствии нагрузки replica count = 1.

Этап 4: Генерация нагрузки и проверка автоскейлинга (2–3 часа)

Действия

  1. Развернуть инструмент генерации нагрузки, например Locust:
    apiVersion: v1
    kind: Pod
    metadata:
      name: load-generator
    spec:
      containers:
        - name: locust
          image: locustio/locust
          command: ["locust", "-f", "/scripts/locustfile.py", "--host", "http://llm-server:8000"]
          volumeMounts:
            - mountPath: /scripts
              name: scripts
      volumes:
        - name: scripts
          configMap:
            name: locust-config
    
    (locustfile.py должен слать запросы с постепенным увеличением числа пользователей)
  2. Запустить нагрузку с шаблоном:
    • 0–10 мин: низкая нагрузка (1–2 concurrent users)
    • 10–20 мин: средняя нагрузка (10–15 users)
    • 20–30 мин: пиковая нагрузка (30–40 users)
    • 30–40 мин: спад до 0.
  3. Наблюдать в Grafana дашборд: GPU utilization, queue length, replica count.
  4. Убедиться, что HPA увеличивает реплики в течение 2-3 минут после превышения порогов, и снижает после стабилизации.
  5. Проверить overseas (overprovision): после спада нагрузки количество реплик должно вернуться к 1 в течение 5–10 минут (с учетом stabilizationWindow).
  6. Замерить время реакции HPA и среднюю задержку LLM-сервера во время пика (должна оставаться приемлемой, <10с).

Ожидаемый результат этапа Графики в Grafana показывают, что реплики растут только при высокой нагрузке и снижаются при её отсутствии. Нет лишних запущенных подов (overprovision).

Этап 5: Оптимизация и документирование (1–2 часа)

Действия

  1. Подобрать оптимальные пороговые значения (target average value) для GPU util и queue length, чтобы минимизировать время реакции и избежать частых скейлов (thrashing).
  2. Настроить HPA на использование averageUtilization вместо averageValue для GPU (если есть суммарная ёмкость):
    - type: Resource
      resource:
        name: nvidia.com/gpu
        target:
          type: Utilization
          averageUtilization: 70
    
    (требует установки метрики nvidia.com/gpu через DCGM)
  3. Задокументировать все шаги и конфиги в README репозитория.
  4. Написать Postmortem-подобный отчёт: какие параметры выбраны, почему, результаты тестов.

Ожидаемый результат этапа Финальная конфигурация HPA, дашборд Grafana с ключевыми метриками и документированный процесс.


5. Критерии приемки (Definition of Done)

  • LLM-сервер развёрнут в Kubernetes, доступен по Service.
  • Prometheus собирает метрики GPU utilization (DCGM_FI_DEV_GPU_UTIL) и queue length (vllm:num_requests_waiting).
  • Prometheus Adapter корректно преобразует обе метрики в Custom Metrics API.
  • HPA создан с minReplicas=1 и maxReplicas≥3, использует обе метрики.
  • При низкой нагрузке (0–5 concurrent users) количество реплик остаётся 1.
  • При высокой нагрузке (30+ concurrent users) количество реплик возрастает до maxReplicas.
  • После снижения нагрузки количество реплик возвращается к 1 не дольше чем через 7 минут.
  • В дашборде Grafana отображаются графики replica count, GPU utilization, queue length.
  • Не наблюдается оверпровижен: среднее количество реплик за период простоя ≤1.2.
  • Документация включает объяснение выбора порогов и анализ результатов теста.

6. Ожидаемый результат

Основной артефакт Архив или репозиторий, содержащий:

  • hpa.yaml — манифест HPA с настройками.
  • prometheus-adapter-values.yaml — конфигурация адаптера.
  • deployment-llm.yaml — манифест развёртывания LLM-сервера.
  • locustfile.py — скрипт для генерации нагрузки.
  • README.md — инструкция по развёртыванию, описание архитектуры и анализ результатов.

Дополнительно

  • Скриншоты Grafana с графиками нагрузки и реплик.
  • Лог выполнения теста (например, вывод kubectl get hpa -w).
  • Краткий отчёт (1-2 стр.) с выводами и предложениями по улучшению.

7. Возможные сложности и их решение

СложностьРешение
GPU недоступен в локальном кластереИспользовать облачный инстанс с GPU или эмулировать через device plugin (но не даст реальной нагрузки). Лучше взять managed K8s с GPU node pool.
Метрика queue_length не появляется в PrometheusПроверить, что LLM-сервер экспортирует /metrics и что ServiceMonitor настроен (или конфигурация scrape). Использовать kubectl port-forward на /metrics.
HPA не видит custom metricsПроверить, что Prometheus Adapter зарегистрирован в Custom Metrics API (kubectl get --raw /apis/custom.metrics.k8s.io/v1beta1). Проверить правильность метрик в rules.
Частые колебания реплик (thrashing)Увеличить stabilizationWindowSeconds для scaleDown, уменьшить scaleUp policies. Использовать averageUtilization вместо averageValue для GPU.
При пиковой нагрузке очередь растёт, но GPU utilisation низкий (например, из-за I/O)Скорректировать target по queue length, добавить метрику CPU или latency. Рассмотреть VPA для CPU.
Ограничение maxReplicas не хватает для пиковой нагрузкиУвеличить maxReplicas или использовать cluster autoscaler для добавления новых нод.

8. Бюджет времени (оценка)

ЭтапВремя
Этап 1: Развёртывание LLM-сервера и мониторинга2–3 ч
Этап 2: Настройка метрики queue length1–2 ч
Этап 3: Настройка Prometheus Adapter и HPA2–3 ч
Этап 4: Генерация нагрузки и проверка2–3 ч
Этап 5: Оптимизация и документирование1–2 ч
Итого10–12 ч

Примечание: Если выполняется впервые с нуля и незнакомы инструменты, время увеличится до 20 ч. Рекомендуется разбить на 2–3 дня.


9. Связанные вопросы из базы знаний

ВопросТема
12Как установить и настроить Prometheus в Kubernetes
34Как развернуть vLLM в кластере
45Основы метрик GPU и DCGM
78Создание Custom Metrics API с Prometheus Adapter
112Настройка HPA с несколькими метриками
156Поведенческие настройки HPA (stabilization window)
189Установка KEDA для event-driven autoscaling
223Избегание оверпровижена в автоскейлинге
287Генерация нагрузки с помощью Locust
340Бюджет времени и стоимость GPU инстансов

10. Чек-лист самопроверки

  • Я развернул LLM-сервер и убедился, что он отвечает на запросы.
  • Я настроил сбор метрик GPU utilization и queue length в Prometheus.
  • Я настроил Custom Metrics API и проверил, что kubectl get hpa показывает целевую метрику.
  • Я запустил генерацию нагрузки с разными уровнями и зафиксировал изменения реплик.
  • Я убедился, что после спада нагрузки реплики уменьшились до 1 и не происходит постоянного масштабирования вхолостую.