Современный Kubernetes приучил нас к тому, что инфраструктура должна быть эластичной. Для управления ресурсами традиционно мы можем использовать Horizontal Pod Autoscaler (HPA): растёт трафик — увеличивается количество подов, трафик падает — лишние поды удаляются. Но в высоконагруженных системах при быстром росте нагрузки стандартные инструменты масштабирования могут стать менее эффективными. Проблема заложена в самой природе реактивного подхода: система всегда догоняет реальность, а не формирует её. Решение лежит на поверхности: надо повышать осознанность и внедрять ИИ.

Меня зовут Дмитрий Чуринов, я руководитель команды разработки в трайбе Synapse. Хочу поделиться практическим опытом внедрения механизмов предиктивного масштабирования. Мой коллега Александр Козлов курирует разработку нескольких продуктов в СберТехе, в том числе Platform V SynAI, отвечающего за интерактивное управление трафиком на основе систем машинного обучения и моделирования.

Вместе мы разберём, как настроить предиктивное управление в Kubernetes на основе open source-компонентов, и почему нам этого не хватило.

Проблема №0: ноль не просто цифра

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

Почему мы не можем использовать HPA? Стандартный HPA в Kubernetes не умеет масштабироваться в ноль и обратно. Он работает на основе текущего потребления ресурсов (CPU/RAM). Если текущее количество подов — ноль, то и потребления ноль: метрик нет, и HPA не знает, что пора просыпаться. В итоге требуется держать минимум один под на всякий случай.

KEDA: оживляем мертвых

Чтобы решить проблему масштабирования из нуля, мы взяли KEDA (Kubernetes Event-driven Autoscaling). И вот почему:

  • Нативная работа с событиями. Когда подов нет, KEDA смотрит не на процессор, а на источник: очередь в RabbitMQ, количество сообщений в Kafka или наличие записей в базе данных.

  • Масштабирование 0 < → 1. Как только в очереди появляется сообщение, KEDA подсказывает Kubernetes, что надо поднимать первый под, а дальше в дело может вступить HPA.

  • Гибкость. Большое количество различных триггеров, которые позволяют гибко настроить масштабирование подов под любые нужды.

  • Активное сообщество. Если требуется дополнительный триггер, возникает ошибка или требуется помощь, то у KEDA есть большое сообщество, где достаточно быстро отвечают на вопросы.

Как создать триггер для масштабирования

Для этого в KEDA существует спецификация ScaledObject, которая описывает определение пользовательского ресурса ScaledObject. Он определяет триггеры и режимы масштабирования.

Пример для триггера Cron: фиксированное количество реплик (масштабирование до 10 реплик с 6 утра до 8 вечера и масштабирование до 0 реплик в противном случае).

apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
  name: cron-scaledobject
  namespace: default
spec:
  scaleTargetRef:
    name: my-deployment
  minReplicaCount: 0
  cooldownPeriod: 300
  triggers:
    - type: cron
      metadata:
        timezone: Asia/Kolkata
        start: 0 6 * * *
        end: 0 20 * * *

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

Холодный старт и задержка подготовки

Хоть KEDA реагирует быстрее HPA и решает проблему масштабирования из нуля, ей требуется время на запуск пода.

Допустим:

  1. 9:00:00 утра — резкий подъём трафика.

  2. KEDA это видит и даёт команду поднимать поды в 9:00:10. 

  3. На запуск подов требуется время.

  4. Только что запущенный под с приложением работает медленнее и требует больше ресурсов.

  5. Всё это время сервис лежит.

Поэтому нам нужно решение, которое сможет предсказать, что через 5 минут будет жарко и поднимать поды нужно уже сейчас.

Ищем пророка

Мы начали искать способ предсказать нагрузку:

  • Расписание (Cron): подходит, если пики нагрузки всегда выпадают на 9:00. Не сработает, если они зависят от поведения или внешних триггеров.

  • Линейная регрессия: не учитывает сезонность и праздники, неустойчива к мусору.

  • Prophet: библиотека для прогнозирования временных рядов на основе аддитивной модели.

Мы остановились на библиотеке с открытым исходным кодом Prophet, потому что она может работать с временными рядами, устойчива к отсутствию данных и сдвигам в тренде, и, как правило, хорошо справляется с отсутствующими значениями.

Как мы подружили математику с K8s

Мы реализовали схему с предиктивным триггером.

Схема работы:

  • Сбор данных: раз в 15 минут специальный сервис (назовём его Predictor) забирает из Prometheus историю запросов за последние 14 дней.

  • Обучение и прогноз: Prophet обрабатывает эти данные и выдаёт прогноз на следующий час с детализацией по минутам.

  • Экспорт метрики: прогноз записывается обратно в Prometheus как новая метрика http_requests_predicted.

  • KEDA: мы настраиваем Prometheus Scaler в KEDA так, чтобы он смотрел на http_requests_predicted.

Чего мы добились? Теперь, если Prophet предсказывает всплеск в 12:00, KEDA начинает поднимать дополнительные реплики уже в 11:55. К моменту наступления пика все поды подняты, прогреты и готовы к бою.

Пример конфигурации для Prophet:

kind: ConfigMap
apiVersion: v1
metadata:
  name: prem-adapter-config
data:
  metrics-config.yaml: |
    metrics:
      - name: "cpu1"
        query: "integration_cpu_load{}"
        recalculationInterval: "30s"
        lookbackWindow: "2h"
        prophet:
          freq: "10s" # Частота метрик
          periods: 200 # Количество периодов для предсказания
          timeout: "2d" # Время хранения метрик
          seasonality: # Отслеживаемые сезонности (В долях от суток).
            hourly: 24 # часовая сезонность
            ten_minutally: 144 # десятиминутная сезонность (В одном дне 1440 минут или 144 десятков минут)

Нехватка ресурсов

Здесь мы столкнулись с проблемой нехватки ресурсов. В поисках решения  обнаружили, что некоторые сервисы работают в разное время. Упростим и для примера рассмотрим сервис А (ночной) и сервис Б (дневной).

  • Сервис А (ночной): работает с 00:00 до 06:00. Ему нужно 50 процессорных ядер.

  • Сервис Б (дневной): работает с 06:05 до 20:00. Ему тоже нужно 50 процессорных ядер.

Если мы забронируем 100 процессорных ядер под оба сервиса, то будем транжирить ресурсы, потому что одновременно нам требуется всего 50 процессорных ядер.

Коммунизм в мире ресурсов

Решая проблему экономии ресурсов у себя в Platform V SynAI, мы пришли к концепции Project Capacity Policy (PCP) и благодаря этому можем переключать режимы и давать ресурсы в зависимости от потребности. Это позволило не держать резервы для каждого сервиса по отдельности, а создать общий пул и из него выделять мощности туда, где они нужны.

На рисунке изображена работа приложения днёми ночью — Profile Day и Profile Night: днём работает Service 1, ночью — Service 2. Без PCP мы должны были держать запас Reserve. С PCP нам не нужно закладывать дополнительные мощности, и мы можем пользоваться и перераспределять ресурсы между Service 1 и Service 2.

Вернёмся к нашим сервисам А и Б. В результате ночью мы отдаём 50 процессорных ядер сервису А, днём их снимаем и отдаём сервису Б.

Project Capacity Policy

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

PCP состоит из секции для указания источников данных Sources и секции для указания уровней использования UsageLevels.

Sources. Источники данных могут быть двух типов:

  • Utilization — прямой замер того, сколько сервис потребляет процессора или памяти;

  • Prometheus — любая кастомная метрика.

UsageLevels. Набор условий (conditions), привязанных к источникам данных. Источники описывают состояние проекта, как в примере с сервисом А и Б, «дневной режим» и «ночной режим».

Пример PCP:

kind: ProjectCapacityPolicy
metadata:
  name: policy1
spec:
  sources:
    - name: cpu_var
      type: utilization
      config:
        type: cpu
        limit: 10 # Лимит в 10 ядер для расчета процентов
    - name: http_requests
      type: prometheus
      config:
        query: 'avg(rate(http_requests{namespace="user-namespace"}[3m]))'
        limit: 1000 # Лимит в 1000 RPS для границ уровня
  usageLevels:
    - name: low
      conditions:
        - source: cpu_var
          lowerBound: 0%
          upperBound: 75%
        - source: http_requests
          lowerBound: 0
          upperBound: 750
  ...

И теперь можно доработать ScaledObject: он больше не будет находиться в вакууме и сможет связываться с UsageLevels. Когда PCP понимает, что наступил «дневной режим», он сообщает об этом всем ScaledObject и сервисам. В таком случае «дневной режим» получает команду о том, что «минимальные реплики выставлены на 50, работай». «Ночной режим» получает информацию «ты пока не нужен».

Недостатки использования open source-инструментов

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

  • Риски отказоустойчивости KEDA. Стандартная схема «Prophet → Prometheus → KEDA» создаёт слишком хрупкую цепочку передачи данных. Любой лаг при опросе источника или временная недоступность Prometheus может привести к замиранию всей системы масштабирования, а это слишком рискованно для критических процессов.

  • Безопасность. Требуются неограниченные права, нет защиты передаваемых данных и детального аудита изменений, внутри продуктов могут использоваться уязвимые библиотеки, что недопустимо в случае повышенных требований к безопасности.

  • Функциональная ограниченность. Стандартные триггеры KEDA изолированы и не учитывают лимиты всего проекта. Они не способны эффективно балансировать ресурсы между зависимыми сервисами в рамках общей квоты.

Эти ограничения привели нас к созданию Platform V SynAI. Мы оптимизировали математическое ядро, сделав его значительно легче и быстрее, внедрили механизмы отказоустойчивости.

Одной из ключевых фич продукта стал ProjectCapacityPolicy (PCP). Благодаря PCP SynAI обеспечивает бесшовную интеграцию между прогнозом и исполнением. В результате у нас получился надёжный, лёгкий и контролируемый бизнес-инструмент.

Заключение

Open source-инструменты KEDA и Prophet помогают перейти от реактивного масштабирования к проактивному для управления ресурсами в кластерах Kubetnetes. Для компаний, где нет критических требований по отказоустойчивости и безопасности, это рабочая схема. Но крупным заказчикам с подобными требованиями, по нашему опыту, больше подходят промышленные решения, позволяющие оптимизировать потребляемые ресурсы.  

Комментарии (0)