Привет, Хабр!

Помните тот момент, когда вы в очередной раз выставляли requests и limits для вашего пода, основываясь на... чем, собственно? На глазок? На данных «ну там вроде 128 мегабайт хватает»? На результатах пятиминутного стресс‑теста, который показал, что под нагрузкой нужно 2 ядра? Мы все через это проходили. Получается классическая ситуация: либо мы недодаем ресурсов, и наш падает от OOMKilled в самый неподходящий момент, либо мы перестраховываемся и заливаем в него гигабайты памяти и ядра, которые он использует раз в год под Новый Год, а кластер тем временем плачет от нехватки нод.

Горизонтальное масштабирование (HPA) — наш спаситель, он известен всем и каждому. Увеличилась нагрузка — запустил еще пару копий приложения. Красиво. Но что, если само приложение не очень‑то умеет работать в несколько копий? Или если нагрузка не «всплесковая», а просто приложение со временем начало есть больше памяти из‑за роста данных? Тут подходит менее раскрученный, но полезный коллега — Vertical Pod Autoscaler (VPA).

Идея VPA до проста: он смотрит на фактическое потребление ресурсов вашими подами и говорит: «твоему приложению на самом деле нужно не 100 милликор, а стабильно 150, давай исправим эту несправедливость». А в продвинутом режиме он не просто говорит, а берет и делает. Главная загвоздка, из‑за которой многие плюются — для применения новых лимитов под нужно перезапустить, это downtime, но эту проблему можно и нужно грамотно обойти.

VPA

Для начала четко разграничим с HPA. HPA управляет количеством подов. VPA управляет аппетитами каждого отдельного пода. Он оперирует понятиями requests (запрос, гарантированная доля ресурса) и limits (потолок, который нельзя превысить). VPA анализирует историю потребления CPU и памяти и на основе этого выдает рекомендации или даже применяет новые значения.

VPA состоит из трех основных компонентов:

  • Recommender: анализирует метрики, хранящиеся в Prometheus (или во встроенном хранилище), и вычисляет целевые значения для requests и limits.

  • Updater: в режиме Auto именно он решает, когда пора убить под, чтобы он пересоздался с новыми ресурсами.

  • Admission Controller: когда API‑сервер Kubernetes получает запрос на создание пода, этот контроллер подменяет оригинальные значения requests и limits на те, что рекомендовал VPA.

Самая частая ошибка — пытаться использовать и HPA, и VPA для одного и того же ресурса (например, CPU) одновременно. Канонический подход: HPA для масштабирования по CPU, VPA — для настройки запросов памяти.

Ставим деплой VPA

Первым делом, нужен сам VPA в кластере. Самый простой способ — использовать официальный Helm‑чарт. Но мы с вами не ищем простых путей, да и для понимания полезно посмотреть на манифесты. Качаем репозиторий и деплоим.

git clone https://github.com/kubernetes/autoscaler.git
cd autoslicer/vertical-pod-autoscaler/
./hack/vpa-up.sh

Этот скрипт поднимет все три компонента в неймспейсе kube-system. Проверим, что все живо:

kubectl --namespace=kube-system get pods -l "app in (vpa-admission-controller, vpa-recommender, vpa-updater)"

Должны увидеть три готовых пода. Отлично, инфраструктура готова.

Режим первый: Recommender

Это самый безопасный режим. VPA будет собирать метрики, считать и выдавать рекомендации, но не будет трогать ваши поды. Идеально для начала, чтобы понять, что же вообще VPA думает о вашем приложении.

Создадим простенькое приложение для теста. Возьмем nginx и искусственно нагрузим его памятью с помощью небольшого скрипта.

Сначала создаем Deployment:

# nginx-vpa-test.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-memory-loader
spec:
  selector:
    matchLabels:
      app: nginx-memory-loader
  template:
    metadata:
      labels:
        app: nginx-memory-loader
    spec:
      containers:
      - name: nginx
        image: nginx:1.25
        resources:
          requests:
            memory: "100Mi"
            cpu: "50m"
          limits:
            memory: "200Mi"
            cpu: "100m"
        volumeMounts:
        - name: scripts
          mountPath: /scripts
      - name: memory-loader
        image: busybox:1.36
        command: ["/bin/sh"]
        args: ["-c", "while true; do tail /dev/zero; done"] # Простейший способ нагрузить память
        resources:
          requests:
            memory: "50Mi"
            cpu: "10m"
          limits:
            memory: "100Mi"
            cpu: "50m"
        volumeMounts:
        - name: scripts
          mountPath: /scripts
      volumes:
      - name: scripts
        emptyDir: {}

Применим его: kubectl apply -f nginx-vpa-test.yaml.

Теперь создаем объект VPA для этого деплоймента. Указываем режим Off, что означает только рекомендации.

# vpa-recommender.yaml
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
  name: nginx-vpa
spec:
  targetRef:
    apiVersion: "apps/v1"
    kind: Deployment
    name: nginx-memory-loader
  updatePolicy:
    updateMode: "Off" # Ключевой момент! Режим рекомендаций.
  resourcePolicy:
    containerPolicies:
    - containerName: "*"
      minAllowed:
        cpu: "25m"
        memory: "50Mi"
      maxAllowed:
        cpu: "1"
        memory: "1Gi"

targetRef: указываем, к какому объекту (Deployment, StatefulSet) мы привязываем VPA.

resourcePolicy: настраиваем безопасные коридоры. minAllowed и maxAllowed — это жесткие границы, за которые VPA не выйдет никогда.. Не дайте VPA выставить лимит памяти в 10 гигабайт из‑за случайного выброса.

Применяем: kubectl apply -f vpa-recommender.yaml.

Теперь нужно подождать. VPA'шному Recommender'у нужно время, чтобы накопить метрики. Обычно хватает 10–15 минут. После этого можно спросить у него совета:

kubectl describe vpa nginx-vpa

В выводе будет примерно следующее:

...
Recommendation:
  Container Recommendations:
    Container Name: nginx
    Lower Bound:
      Cpu:     60m
      Memory:  131072k
    Target:
      Cpu:     80m
      Memory:  262144k # VPA рекомендует увеличить память до 256Mi
    Uncapped Target:
      Cpu:     80m
      Memory:  262144k
    Upper Bound:
      Cpu:     100m
      Memory:  500Mi
...

VPA видит, что контейнер nginx стабильно использует около 256Mi памяти, но при этом его limit стоит на 200Mi. Это потенциальный риск OOMKill. Рекомендация — повысить request до 256Mi, а limit, скорее всего, до 500Mi. Можно взять эти цифры и вручную обновить манифест вашего деплоймента. Уже на этом этапе польза огромна.

Режим второй: Auto

Когда вы убедились, что Recommender не несет околесицу, можно переходить на следующий уровень — режим Auto. В этом режиме Updater будет самостоятельно принимать решение о пересоздании пода с новыми ресурсами.

Меняем в нашем VPA‑манифесте одну строчку:

# vpa-auto.yaml
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
  name: nginx-vpa
spec:
  targetRef:
    apiVersion: "apps/v1"
    kind: Deployment
    name: nginx-memory-loader
  updatePolicy:
    updateMode: "Auto" # Теперь VPA будет действовать!
  resourcePolicy:
    containerPolicies:
    - containerName: "*"
      minAllowed:
        cpu: "25m"
        memory: "50Mi"
      maxAllowed:
        cpu: "1"
        memory: "1Gi"

Что произойдет после применения (kubectl apply -f vpa-auto.yaml)?

  1. VPA Updater поймет, что текущие ресурсы пода отличаются от целевых (Target).

  2. Он эвакуирует под (помечает его на удаление) в соответствии с политиками Pod Disruption Budget (о них ниже).

  3. Когда Deployment Controller увидит, что под убит, он создаст новый.

  4. В момент создания нового пода Admission Controller подставит в его манифест новые, рассчитанные VPA значения requests и limits.

Да, под перезапустится. Это downtime. Для stateless‑сервисов, которые запущены в нескольких репликах, это обычно не проблема — Kubernetes сделает это грамотно, не убивая все реплики разом. Но для stateful‑нагрузок или сервисов в одну реплику это критично. Минимизировать ущерб — наша задача.

Pod Disruption Budget

PDB — это механизм Kubernetes, который говорит: «система, когда ты решишь перезапустить мои поды, убедись, что одновременно работает хотя бы N штук».

Допустим, у нашего nginx-memory-loader три реплики. Мы хотим, чтобы в любой момент времени минимум две из них были доступны. Создаем PDB:

# pdb.yaml
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: nginx-pdb
spec:
  minAvailable: 2 # Минимум доступных подов. Можно использовать maxUnavailable.
  selector:
    matchLabels:
      app: nginx-memory-loader

Теперь, когда VPA Updater захочет перезапустить поды, он будет делать это в рамках этого бюджета. Он не сможет убить сразу три пода, если это нарушит условие minAvailable: 2. Он будет перезапускать их по одному, обеспечивая плавный rolling update. Всегда используйте PDB вместе с VPA в режиме Auto для критичных нагрузок.

Глубже

Kubernetes присваивает каждому поду класс качества обслуживания (Quality of Service) на основе его requests и limits. Это напрямую влияет на то, чей под будет убит первым при нехватке ресурсов на ноде.

  • Guaranteed: requests == limits для обоих ресурсов (CPU и memory). Высший приоритет.

  • Burstable: requests < limits. Средний приоритет.

  • BestEffort: requests и limits не заданы. Низший приоритет, умрет первым.

VPA, изменяя значения, может менять QoS класса вашего пода. Если изначально у вас был Burstable (requests 100Mi, limits 200Mi), а VPA выставил requests и limits в 256Mi, то под перейдет в класс Guaranteed. Это хорошо с точки зрения стабильности. Но если VPA поднимет только requests, а limits останется высоким, QoS класс не изменится. Нужно держать это в уме.

VPA не бездумно перезапускает поды. Им можно управлять.

  • minReplicas: минимальное количество реплик, для которого VPA будет активен.

  • Контроль над рекомендациями по CPU и памяти можно разделить. Например, можно разрешить VPA управлять памятью, но запретить трогать CPU.

Пример настройки для более точечного контроля:

apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
  name: nginx-vpa-advanced
spec:
  targetRef:
    apiVersion: "apps/v1"
    kind: Deployment
    name: nginx-memory-loader
  updatePolicy:
    updateMode: "Auto"
  resourcePolicy:
    containerPolicies:
    - containerName: "nginx"
      minAllowed:
        memory: "100Mi"
      maxAllowed:
        memory: "2Gi"
      # controlledResources: ["memory"] # Раскомментируй, чтобы VPA управлял ТОЛЬКО памятью
      # controlledValues: "RequestsOnly" # Раскомментируй, чтобы VPA менял только requests, оставляя limits как есть
    - containerName: "memory-loader"
      mode: "Off" # А для этого контейнера вообще выключаем VPA

VPA vs HPA

Расставим точки над i. Это не или‑или, а инструменты для разных задач.

Используйте HPA, когда:

  • Нагрузка резко меняется в течение короткого времени (дневной трафик, всплески активности).

  • Приложение умеет масштабироваться горизонтально (stateless, сессии вынесены наружу).

  • Вам нужно быстро реагировать на увеличение числа запросов.

Используйте VPA, когда:

  • Нагрузка растет постепенно (увеличивается объем данных, медленный рост пользовательской базы).

  • Приложение плохо горизонтально масштабируется (stateful, монолит с большим состоянием в памяти).

  • Главная задача — оптимизировать utilization нод и снизить costs (особенно в облаках).

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

Идеальная комбинация для многих веб‑сервисов: HPA по CPU для отражения всплесков трафика, и VPA по памяти для плавной адаптации к росту данных.


VPA берет на себя рутину по настройке ресурсов, но требует от вас понимания и контроля. Если вы подружитесь с ним, он поможет выжать из вашего кластера максимум эффективности, избавит от ночных вызовов из‑за OOMKilled и позволит ресурсам работать в соответствии с реальными потребностями, а не с вашими предположениями полугодовой давности.

Удачи в оптимизации.

В работе с Kubernetes мы часто упираемся в вопрос оптимального управления ресурсами: requests, limits, баланс между HPA и VPA. Это требует понимания принципов планирования, тонкостей настройки и практических навыков. Если вы хотите глубже разобраться в том, как строится инфраструктурная платформа на Kubernetes и как на практике применять подобные механизмы, обратите внимание на курс «Инфраструктурная платформа на основе Kubernetes».

Пройдите вступительный тест, чтобы узнать, подойдет ли вам программа курса.

Рост в IT быстрее с Подпиской — дает доступ к 3-м курсам в месяц по цене одного. Подробнее

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