Урок по треку Docker и Kubernetes 8 мин

HPA, StatefulSet и тома за 8 минут

HPA и метрики, StatefulSet, PV, PVC, StorageClass, PodDisruptionBudget и размещение подов.

Этот модуль про две вещи: как кластер подстраивает число подов под нагрузку и как в эфемерном мире подов хранить данные, которые должны пережить пересоздание.

HPA: горизонтальное автомасштабирование

HorizontalPodAutoscaler меняет replicas у Deployment или StatefulSet по метрикам.

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: api
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: api
  minReplicas: 3
  maxReplicas: 20
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 70

Формула: желаемое = ceil(текущее × текущая_метрика / целевая). При 4 подах и средней загрузке CPU 140% от requests при цели 70% получится 8 подов.

Важное:

  • Utilization считается от requests, а не от лимитов. Без requests HPA по CPU не работает, а заниженные requests дают лишнее масштабирование.
  • Нужен metrics-server (для CPU и памяти). Для своих метрик (RPS, длина очереди) нужен адаптер Prometheus или KEDA, который масштабирует и по внешним событиям, вплоть до нуля.
  • Поведение задаёт behavior: окно стабилизации на уменьшение по умолчанию 300 секунд, чтобы не было «пилы».
  • Память плохой сигнал для масштабирования: многие рантаймы не отдают память после пика, и HPA не сможет уменьшить число подов.
  • Не ставь HPA и ручной replicas в манифесте, который постоянно применяется: каждый apply будет сбрасывать число реплик.

VPA меняет requests и limits подов (вертикально), Cluster Autoscaler или Karpenter добавляют и убирают узлы, когда поды не помещаются (Pending) или узлы простаивают. Обычно работают вместе: HPA добавил поды, они не поместились, автоскейлер узлов поднял узел.

Тома: PV, PVC, StorageClass

  • PersistentVolume (PV): кусок хранилища в кластере (облачный диск, NFS, локальный диск).
  • PersistentVolumeClaim (PVC): заявка приложения: «нужно 20 ГиБ, режим доступа такой, класс такой».
  • StorageClass: шаблон для динамического создания PV: какой драйвер (CSI), тип диска, политика удаления.
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: uploads
spec:
  accessModes: [ReadWriteOnce]
  storageClassName: fast-ssd
  resources:
    requests:
      storage: 20Gi

Режимы доступа:

  • ReadWriteOnce (RWO): монтируется на запись одним узлом (несколько подов на этом узле могут). Обычные облачные блочные диски.
  • ReadOnlyMany (ROX): только чтение многими узлами.
  • ReadWriteMany (RWX): запись многими узлами: NFS, CephFS, облачные файловые хранилища.
  • ReadWriteOncePod: строго один под.

reclaimPolicy: Delete (диск удаляется вместе с PVC, частое значение по умолчанию у динамических классов) или Retain (PV остаётся, данные можно спасти). Для важных данных Retain и бэкапы. volumeBindingMode: WaitForFirstConsumer создаёт диск в той зоне, где запланирован под, иначе под и диск могут оказаться в разных зонах, и под застрянет в Pending.

Расширение: если класс разрешает (allowVolumeExpansion: true), увеличивают spec.resources.requests.storage у PVC. Уменьшить нельзя.

StatefulSet

Для приложений с состоянием (базы, брокеры, кластеры вроде Kafka, etcd, Elasticsearch):

  • Стабильные имена: db-0, db-1, db-2, сохраняются при пересоздании.
  • Стабильный DNS через headless Service: db-0.db.prod.svc.cluster.local.
  • Свой том на реплику: volumeClaimTemplates создаёт PVC data-db-0, data-db-1... Под db-1 после пересоздания получает тот же PVC.
  • Порядок: по умолчанию поды создаются по одному по возрастанию номера (следующий после готовности предыдущего) и удаляются в обратном порядке. podManagementPolicy: Parallel это отключает.
  • Обновление RollingUpdate идёт от старшего номера к младшему; partition позволяет обновить только часть реплик (канарейка).
  • При удалении StatefulSet или уменьшении реплик PVC не удаляются по умолчанию: данные защищены (настраивается persistentVolumeClaimRetentionPolicy).

StatefulSet не делает репликацию данных за тебя: кто мастер, как синхронизировать реплики, как переключаться при отказе, решает само приложение или оператор (CloudNativePG, Strimzi, операторы Redis и других). Многие команды держат базы вне кластера в управляемых сервисах, и это нормальный выбор.

PodDisruptionBudget

PDB ограничивает добровольные нарушения: kubectl drain при обслуживании узла, обновление кластера, работа автоскейлера узлов.

apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: api
spec:
  minAvailable: 2        # или maxUnavailable: 1
  selector:
    matchLabels: { app: api }

От падения узла PDB не защищает (это недобровольное нарушение). Ловушка: minAvailable равен числу реплик, и drain никогда не закончится.

Размещение подов

  • nodeSelector и node affinity: на какие узлы можно.
  • Pod anti-affinity и topologySpreadConstraints: распределить реплики по узлам и зонам, чтобы отказ одного не уронил всё.
  • Taints и tolerations: узел отталкивает поды без соответствующей толерантности (выделенные узлы под GPU или под базы).
  • PriorityClass: при нехватке места высокоприоритетные поды могут вытеснить низкоприоритетные.

Частые ошибки

  • HPA без requests или с заниженными requests.
  • Масштабирование по памяти для JVM или Node.
  • База в Deployment с одним RWO-томом и RollingUpdate: новый под не может смонтировать диск, пока старый жив.
  • reclaimPolicy: Delete для данных, которые нельзя потерять.
  • Все реплики на одном узле без anti-affinity.
  • PDB, который блокирует обслуживание узлов.

Что спросят на собесе

  • Как работает HPA и от чего считается утилизация.
  • Чем StatefulSet отличается от Deployment.
  • Что такое PV, PVC и StorageClass, режимы доступа.
  • Как HPA и автоскейлер узлов работают вместе.
  • Зачем PodDisruptionBudget и от чего он не защищает.

Закрепите за 5 минут

После урока в тренажёре 3-5 проверочных вопросов, дальше модуль уходит в повторения.