core code helper

Kubernetes у продакшені: probes, requests/limits і автоскейлінг

Три налаштування, без яких Deployment не готовий до продакшену: перевірки здоров'я, ресурси контейнерів і HPA. Що ставити, які значення і як не вбити сервіс власними руками.

0 імпортували Імпортувати собі

Deployment з попередньої статті запуститься, але в продакшені швидко з'являться питання: чому трафік іде на под, що ще не прогрівся? чому сусідній сервіс з'їв усю пам'ять вузла? чому в пікові години все гальмує? Відповіді — у трьох налаштуваннях.

Probes: як Kubernetes дізнається, що под здоровий

Probe Питання Якщо не проходить
startupProbe Застосунок уже запустився? Інші проби не запускаються; після ліміту — рестарт
readinessProbe Готовий приймати трафік? Под прибирається з балансування (без рестарту)
livenessProbe Живий чи завис? Контейнер перезапускається
containers:
  - name: api
    image: ghcr.io/acme/api:1.4.2
    startupProbe:
      httpGet: { path: /healthz, port: 8000 }
      periodSeconds: 5
      failureThreshold: 24        # до 2 хвилин на старт
    readinessProbe:
      httpGet: { path: /ready, port: 8000 }
      periodSeconds: 5
      failureThreshold: 3
    livenessProbe:
      httpGet: { path: /healthz, port: 8000 }
      periodSeconds: 10
      failureThreshold: 3
Обережно

Не перевіряйте в liveness зовнішні залежності (базу, Redis, інші сервіси). Якщо база ляже на хвилину, liveness почне перезапускати всі поди одночасно — і ви отримаєте каскадну аварію замість короткої деградації. Залежності — лише в readiness, а liveness має відповідати на питання «чи не завис сам процес».

Типовий розподіл:

  • /healthz — процес відповідає (без звернень до залежностей);
  • /ready — прогрів завершено, пул з'єднань з БД є, міграції пройшли.

Requests і limits

resources:
  requests:
    cpu: 250m        # 0.25 ядра — гарантовано, за цим планувальник вибирає вузол
    memory: 384Mi
  limits:
    memory: 512Mi    # більше — OOMKilled
  • requests — скільки ресурсу резервується. Планувальник ставить под лише на вузол, де є стільки вільного.
  • limits — стеля. Перевищення пам'яті → контейнер вбиває OOM killer (OOMKilled). Перевищення CPU → троттлінг (процес сповільнюється, але не вмирає).
Порада

Поширена практика — ставити limit на пам'ять (≈ requests + запас) і не ставити limit на CPU. CPU-ліміт часто спричиняє троттлінг навіть тоді, коли на вузлі є вільні ядра, і це виглядає як «незрозумілі» стрибки латентності.

Як підібрати значення: запустіть сервіс під реальним навантаженням, подивіться споживання (kubectl top pods, Prometheus) і поставте requests трохи вище типового, а memory limit — вище піку.

Класи QoS

Клас Умова Кого виселяють першим
Guaranteed requests = limits для CPU і пам'яті Останніми
Burstable Є requests, але не дорівнюють limits Посередині
BestEffort Немає ні requests, ні limits Першими

Под без requests — BestEffort: його виселять першим при нестачі пам'яті на вузлі.

HPA: горизонтальний автоскейлінг

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: 65 }
  behavior:
    scaleDown:
      stabilizationWindowSeconds: 300   # не скидати поди при кожному короткому спаді

HPA рахує утилізацію відносно requests. Без requests.cpu він не працюватиме — це найчастіша причина «HPA показує <unknown>».

Для черг і подій масштабування за CPU погано підходить — краще за довжиною черги чи кількістю запитів. Для цього використовують зовнішні метрики або KEDA.

PodDisruptionBudget

Оновлення вузлів, дренаж, автоскейлер кластера — усе це «добровільні» вимкнення подів. PDB не дає вимкнути забагато одночасно:

apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: api
spec:
  minAvailable: 2
  selector:
    matchLabels: { app: api }

Коректне завершення

При видаленні пода Kubernetes надсилає SIGTERM і чекає terminationGracePeriodSeconds (30 с за замовчуванням). Застосунок має перестати приймати нові запити, дообробити поточні й закритися. Короткий preStop зі sleep 5 дає балансувальникам час прибрати под з ротації до того, як він перестане відповідати.

Под постійно перезапускається зі статусом OOMKilled. Що перевірити?

Memory limit замалий для реального споживання або в застосунку витік пам'яті. Подивіться графік пам'яті до моменту вбивства: якщо вона росте до ліміту поступово годинами — схоже на витік; якщо одразу після старту чи під навантаженням — збільште limit і requests. Для JVM, Node.js і Python перевірте, що розмір heap налаштовано з урахуванням ліміту контейнера.

Збережіть статтю собі

Увійдіть, щоб імпортувати її у власні нотатки, відмічати вивчене, ставити лайки й питати спільноту. Реєстрація безкоштовна: вхід через Google займає кілька секунд.

Увійти через Google Інші способи входу