Перейти к содержимому
Hogin Hogin
Назад

In-Place Pod Resize вышел в GA в Kubernetes 1.35: меняем CPU/память без рестарта пода

10 мин чтения

Раньше единственный способ поменять CPU или память у уже работающего пода — отредактировать requests/limits в спеке контроллера, дождаться, пока Kubernetes удалит старый под и создаст новый, и понадеяться, что он приземлится на тот же узел с той же локальностью данных. Kubernetes 1.35 закрывает эту главу: In-Place Pod Resize (KEP-1287) получил статус stable, и kubectl patch pod --subresource resize меняет ресурсы контейнера, пока он продолжает работать. Разбираем, что именно стало стабильным, что всё ещё форсирует рестарт, и как это стыкуется с VPA, scheduler и cluster-autoscaler.

Содержание

Открыть содержание

Почему это раньше было больно

Под в Kubernetes — это не просто набор контейнеров, это ещё и requests/limits, зафиксированные в момент создания и намертво прибитые к объекту Pod. Поле spec.containers[].resources было неизменяемым: попытка поменять его через kubectl edit или kubectl apply отклонялась апи-сервером. Единственный легальный путь — пересоздать под целиком.

Для стейтлесс-сервиса за деплойментом это просто лишний rolling restart. Для всего остального — реальная цена:

Kubernetes API это ограничение обходил кустарно: люди резервировали requests с запасом «на будущее», раздували лимиты «про запас» и мирились с тем, что VPA нельзя включить на что-то чувствительное к рестартам. In-place resize убирает саму причину компромисса — под остаётся тем же подом, с тем же IP, с тем же PID 1 в контейнере, просто с другим cgroup-лимитом вокруг него.

Старый путь пересоздания пода против нового resize-subresource

Что именно стало stable в 1.35

GA в 1.35 «Timbernetes» закрывает три вещи, которые раньше жили за feature gate InPlacePodVerticalScaling (alpha в 1.27, beta в 1.33):

Что всё ещё форсирует рестарт контейнера

In-place resize — это не «любое изменение ресурсов бесплатно». Рестарт контейнера (не пода целиком — под и его IP переживают этот рестарт) остаётся нужен в нескольких случаях:

Какие изменения ресурсов проходят без рестарта, а какие требуют RestartContainer

Живой пример: resize запущенного пода

Патчим память у контейнера, который уже работает, без единого рестарта — под cgroups v2 и resizePolicy: NotRequired:

kubectl get pod worker-0 -o jsonpath='{.status.containerStatuses[0].restartCount}{"\n"}'
# 0

kubectl patch pod worker-0 --subresource resize --patch \
  '{"spec":{"containers":[{"name":"worker","resources":{"requests":{"memory":"512Mi","cpu":"250m"},"limits":{"memory":"1Gi","cpu":"1"}}}]}}'
# pod/worker-0 patched

kubectl get pod worker-0 -o jsonpath='{.status.containerStatuses[0].restartCount}{"\n"}'
# 0

kubectl get pod worker-0 -o jsonpath='{.status.containerStatuses[0].resources}{"\n"}'
# {"limits":{"cpu":"1","memory":"1Gi"},"requests":{"cpu":"250m","memory":"512Mi"}}

restartCount не сдвинулся — значит, это действительно был in-place resize, а не быстрый рестарт, который легко спутать с ним по логам. Если бы на узле физически не хватило места под новый request, status.conditions под именем PodResizePending показал бы reason: Deferred вместо мгновенного применения — kubelet держит желаемое значение в spec, но не трогает cgroup, пока место не освободится (или пока resize не отменят обратным патчем).

Интеграция с Vertical Pod Autoscaler

VPA получил третий режим обновления — updateMode: InPlaceOrRecreate — который приоритетно бьёт в resize-subresource и только при неудаче откатывается на старое поведение:

apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
  name: worker-vpa
spec:
  targetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: worker
  updatePolicy:
    updateMode: InPlaceOrRecreate
  resourcePolicy:
    containerPolicies:
      - containerName: worker
        minAllowed:
          cpu: 100m
          memory: 256Mi
        maxAllowed:
          cpu: 2
          memory: 4Gi

Порядок действий VPA под этим режимом:

  1. Recommender посчитал новую цель — пробуем pods/resize на текущем поде.
  2. Kubelet принял: применяется на месте, restartCount не растёт, под не покидает узел.
  3. Kubelet отклонил как Infeasible (новый request физически не влезает на текущий узел) или resizePolicy требует рестарта контейнера, а разница слишком велика — VPA откатывается на старое поведение: evict пода и пересоздание с новыми значениями, как раньше делал updateMode: Recreate.

Для оператора это значит, что InPlaceOrRecreate почти всегда безопаснее чистого Recreate — но не убирает необходимость мириться с редким пересозданием на переполненных узлах.

Что это меняет у scheduler и cluster-autoscaler

Самый тонкий эффект GA — не в самом резайзе, а в том, что происходит с уже прошедшим scheduling решением, когда requests меняются постфактум:

Сравнение: было / стало

ПараметрДо in-place resize (≤1.26 и без feature gate)С GA in-place resize (1.35+)
Изменение CPU у running-подапересоздание пода целикомresize-subresource, без рестарта контейнера
Изменение memory limit вверх (cgroups v2)пересоздание пода целикомresize-subresource, без рестарта контейнера
Изменение memory limit вниз (cgroups v1)пересоздание пода целикомвсё ещё требует RestartContainer
IP пода / локальный кэш процессатеряются при пересозданиисохраняются — под не покидает узел
VPA update modeOff / Initial / Recreate+ InPlaceOrRecreate с фолбэком на recreate
Смена QoS-класса ресайзомневозможна (весь под пересоздаётся с новым классом)по-прежнему невозможна — апи-сервер отклоняет патч
Влияние на schedulerновое scheduling-решение при каждом изменениибез пересмотра, kubelet сверяет допустимость локально

Что нужно, чтобы включить это у себя

Как проверить, что резайз действительно прошёл in-place

kubectl get pod worker-0 -o jsonpath='{.status.containerStatuses[0].restartCount}{"\n"}'
# restartCount не изменился — значит, resize применился без пересоздания контейнера

kubectl get pod worker-0 -o jsonpath='{.status.conditions[?(@.type=="PodResizePending")]}{"\n"}'
# пусто, если resize применился сразу; {"reason":"Deferred",...} — если под ждёт места на узле

kubectl get pod worker-0 -o jsonpath='{.status.containerStatuses[0].resources}{"\n"}'
# фактические cpu/memory limits и requests, применённые в cgroup прямо сейчас

Если restartCount вырос сразу после патча — resize сработал через RestartContainer, а не через тихое применение на лету; проверьте resizePolicy контейнера и то, на каком cgroup-драйвере находится узел.

Итог

In-Place Pod Resize закрывает целый класс ненужного pod churn’а — но только там, где вы заранее проверили, что под ним действительно cgroups v2 и современный CRI, а не унаследованный узел, который тихо откатит фичу на рестарт именно тогда, когда это будет незаметно. Если ваш стек полагается на рестарт контейнера как побочный способ сбросить утёкшую память — VPA в режиме InPlaceOrRecreate эту привычку не одобрит, он просто перестанет её вызывать без необходимости. Прежде чем включать resize повсеместно, стоит явно решить, где resizePolicy должен остаться RestartContainer, а не унаследовать дефолт молча.


Поделиться:

Следующая статья
Helm по-взрослому: OCI-реестры, зависимости и чарты без боли