Коротко:
- Без requests планировщик не знает, куда ставить под, и может перегрузить ноду. Без limits один контейнер способен съесть всю память на хосте.
- Ставить limits равными requests - безопасно, но дорого: вы резервируете ресурсы, которые реально не используются.
- Главные симптомы неправильной настройки - OOMKilled и CPU throttling. Оба убивают производительность, но по-разному.
- Реальное потребление смотрят через
kubectl top, Prometheus и VPA в режиме рекомендаций. - Goldilocks автоматически собирает рекомендации VPA по всем неймспейсам и показывает их в веб-интерфейсе.
- Хорошая точка старта: request = p95 потребления за 7 дней, limit по CPU = 2-4x от request, limit по памяти = 1.2-1.5x от request.
Почему это вообще важно
Кластер без настроенных ресурсов похож на офис без переговорок: все занимают место стихийно, и в какой-то момент оказывается, что свободного места нет, а половина людей сидит в пустых комнатах. Ноды перегружаются, поды вытесняются, сервисы начинают лагать или падать - и всё это происходит не из-за нехватки железа, а из-за того, что планировщик не получил нужных подсказок.
С другой стороны, команды, которые боятся нестабильности, часто уходят в другую крайность: выставляют огромные значения с запасом. Под просит 4 CPU и 8 Gi памяти, реально потребляет 0.2 CPU и 300 Mi - и так по всему кластеру. Облачный счет растет, ноды простаивают, а автоскейлер не может уменьшить кластер, потому что ресурсы формально заняты.
Правильная настройка resource requests и limits решает обе проблемы: кластер остается стабильным, а вы платите за то, что реально нужно.
Как это работает: requests, limits и планировщик
Прежде чем идти к практике, стоит разобраться с механикой. В Kubernetes каждый контейнер может иметь два параметра для CPU и памяти.
Request - это минимум, который планировщик гарантирует поду при размещении на ноде. Именно по requests планировщик решает, влезет ли под на конкретный узел. Если нода имеет 4 CPU, а сумма requests всех подов на ней равна 4 CPU, новый под с request 0.5 CPU туда не попадет - даже если реальное потребление всех подов составляет 1 CPU.
Limit - это потолок потребления. Контейнер не может использовать больше, чем указано в limit. Поведение при достижении потолка разное для CPU и памяти: CPU просто троттлится (процессорное время режется), а при превышении лимита по памяти процесс убивается с кодом OOMKilled.
Это принципиальное различие часто упускают. CPU throttling - это деградация производительности, которую трудно заметить без метрик. OOMKilled - это падение пода, которое видно сразу, но причину не всегда понимают правильно.
Пример манифеста с корректно выставленными значениями:
resources:
requests:
cpu: "250m"
memory: "256Mi"
limits:
cpu: "1000m"
memory: "512Mi"Здесь под гарантированно получит 0.25 CPU и 256 Mi памяти при планировании, но сможет использовать до 1 CPU и 512 Mi при наличии свободных ресурсов на ноде.
Три антипаттерна, которые встречаются чаще всего
1. Нет никаких значений
Под без requests попадает в класс QoS BestEffort - самый низкий приоритет. При нехватке памяти на ноде такие поды вытесняются первыми. Планировщик не учитывает их при размещении, что приводит к непредсказуемой загрузке нод. Один «тяжелый» под без ограничений может занять всю память узла и вызвать каскадное вытеснение соседей.
2. Limits равны requests
Это популярный совет из старых гайдов: «ставь одинаково, чтобы под получал ровно то, что просит». Технически это дает класс QoS Guaranteed - самый высокий приоритет при вытеснении. Но у подхода есть цена: вы резервируете ресурсы жестко. Под с request 1 CPU и limit 1 CPU никогда не использует больше, даже если нода пустая. В масштабах кластера это прямой kubernetes overprovision: вы платите за ресурсы, которые физически есть, но формально заняты.
3. Завышенные значения «на всякий случай»
Команда не знает реального потребления, боится OOMKilled и ставит limits с большим запасом. Под потребляет 100 Mi памяти, а limit стоит 4 Gi. Автоскейлер кластера видит, что ресурсы «заняты», и не уменьшает количество нод. Это самая дорогая ошибка в облаке - и самая незаметная, пока не придет счет.
OOMKilled и throttling: как диагностировать
OOMKilled
Под падает с этим статусом, когда контейнер превысил memory limit. Проверить просто:
kubectl describe pod
# ищем в секции Last State:
# Reason: OOMKilledИли через события:
kubectl get events --field-selector reason=OOMKillingПричины бывают двух типов. Первый - limit реально занижен: приложение в норме потребляет больше, чем ему разрешено. Второй - утечка памяти: приложение со временем потребляет всё больше и в итоге упирается в потолок. Это разные проблемы с разными решениями. В первом случае нужно поднять limit, во втором - чинить код.
Чтобы отличить одно от другого, смотрите на динамику: если потребление памяти монотонно растет до момента убийства - это утечка. Если под падает сразу при старте или при пиковой нагрузке - limit занижен.
CPU throttling
Это тихий убийца производительности. Под работает, не падает, но отвечает медленнее, чем должен. В Prometheus метрика выглядит так:
rate(container_cpu_cfs_throttled_seconds_total[5m])
/ rate(container_cpu_cfs_periods_total[5m])Если значение больше 25-30%, под регулярно не получает запрошенное процессорное время. Это прямое следствие слишком низкого CPU limit. Особенно болезненно для latency-sensitive сервисов: API, которое должно отвечать за 50 мс, начинает отвечать за 200-500 мс просто потому, что ядро режет ему время.
Важно: CPU throttling происходит даже когда нода не загружена. Это не про нехватку ресурсов на хосте - это про жесткий потолок, который вы сами выставили в манифесте. Низкий CPU limit = throttling при любой нагрузке.
Как найти реальное потребление: инструменты
kubectl top
Самый быстрый способ посмотреть текущее потребление:
kubectl top pods -n --sort-by=memory
kubectl top nodesМинус - показывает только текущий момент. Для выставления значений нужна история: пики нагрузки, ночные джобы, периодические задачи.
Prometheus + Grafana
Исторические данные по потреблению - основа для расчета адекватных значений. Полезные запросы:
# p95 потребления CPU за 7 дней
quantile_over_time(0.95,
rate(container_cpu_usage_seconds_total{
container!="", pod=~"myapp-.*"
}[5m])[7d:5m]
)
# максимум потребления памяти за 7 дней
max_over_time(
container_memory_working_set_bytes{
container!="", pod=~"myapp-.*"
}[7d]
)Смотрите именно на container_memory_working_set_bytes, а не на container_memory_usage_bytes. Первая метрика показывает активно используемую память без кеша, и именно её сравнивает OOM killer с лимитом.
VPA в режиме рекомендаций
Vertical Pod Autoscaler умеет не только автоматически менять requests - он может работать в режиме Off, просто собирая рекомендации без применения. Это безопасный способ получить data-driven предложения по значениям.
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
name: myapp-vpa
spec:
targetRef:
apiVersion: apps/v1
kind: Deployment
name: myapp
updatePolicy:
updateMode: "Off"После нескольких дней сбора данных смотрим рекомендации:
kubectl describe vpa myapp-vpaVPA покажет три значения: Lower Bound (минимум для стабильной работы), Target (рекомендуемое) и Upper Bound (максимум, который наблюдался). Это хорошая отправная точка, но не финальный ответ - VPA не знает о ваших SLO и бизнес-пиках.
Goldilocks
Goldilocks от Fairwinds - это надстройка над VPA, которая разворачивает VPA-объекты для всех деплойментов в указанных неймспейсах и показывает агрегированные рекомендации в веб-интерфейсе. Название отсылает к сказке про Машу и трёх медведей: цель - найти значения, которые «в самый раз».
Установка через Helm:
helm repo add fairwinds-stable https://charts.fairwinds.com/stable
helm install goldilocks fairwinds-stable/goldilocks \
--namespace goldilocks --create-namespace
# включаем для нужного неймспейса
kubectl label namespace production \
goldilocks.fairwinds.com/enabled=trueПосле этого Goldilocks создаст VPA-объекты для каждого деплоймента в неймспейсе production и начнет собирать данные. Через несколько дней в дашборде появятся рекомендации с разбивкой по контейнерам и классам QoS.
Вакансии для DevOps-инженеров
Вакансии для DevOps-инженеров
Когда использовать Goldilocks: если в кластере десятки или сотни деплойментов с неизвестным или устаревшим профилем потребления. Вручную профилировать каждый нереально - Goldilocks делает это автоматически и дает единую точку просмотра.
Пошаговый процесс выставления адекватных значений
- Собрать исторические данные. Минимум 7 дней, лучше 30. Включить все типичные сценарии нагрузки: дневные пики, ночные джобы, периодические задачи. Использовать Prometheus или включить VPA в режиме Off.
- Определить request по CPU. Берем p95 потребления за период наблюдения. Это значение гарантирует, что под получит достаточно ресурсов в 95% времени. Если сервис latency-sensitive, можно взять p99.
- Определить request по памяти. Берем типичное рабочее потребление (не пик). Память - не CPU: она не «возвращается» автоматически, поэтому request должен покрывать нормальный рабочий объем.
- Выставить CPU limit. Хорошее соотношение - 2-4x от request. Это дает поду возможность использовать свободные ресурсы ноды при пиках, но не позволяет монополизировать CPU. Если видите throttling выше 25% - поднимайте limit, а не request.
- Выставить memory limit. 1.2-1.5x от request. Память нельзя давать слишком щедро: если под реально потребляет столько, сколько указано в limit, это сигнал либо к утечке, либо к пересмотру архитектуры. Большой разрыв между request и limit по памяти маскирует проблемы.
- Проверить результат. После применения новых значений наблюдать за throttling и OOMKilled в течение нескольких дней. Смотреть на метрику
kube_pod_container_status_restarts_total- рост рестартов сигнализирует о проблеме. - Повторять регулярно. Профиль потребления меняется с каждым релизом. Значения, выставленные полгода назад, могут быть неактуальны. Goldilocks или периодический аудит через VPA помогают держать это под контролем.
Классы QoS: что выбрать осознанно
Kubernetes автоматически назначает каждому поду класс Quality of Service на основе того, как выставлены requests и limits. Это влияет на приоритет при вытеснении.
| Класс QoS | Условие | Приоритет при нехватке памяти |
|---|---|---|
| Guaranteed | requests == limits для всех контейнеров | Вытесняется последним |
| Burstable | requests < limits, или задан только один параметр | Вытесняется после BestEffort |
| BestEffort | Нет ни requests, ни limits | Вытесняется первым |
Для production-сервисов рекомендуется Burstable с разумным соотношением request/limit. Guaranteed подходит для критичных компонентов (например, системных подов), где предсказуемость важнее экономии. BestEffort - только для задач, которые можно безболезненно прервать.
LimitRange и ResourceQuota: защита на уровне неймспейса
Индивидуальная настройка каждого пода - это хорошо, но в реальных кластерах нужна защита от случайных деплойментов без ресурсных параметров. Для этого есть два объекта.
LimitRange задает дефолтные и максимальные значения для контейнеров в неймспейсе. Если под задеплоен без requests/limits, LimitRange автоматически подставит дефолты:
apiVersion: v1
kind: LimitRange
metadata:
name: default-limits
namespace: production
spec:
limits:
- type: Container
default:
cpu: "500m"
memory: "256Mi"
defaultRequest:
cpu: "100m"
memory: "128Mi"
max:
cpu: "4"
memory: "4Gi"ResourceQuota ограничивает суммарное потребление всего неймспейса. Это защита от ситуации, когда одна команда случайно задеплоила 50 реплик с большими requests и занял ресурсы всего кластера.
Типичные ошибки при настройке
| Ошибка | Последствие | Как исправить |
|---|---|---|
| Нет requests у подов | Непредсказуемое планирование, вытеснение при нагрузке | Добавить requests на основе реального потребления |
| CPU limit слишком низкий | Постоянный throttling, деградация latency | Поднять limit или убрать его для CPU (спорно, но допустимо) |
| Memory limit == memory request с большим запасом | Переплата за зарезервированные, но неиспользуемые ресурсы | Снизить request до реального потребления |
| Значения не обновляются после релизов | Устаревший профиль, накопленный overprovision | Регулярный аудит через VPA или Goldilocks |
| Одинаковые значения для всех сервисов | Одни сервисы задыхаются, другие простаивают | Профилировать каждый сервис отдельно |
Чеклист: перед тем как выставить значения в продакшен
- Собраны метрики потребления за минимум 7 дней (p95 CPU, max и типичное потребление памяти)
- Проверено, нет ли OOMKilled в истории подов за последние 2 недели
- Проверен уровень CPU throttling через Prometheus
- VPA запущен в режиме Off и дал рекомендации
- CPU limit выставлен как минимум в 2x от request
- Memory limit выставлен в 1.2-1.5x от request
- В неймспейсе настроен LimitRange с дефолтами
- После применения значений настроен алерт на рост рестартов подов
- Запланирован повторный аудит после следующего крупного релиза
FAQ
Что будет, если не выставить CPU limit совсем?
Контейнер сможет использовать столько CPU, сколько есть на ноде. Это не всегда плохо: при отсутствии конкуренции под работает быстрее. Но при высокой нагрузке один «жадный» контейнер может вытеснить соседей по CPU. Для production-сервисов без limits по CPU - приемлемо, если вы осознаете риск и мониторите потребление. Для памяти - нет: отсутствие memory limit опасно.
Чем VPA отличается от HPA?
HPA (Horizontal Pod Autoscaler) масштабирует количество реплик пода в зависимости от нагрузки. VPA меняет размер самого пода - его requests и limits. Они решают разные задачи и могут использоваться вместе, но с осторожностью: VPA в режиме Auto перезапускает поды для применения новых значений, что может конфликтовать с HPA.
Как понять, что кластер страдает от overprovision?
Смотрите на соотношение allocatable ресурсов ноды и реального потребления. Если ноды загружены на 10-20% по факту, но планировщик не может разместить новые поды - это классический kubernetes overprovision. Команда kubectl describe node покажет Allocated resources vs реальное потребление через kubectl top node.
Что такое OOMKilled и как отличить его от обычного краша?
OOMKilled - это принудительное завершение процесса ядром Linux, когда контейнер превысил memory limit. В отличие от обычного краша приложения, здесь нет stacktrace и нет логов о причине - под просто исчезает. Диагностируется через kubectl describe pod: в секции Last State будет Reason: OOMKilled и Exit Code 137.
Goldilocks или VPA напрямую - что выбрать?
Для одного-двух деплойментов достаточно VPA в режиме Off. Goldilocks оправдан, когда нужно профилировать десятки сервисов сразу: он автоматически создает VPA-объекты и дает единый дашборд. По сути Goldilocks - это удобная обертка над VPA, а не альтернатива.
Можно ли использовать VPA в режиме Auto в продакшене?
Технически да, но с осторожностью. VPA Auto перезапускает поды для применения новых значений - это означает кратковременную недоступность реплики. Для stateless-сервисов с несколькими репликами это обычно приемлемо. Для stateful-сервисов или сервисов с одной репликой - рискованно. Хорошая практика: Auto в staging, Off или Initial в production.
Итог
Правильная настройка ресурсов - это не разовая задача, а часть операционной культуры. Значения, которые работали при запуске сервиса, устаревают с каждым релизом. Профиль потребления меняется, нагрузка растет, появляются новые фичи - и то, что казалось щедрым запасом, превращается либо в узкое место, либо в деньги, выброшенные на ветер.
Хороший процесс выглядит так: собрать метрики, получить рекомендации от VPA или Goldilocks, осознанно выставить значения с учетом SLO и бизнес-пиков, настроить алерты на OOMKilled и throttling, повторить после следующего крупного изменения. Это занимает несколько часов, но экономит деньги и нервы при каждом инциденте.
Главное, что стоит запомнить: requests влияют на планирование и стоимость, limits - на стабильность. Занижать первое опасно для кошелька, занижать второе - для надежности. Найти баланс помогают не интуиция, а данные.