🐳 Розділ 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» — вони увійдуть у конфлікт і нескінченний шторм перезапусків.

Пов’язані питання