Многие из вас наверняка сталкивались с ситуацией, когда аккуратно настроили поды в кластере, выставили CPU и память с точностью до 10 МБ, но вдруг приходит большой трафик и приложение начинает потреблять намного больше памяти, чем ожидалось. Тогда начинается игра в рулетку с VPA — удастся ли ему правильно подобрать новые значения ресурсов, так как режим Recreate
иногда преподносит неожиданные сюрпризы? Например, при наплыве трафика пересоздаст под, что под нагрузкой может вызвать перегрузку остальных компонентов и цепную ошибку. Или же можно сразу выделять запас ресурсов (если есть возможность)?
Всем привет! Меня зовут Юрий Лосев, я Technical Product Manager в команде Deckhouse компании «Флант». В этой статье я разберу, как бороться с проблемой нехватки ресурсов в контейнерах. Впервые решение появилось в версии Kubernetes 1.27. Речь про функционал, с помощью которого можно изменять вычислительные ресурсы у уже работающих подов без их перезапуска. В 1.33 этот функционал значительно улучшили и сделали доступным по умолчанию.
Рассмотрим, как это работает, где на практике особенно полезно и какие существуют ограничения.

Изменение запросов и лимитов без пересоздания пода
KEP-1287 добавил возможность изменять поля resources.requests и resources.limits в спецификации пода, которые раньше были неизменяемыми (immutable fields), без перезапуска пода. То есть теперь они меняются «на лету» или по месту.
Как выглядит этот процесс по шагам:
Пользователь меняет ресурсы контейнера (CPU или память) в манифесте пода и отправляет изменения в API-сервер Kubernetes (kube-api).
kube-api передаёт обновлённую спецификацию пода на соответствующий узел.
На узле kubelet выполняет быструю проверку ресурсов:
capacity узла — сумма всех выделенных на контейнеры ресурсов >= новые ресурсы
Если условие выполняется — изменения применяются, если нет — под получает статусPodResizePending
.После проверки kubelet использует CRI, чтобы сообщить среде выполнения контейнеров (например, containerd, cri-o или podman), например: «Этому контейнеру нужно больше/меньше памяти».
Среда выполнения (runtime) автоматически подстраивает cgroups контейнера, увеличивая или уменьшая выделенные ресурсы без перезапуска контейнера. Этот процесс происходит асинхронно и не блокирует выполнение — kubelet продолжает заниматься другими задачами.

Совместимость Container Runtime
В таблице ниже указаны минимальные версии популярных Container Runtime, которые полностью поддерживают CRI (или не все) и работают с современными версиями Kubernetes:
Container Runtime |
Compatible Version |
Release Notes |
containerd |
v1.6.9+ |
|
CRI-O |
v1.24.2+ |
|
Podman |
v4.0.0+ |
|
Docker |
Полностью не поддерживается |
Но кто использует Docker как CRI в 2025-м? |
Сценарии, в которых новая функция обновления ресурсов спасает приложение
Посмотрим на реальные случаи, где эта возможность будет использоваться на 100 %.
Базы данных
Например, инстанс PostgreSQL вдруг требует больше памяти, чтобы потянуть новый отчёт. Раньше приходилось перезапускать под, что вызывало простой и потерю соединений. Теперь ресурсы можно увеличить «на лету» — без остановки, а пользователи этого даже и не заметят.
Stateless веб-приложения
Приложения на NodeJS, Golang, Python спокойно используют дополнительные CPU и память без рестартов. Это идеально подходит для динамического масштабирования во время пикового трафика.
Для JVM-приложений просто увеличить лимиты памяти пода недостаточно, так как параметр -Xmx
(максимальный размер кучи) обычно задаётся при старте. Хотя CPU и не-heap-память можно менять «на лету», для полного использования новых лимитов Java-приложению всё равно потребуется рестарт. Увы, идеального решения пока нет.
ML-сервисы
Сервисы на базе TensorFlow, которым вдруг понадобилась поддержка более тяжёлых моделей или запросов большего объёма, теперь могут получить больше ресурсов без остановки обработки.
Прокси-сайдкары в service mesh
Envoy-прокси в Istio и других mesh-системах можно адаптировать под изменяющийся трафик без сбоев и влияния на основное приложение. Это одна из лучших возможностей, ведь трафик часто сложно предсказать заранее.
Обновление ресурсов «на лету» на практике
Но достаточно теории, мы ведь любим практику. Давайте потрогаем этот функционал своими руками.
Есть некоторая разница между Kubernetes 1.27–1.32 и Kubernetes 1.33. Чтобы понять её и почему это сделано, начнём эксперимент с версии 1.27.
Обновление ресурсов в K8s v1.27
В Kubernetes 1.27 завезли FeatureGate InPlacePodVerticalScaling
, который позволяет изменять поле .spec.containers[*].resources
у подов. Чтобы эта функция работала, её надо выставить не только у control-plane-ресурсов (apiserver, controller-manager, scheduler), но и для kubelet. При этом после включения флага InPlacePodVerticalScaling
kubelet начинает отображать статистику выделенных ресурсов в статусе контейнера:
status:
containerStatuses:
- allocatedResources:
cpu: 150m
memory: 128Mi
containerID: containerd://xxx
image: docker.io/library/ubuntu:22.04
imageID: docker.io/library/ubuntu@xxx8b1649e5b269eee
name: resource-watcher
ready: true
resources:
limits:
cpu: 150m
memory: 128Mi
requests:
cpu: 150m
memory: 128Mi
restartCount: 0
А вот дальше становится интересно. После указания данных флагов мы можем менять не только поля image
, tolerations
, terminationGracePeriodSeconds
у пода (как в обычном состоянии), но и resources
:
The Pod "resize-demo" is invalid: spec: Forbidden: pod updates may not change fields other than `spec.containers[*].image`,`spec.initContainers[*].image`,`spec.activeDeadlineSeconds`,`spec.tolerations` (only additions to existing tolerations),`spec.terminationGracePeriodSeconds` (allow it to be set to 1 if it was previously negative),`spec.containers[*].resources` (for CPU/memory only)
Для проверки задеплоим следующий под:
apiVersion: v1
kind: Pod
metadata:
name: resize-demo
spec:
containers:
- name: resource-watcher
image: ubuntu:22.04
command:
- "/bin/bash"
- "-c"
- |
apt-get update && apt-get install -y procps bc
echo "=== Pod Started: $(date) ==="
# Functions to read container resource limits
get_cpu_limit() {
if [ -f /sys/fs/cgroup/cpu.max ]; then
# cgroup v2
local cpu_data=$(cat /sys/fs/cgroup/cpu.max)
local quota=$(echo $cpu_data | awk '{print $1}')
local period=$(echo $cpu_data | awk '{print $2}')
if [ "$quota" = "max" ]; then
echo "unlimited"
else
echo "$(echo "scale=3; $quota / $period" | bc) cores"
fi
else
# cgroup v1
local quota=$(cat /sys/fs/cgroup/cpu/cpu.cfs_quota_us)
local period=$(cat /sys/fs/cgroup/cpu/cpu.cfs_period_us)
if [ "$quota" = "-1" ]; then
echo "unlimited"
else
echo "$(echo "scale=3; $quota / $period" | bc) cores"
fi
fi
}
get_memory_limit() {
if [ -f /sys/fs/cgroup/memory.max ]; then
# cgroup v2
local mem=$(cat /sys/fs/cgroup/memory.max)
if [ "$mem" = "max" ]; then
echo "unlimited"
else
echo "$((mem / 1024 / 1024)) MiB"
fi
else
# cgroup v1
local mem=$(cat /sys/fs/cgroup/memory/memory.limit_in_bytes)
echo "$((mem / 1024 / 1024)) MiB"
fi
}
# Print resource info every 5 seconds
while true; do
echo "---------- Resource Check: $(date) ----------"
echo "CPU limit: $(get_cpu_limit)"
echo "Memory limit: $(get_memory_limit)"
echo "Available memory: $(free -h | grep Mem | awk '{print $7}')"
sleep 5
done
resizePolicy:
- resourceName: cpu
restartPolicy: NotRequired
- resourceName: memory
restartPolicy: NotRequired
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "128Mi"
cpu: "100m"
Обратите внимание на нюансы: мы создаём под с установленными requests и limits:
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "128Mi"
cpu: "100m"
Попробуем изменить ресурсы этого пода:
kubectl patch pod resize-demo --patch \
'{"spec":{"containers":[{"name":"resource-watcher", "resources":{"requests":{"cpu":"200m"}, "limits":{"cpu":"200m"}}}]}}'
pod/resize-demo patched
Наш запрос успешно выполнился и ресурсы пода изменились. Кажется, что всё работает так, как и ожидалось, но не всё так просто, следующий запрос падает:
kubectl patch pod resize-demo --patch \
'{"spec":{"containers":[{"name":"resource-watcher", "resources":{"requests":{"cpu":"150m"}, "limits":{"cpu":"200m"}}}]}}'
The Pod "resize-demo" is invalid: metadata: Invalid value: "Guaranteed": Pod QoS is immutable
Почему же так происходит? При изменении пода Kubernetes автоматически заполняет некоторые поля. Если изначально значения requests и limits были равны, то QoS-класс пода был Guaranteed
. После изменения, когда requests и limits становятся разными, QoS-класс должен измениться на Burstable
. Однако это поле является immutable, то есть его нельзя изменить, поэтому такой запрос на изменение не пройдёт.
Но с некоторой подготовкой — точным определением профиля использования и необходимыми гарантиями в runtime — мы можем менять ресурсы контейнера. Например:
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 200m
memory: 256Mi
У нас есть заданные ресурсы, но мы поняли, что не вписываемся в реальное потребление приложения, и решили пропорционально всё увеличить:
kubectl patch pod resize-demo --patch \
'{"spec":{"containers":[{"name":"resource-watcher", "resources":{"requests":{"cpu":"200m", "memory": "200Mi"}, "limits":{"cpu":"500m", "memory": "500Mi"}}}]}}'
pod/resize-demo patched
…или увеличить лимиты, чтобы не поймать OOM:
kubectl patch pod resize-demo \
--type='json' \
-p='[
{
"op": "add",
"path": "/spec/containers/0/resources/limits/memory",
"value": "750Mi"
},
{
"op": "add",
"path": "/spec/containers/0/resources/limits/cpu",
"value": "750m"
}
]'
pod/resize-demo patched
Иногда использовать jsonpatch удобнее, если не хочется писать имя контейнера.
Результат:
kubectl get pods resize-demo -o yaml
containerStatuses:
- allocatedResources:
cpu: 100m
memory: 128Mi
containerID: containerd://xxx
image: docker.io/library/ubuntu:22.04
imageID: docker.io/library/ubuntu@xxx
name: resource-watcher
ready: true
resources:
limits:
cpu: 750m
memory: 750Mi
requests:
cpu: 100m
memory: 128Mi
restartCount: 0
Как я уже писал, задача resize происходит асинхронно, поэтому новые значения не применяются мгновенно. В Kubernetes 1.27 пока нет специального поля, которое показывало бы статус этой операции. Поэтому просто повторите команду через 30 секунд или используйте режим наблюдения с помощью команды:
kubectl get pods resize-demo -o yaml -w
Как видим, ресурсы контейнера успешно меняются, при этом значение restartCount
остаётся равным 0, то есть контейнер не перезагружался.
Также мы видим изменения в статусе пода с точки зрения Kubernetes. Но важно понять, меняется ли что-то в самом поде. Ведь главное — чтобы CRI действительно обновил текущие cgroups. Для проверки мы запустили под, который выводит в логи текущее значение из cgroup. Посмотрим логи этого пода:
kubectl logs resize-demo --tail=4
---------- Resource Check: Tue Jun 22 12:41:15 UTC 2025 ----------
CPU limit: .750 cores
Memory limit: 750 MiB
Available memory: 3.9Gi
Просмотрите всю историю логов, чтобы увидеть момент изменения:
kubectl logs resize-demo --tail=-1
...
---------- Resource Check: Tue Jun 22 12:41:10 UTC 2025 ----------
CPU limit: .100 cores
Memory limit: 128 MiB
Available memory: 3.9Gi
---------- Resource Check: Tue Jun 22 12:41:15 UTC 2025 ----------
CPU limit: .750 cores
Memory limit: 750 MiB
Available memory: 3.9Gi
Как видим, containerd всё поменял и наш контейнер работает с новыми лимитами.
resizePolicy
Возможно, вы обратили внимание на часть спеки:
resizePolicy:
- resourceName: cpu
restartPolicy: NotRequired
- resourceName: memory
restartPolicy: NotRequired
Это новое поле, которое появилось при включении FeatureFlag InPlacePodVerticalScaling
. Оно указывает, что делать контейнеру при изменении размера. По умолчанию стоит restartPolicy: NotRequired
, то есть контейнер не будет перезагружаться при изменении лимитов ресурсов.
Но есть приложения, которым при изменении лимитов нужен рестарт, например JVM с опцией -Xmx
. Для таких контейнеров можно установить restartPolicy: RestartContainer
. Это позволяет гибко настроить приложение. Например, сайдкар с Envoy-прокси можно перезапускать без рестарта, а «тяжёлое» Java-приложение — только с рестартом, если это необходимо.
Для таких подов перезапуск будет виден при выводе списка:
kubectl get pods resize-demo
NAME READY STATUS RESTARTS AGE
resize-demo 1/1 Running 1 (42s ago) 4m32s
Это также отразится в статусе контейнера:
resources:
limits:
cpu: 200m
memory: 512Mi
requests:
cpu: 50m
memory: 64Mi
restartCount: 1
Обновление ресурсов в K8s v1.33
Теперь посмотрим, что интересного привезли в Kubernetes 1.33:
FeatureFlag
InPlacePodVerticalScaling
включили по умолчанию.Появился новый subresource
/resize
(доступен начиная с kubectl 1.32), который позволяет изменять ресурсы пода без проблем с остальными полями в спецификации.В статус пода добавились новые состояния, показывающие процесс изменения ресурсов:
-
type: PodResizePending — kubelet не может сразу удовлетворить запрос на изменение ресурсов. В поле
message
можно увидеть детали и причины:reason: Infeasible — запрошенное изменение невозможно на текущем узле (например, запрошено больше ресурсов, чем доступно);
reason: Deferred — запрошенное изменение сейчас невозможно, но может стать возможным позже (например, если будет удалён другой pod). kubelet будет пытаться повторять изменение ресурсов.
type: PodResizeInProgress — kubelet принял запрос на изменение и выделил ресурсы, но изменения ещё применяются. Обычно это происходит быстро, но иногда может занять больше времени в зависимости от типа ресурса и поведения среды выполнения (runtime). Любые ошибки при применении изменений будут отражены в поле message (вместе с reason: Error).
Теперь изменение ресурсов работает без жёстких ограничений, которые были раньше, — например, связанных с классом QoS или ошибками из-за resizePolicy
:
kubectl patch pod resize-demo --subresource resize --patch \
'{"spec":{"containers":[{"name":"resource-watcher", "resources":{"requests":{"memory":"256Mi"}, "limits":{"memory":"256Mi"}}}]}}'
pod/resize-demo patched
Однако некоторые ограничения все-таки остались.
Ограничения In-Place Pod Resize
Если под использует swap, то изменить память без перезагрузки нельзя — потребуется рестарт контейнера.
Не работает на Windows-узлах.
Можно изменять только CPU и память.
Memory limit нельзя понижать без рестарта, а вот memory request — можно. Это связано с повышенным риском возникновения ошибок OOM, если лимит будет слишком низким.
QoS-класс пода не меняется. То есть, как бы вы ни изменили параметры, QoS останется таким же, каким был при старте. Это не блокирует обновление ресурсов, но изменить класс QoS пока нельзя.
Init- и ephemeral-контейнеры нельзя рестартить.
Нельзя полностью удалить requests или limits, можно только изменить их значения.
Поле
resizePolicy
нельзя изменять после создания пода.
Также, начиная с версии 1.33, поддерживается VPA версии 1.4 и выше с новым режимом mode: InPlaceOrRecreate
. Он позволяет применять рекомендации по ресурсам «на лету». Работает через subresource resize
, поэтому в более старых версиях его не включить. Это можно исправить, но VPA упирается в ограничения, которые были описаны выше, и не может нормально поправить поды.

Поэтому светлое будущее автоматического изменения ресурсов ещё не наступило, но уже стоит на пороге.
Заключение
Функция обновления ресурсов контейнеров «на лету» — это большой шаг вперёд для гибкости и надёжности Kubernetes-кластеров. Теперь можно оперативно реагировать на изменения нагрузки и не опасаться простоев и цепных сбоев из-за пересоздания подов. Да, у технологии ещё есть ограничения, не все сценарии покрыты идеально, но реализация in-place resize по умолчанию в последнем релизе говорит о быстром темпе развития.
P. S.
Читайте также в нашем блоге: