Привет, Хабр!
Помните тот момент, когда вы в очередной раз выставляли 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-м курсам в месяц по цене одного. Подробнее