🐳 Раздел 14 · Вопрос #14

Как происходит масштабирование в Kubernetes

Масштабирование в Kubernetes — это динамическое изменение вычислительных мощностей для обработки меняющегося объёма пользовательской нагрузки.


🟢 Junior Level

30-секундный ответ

Масштабирование в Kubernetes — это динамическое изменение вычислительных мощностей для обработки меняющегося объёма пользовательской нагрузки.

Масштабирование происходит на трёх независимых уровнях:

  1. Горизонтальное масштабирование подов (Horizontal Scaling — HPA): добавление или удаление копий (реплик) подов приложения (например, было 2 пода, стало 10). Это основной и самый надёжный способ масштабирования в микросервисной архитектуре.
  2. Вертикальное масштабирование подов (Vertical Scaling — VPA): динамическое увеличение или уменьшение ресурсов CPU и RAM, выделенных конкретному контейнеру.
  3. Масштабирование кластера (Cluster Autoscaler / Karpenter): автоматический заказ и добавление новых физических серверов (нод) в кластер, если для новых подов не хватает места.

Наглядная аналогия из жизни

Представьте кассовую зону в супермаркете:

  • Горизонтальное масштабирование (HPA): открываются новые соседние кассы с дополнительными кассирами (больше реплик).
  • Вертикальное масштабирование (VPA): кассиру заменяют старый компьютер на супермощный моноблок и ставят скоростной сканер штрихкодов (больше CPU/RAM на процесс).
  • Масштабирование кластера (Cluster Autoscaler): если в супермаркете кончилось место для установки новых кассовых столов, администрация сносит стену и арендует соседнее помещение (добавление новых нод).

Ручное vs Автоматическое масштабирование

  • Ручное (Imperative / Declarative):
    kubectl scale deployment order-service --replicas=5
    
  • Автоматическое (HPA):
    # Автомасштабирование от 2 до 10 реплик при загрузке CPU > 60%
    kubectl autoscale deployment order-service --cpu-percent=60 --min=2 --max=10
    

🟡 Middle Level

3-уровневая таксономия автомасштабирования в K8s

                      +─────────────────────────────────────────+
                      |               МАСШТАБИРОВАНИЕ           |
                      +────────────────────┬────────────────────+
                                           │
         ┌─────────────────────────────────┼─────────────────────────────────┐
         ▼                                 ▼                                 ▼
   [ Уровень 1: HPA ]                [ Уровень 2: VPA ]              [ Уровень 3: Cluster ]
   Горизонтально                     Вертикально                     Пул серверов
   Больше копий Pods                 Больше CPU/RAM на Pod           Больше Worker Nodes
   (HorizontalPodAutoscaler)         (VerticalPodAutoscaler)         (Cluster Autoscaler/Karpenter)

Как HPA рассчитывает количество реплик (Математическая модель)

Контроллер HPA каждые 15 секунд (параметр horizontal-pod-autoscaler-sync-period) опрашивает Metrics API и вычисляет желаемое число реплик по строгой формуле:

\[\mathbf{DesiredReplicas} = \left\lceil \mathbf{CurrentReplicas} \times \left( \frac{\mathbf{CurrentMetricValue}}{\mathbf{TargetMetricValue}} \right) \right\rceil\]

Пример расчёта:

  • Текущее число подов: 2.
  • Целевой порог загрузки CPU (target): 50%.
  • Фактическая текущая загрузка CPU: 80%.
  • Решение HPA: $\lceil 2 \times (80 / 50) \rceil = \lceil 3.2 \rceil =$ 4 реплики.

[!IMPORTANT] HPA не работает без resources.requests! Процент утилизации CPU рассчитывается строго относительно значения requests.cpu. Если реквесты не заданы в манифесте пода, Metrics Server не может вычислить процент, и HPA переходит в состояние ошибки ScalingActive=False.


Предотвращение осцилляции (Flapping / Flapping Effect)

При резких всплесках трафика возможен эффект «дребезга»: нагрузка выросла на 5 секунд $\to$ HPA добавил 10 подов $\to$ нагрузка спала $\to$ HPA удалил 8 подов $\to$ JVM не успела прогреться $\to$ оставшиеся 2 пода захлебнулись. Для стабилизации используется блок behavior:

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: order-service-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: order-service
  minReplicas: 2
  maxReplicas: 10
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 60
  behavior:
    scaleUp:
      stabilizationWindowSeconds: 0 # Мгновенная реакция на пик нагрузки
      policies:
      - type: Percent
        value: 100 # Удвоить поды за 15 секунд
        periodSeconds: 15
    scaleDown:
      stabilizationWindowSeconds: 300 # Ждать 5 минут спада перед удалением подов!
      policies:
      - type: Pods
        value: 1 # Удалять не более 1 пода в минуту
        periodSeconds: 60

🔴 Senior Level

Почему масштабирование Java по Memory — катастрофический антипаттерн

На собеседованиях Senior-уровня частый вопрос:

«Почему опасно настраивать HPA для Spring Boot приложения по метрике type: Resource, name: memory?»

Глубокое HotSpot объяснение:

  1. Поведение аллокатора памяти JVM: при запуске JVM выделяет память из пула операционной системы до границы -Xmx (или MaxRAMPercentage).
  2. Память не возвращается мгновенно: после того как сборщик мусора (G1 или ZGC) очистил объекты в Young/Old Generation, выделенные виртуальные страницы памяти ядра Linux (RSS — Resident Set Size) НЕ возвращаются обратно операционной системе немедленно.
  3. Ловушка для HPA: метрика контейнера container_memory_working_set_bytes видит занятую память около 80–90%, даже если реальная нагрузка на сервис нулевая.
  4. Каскадное масштабирование: HPA считает, что приложению не хватает памяти, и начинает бесконечно наращивать поды до границы maxReplicas. Кластер переполняется раздутыми подами, тратя тысячи долларов впустую. Правило: Java-сервисы масштабируют строго по CPU utilization либо по Custom/External метрикам (RPS, Latency, Kafka Consumer Lag).

KEDA: Event-Driven Autoscaling и Scale-to-Zero

Стандартный HPA умеет опрашивать только внутренние метрики кластера и не умеет масштабировать от 0 до 1 (Scale-to-Zero). KEDA (Kubernetes Event-driven Autoscaling) расширяет возможности Kubernetes:

  • Масштабирует поды на основе внешних очередей: Kafka Lag, RabbitMQ Queue Length, AWS SQS, Redis Lists.
  • Умеет опускать реплики до 0, если в очереди нет сообщений, и мгновенно поднимать 1 под при появлении первого сообщения в топике.
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
  name: order-kafka-consumer-scaler
spec:
  scaleTargetRef:
    name: order-consumer-deployment
  minReplicaCount: 0 # Поддержка Scale to Zero!
  maxReplicaCount: 30
  triggers:
  - type: kafka
    metadata:
      bootstrapServers: kafka:9092
      consumerGroup: order-processors
      topic: new-orders
      lagThreshold: "50" # На каждые 50 непрочитанных сообщений добавлять 1 под!

Масштабирование узлов: Karpenter vs Cluster Autoscaler

| Характеристика | Classical Cluster Autoscaler | AWS Karpenter (Next-Gen) | | :— | :— | :— | | Принцип работы | Управляет облачными Node Groups (ASG) | Напрямую заказывает виртуальные машины через Cloud API | | Скорость реакции | 3–6 минут на добавление ноды | 30–45 секунд | | Подбор инстансов | Шаблонный (добавляет инстансы фиксированного типа) | Just-in-Time Bin-Packing: выбирает оптимальный CPU/RAM под текущие поды | | Консолидация (Дефрагментация) | Примитивное удаление пустых нод | Активная миграция подов и замена дорогих нод на дешевые |


In-Place Pod Resize (Kubernetes 1.27+ / 1.29 beta)

Исторически изменение ресурсов через VPA требовало принудительного перезапуска и выселения пода (RestartPolicy). Фича InPlacePodVerticalScaling позволяет изменять CPU и RAM контейнера на лету без перезапуска JVM-процесса через динамическое обновление cgroups.procs ядра Linux, исключая разрыв соединений.


4 Tricky Questions

1. Что произойдёт, если на один и тот же Deployment одновременно повесить HPA (по CPU) и VPA (в автоматическом режиме UpdateMode: "Auto")?

Ответ: Возникнет разрушительный конфликт двух управляющих контуров (Feedback Loop / Flapping Conflict):

  • При резком росте нагрузки на приложение загрузка CPU вырастает до 90%.
  • HPA решит, что нужно увеличить количество реплик (например, с 2 до 6).
  • VPA параллельно решит, что подам не хватает ресурсов, принудительно перезапустит текущие поды и выделит им больше ядер CPU (с 1 до 4 ядер).
  • Из-за увеличения ядер CPU относительный процент загрузки мгновенно падает. HPA реагирует на это и начинает уменьшать реплики обратно до 2.
  • Система входит в нестабильный осцилляционный штопор с постоянными перезапусками подов. Правило: VPA и HPA никогда не настраиваются одновременно по одним и тем же метрикам. Допустимо сочетать HPA по Custom Metrics (RPS/Kafka Lag) с VPA по памяти.

2. Почему при масштабировании приложений на Java Spring Boot с помощью HPA часто наблюдается резкий всплеск ошибок 503 и задержек (Latency Spikes) в первые 30 секунд после старта новых реплик?

Ответ: Проблема вызвана особенностями прогрева виртуальной машины Java (JVM Warmup):

  1. Новые поды только что запустились, код выполняется в режиме интерпретатора, JIT-компилятор C1/C2 ещё не оптимизировал байт-код в нативный машинный код, а пулы соединений HikariCP и кэши пустые.
  2. Как только readinessProbe отвечает успехом, Kubernetes мгновенно начинает балансировать на новый «холодный» под полную треть или четверть общего трафика.
  3. Непрогретый под испытывает 100% нагрузку на CPU на компиляцию методов, запросы выстраиваются в очередь сокетов Tomcat, превышают таймауты и падают с ошибками. Решение:
    • Использование Spring Boot 3 AOT / GraalVM Native Image (старт за 50 мс без прогрева).
    • Настройка readinessProbe с прогревочным endpoint’ом.
    • Внедрение Service Mesh (Envoy / Istio) с функцией Slow Start Window (постепенное линейное наращивание веса трафика на новый под в течение 60 секунд).

3. Как HPA ведёт себя, если один из подов завис или находится в нездоровом состоянии (Unready) во время расчёта метрик?

Ответ: Алгоритм HPA содержит защитный механизм от искажения статистики:

  • Если под находится в статусе Unready или его метрика CPU не может быть собрана (например, под только запускается), HPA полностью исключает этот под из расчёта среднего значения утилизации.
  • Если бы HPA учитывал холодные поды с нулевым CPU, средняя загрузка искусственно занижалась бы, мешая необходимому масштабированию.
  • Если все поды не отдают метрики, HPA сохраняет текущее количество реплик и отказывается масштабировать систему вверх или вниз до восстановления сбора метрик.

4. В чём разница между метриками Resource, Pods, Object и External в спецификации autoscaling/v2?

Ответ:

  • Resource: стандартные ресурсы контейнера, собираемые Metrics Server из cgroups (строго cpu или memory). Вычисляются как среднее использование (averageValue) или процент от реквестов (averageUtilization).
  • Pods: метрики самого пода, агрегированные по всем подам в таргете (например, среднее количество HTTP RPS на один под через Prometheus Adapter).
  • Object: метрика стороннего объекта Kubernetes внутри того же namespace (например, количество активных соединений на контроллере Ingress).
  • External: метрика внешнего сервиса, физически не представленного в кластере K8s (например, глубина очереди AWS SQS, длина очереди сообщений RabbitMQ или метрика в Datadog).

🎯 Шпаргалка для интервью

Главное о масштабировании

  • HPA: изменяет число реплик подов (HorizontalPodAutoscaler); основной инструмент в K8s.
  • VPA: изменяет CPU/RAM одного пода (VerticalPodAutoscaler).
  • Cluster Autoscaler / Karpenter: добавляет/удаляет рабочие ноды (серверы).
  • Обязательное условие HPA: наличие Metrics Server и блока resources.requests в манифесте пода.
  • Java Golden Rule: никогда не масштабировать Java по памяти (Memory). Масштабировать строго по CPU или RPS/Kafka Lag.
  • Защита от колебаний: настраивать behavior.scaleDown.stabilizationWindowSeconds (300 секунд).
  • KEDA: автомасштабирование по событиям (Kafka Lag, RabbitMQ) с поддержкой Scale to Zero ($0 \to 1$).

Сравнение Karpenter и Cluster Autoscaler

| Параметр | Cluster Autoscaler | Karpenter | | :— | :— | :— | | Архитектура | Через AWS AutoScaling Groups | Напрямую через Cloud API | | Скорость добавления ноды | 3–5 минут | 30–45 секунд | | Подбор ресурсов | Статический тип ВМ | Динамический под размер подов |

Красные флаги на собеседовании (Чего говорить нельзя)

  • ❌ «Я настраиваю HPA для Java-сервиса по памяти, чтобы при 80% RAM поднимались поды» — JVM не отдает память ОС, HPA моментально улетит в maxReplicas навсегда.
  • ❌ «HPA может работать без указания requests в Deployment» — без requests процент утилизации посчитать невозможно.
  • ❌ «HPA и VPA в автоматическом режиме отлично работают вместе по CPU» — они войдут в конфликт и бесконечный шторм перезапусков.

Связанные вопросы