Привет, Хабр!
Помните тот момент, когда вы в очередной раз выставляли 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)?
VPA Updater поймет, что текущие ресурсы пода отличаются от целевых (Target).
Он эвакуирует под (помечает его на удаление) в соответствии с политиками Pod Disruption Budget (о них ниже).
Когда Deployment Controller увидит, что под убит, он создаст новый.
В момент создания нового пода 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-м курсам в месяц по цене одного. Подробнее
