Kubernetes 1.35 в Deckhouse: in-place pod resizing graduated и что это значит для stateful-нагрузок
Kubernetes 1.35 вышел с graduated in-place pod resizing и обновлённым scheduler framework. Тестируем вертикальный автоскейлинг без рестарта пода на stateful-нагрузках в Deckhouse.
Kubernetes 1.35 вышел с graduated in-place pod resizing и улучшенным scheduler framework
Kubernetes 1.35 вышел несколько дней назад, и в этом релизе наконец-то случилось то, что ждали достаточно долго: in-place pod resizing перешёл в статус graduated. Фича была в alpha с 1.27, потом долго жила в beta с разными оговорками - и вот теперь API считается финальным. Для нас это не абстрактно: есть несколько stateful-нагрузок у клиентов, где вертикальный автоскейлинг с рестартом пода - это боль, и мы давно присматривались к тому, когда можно будет перейти на in-place resize в production.
Deckhouse с поддержкой 1.35 ещё не вышел в stable-канале на момент написания, но мы подняли тестовый кластер на кандидате и потратили пару дней на замеры. Результаты неоднозначные - об этом честно.
Что graduated означает на практике
Graduated в Kubernetes - это не просто маркетинг. Это конкретные гарантии: API не будет ломаться без deprecation notice, фича включена по умолчанию без feature gate, поведение считается стабильным.
Для in-place pod resizing graduated означает несколько вещей:
- Feature gate
InPlacePodVerticalScalingвключён по умолчанию. Больше не нужно явно включать его в kubelet и kube-apiserver - это меняет расчёт при обновлении кластеров, где кто-то мог случайно не добавить gate на все ноды. - API поля
resizeвresourcesтеперь финальные.pod.spec.containers[].resources.requestsможно менять через patch без пересоздания пода - и это официально поддержанный путь. - Статус resize виден в
pod.status.containerStatuses[].resources. Отдельное поле, которое показывает актуальное состояние ресурсов контейнера в отличие от запрошенных.
Поведение при изменении limits vs requests тоже уточнили: если контейнер не принимает изменение без рестарта (это зависит от cgroup-настроек и runtime), pod переходит в статус Infeasible или Deferred, но не рестартует автоматически. Это важно - ниже расскажем, почему.
Scheduler framework: что изменилось
Второй крупный блок в 1.35 - улучшения scheduler framework. Здесь нет одного громкого изменения, скорее набор доработок:
- PreScore плагины теперь получают полный список feasible нод, а не выборку после sampling. Для кастомных scheduler плагинов, которые делают scoring на основе балансировки - это меняет точность.
- QueueingHint API стабилизирован. Это механизм, который позволяет плагину сказать: «вот это событие может разблокировать под из очереди». Раньше он был experimental и не все плагины его реализовывали корректно. Теперь стабилен, и scheduler тратит меньше лишних попыток scheduling на поды, которые всё равно не поместятся.
- Scheduler теперь правильно учитывает resize-статус пода при принятии решений. Под в статусе
Deferred resizeне рассматривается как кандидат на rescheduling только из-за ресурсного несоответствия.
Для большинства кластеров это фоновые улучшения. Для тех, кто пишет кастомные плагины к scheduler или использует VPA активно - заметнее.
Как мы тестировали in-place resize на stateful-нагрузке
Тестовый стенд: кластер на Deckhouse (кандидат с 1.35), нагрузка - PostgreSQL в StatefulSet с одним primary и двумя репликами. Не самый экзотический кейс, но показательный: это именно та нагрузка, где рестарт пода при вертикальном масштабировании болезненен - переключение с primary на реплику и обратно, временная недоступность записей.
Сценарий: меняем requests.cpu для primary с 2 до 4 через patch. Смотрим что происходит.
Что получили:
Изменение применилось без рестарта контейнера. CPU-лимит у cgroup увеличился в течение нескольких секунд. PostgreSQL продолжал принимать запросы без прерывания. В pod.status появился containerStatuses[].resources с новыми значениями.
Это работает. Availability при изменении CPU requests не пострадала.
Где оказалось сложнее:
Memory requests - другая история. Уменьшение requests.memory для контейнера у нас привело к статусу Deferred: kubelet честно сказал, что уменьшить allocation без рестарта не может при текущей конфигурации cgroup v2 и containerd. Это не баг - это задокументированное поведение, но важно понимать: in-place resize работает легко на увеличение, а уменьшение памяти по-прежнему может требовать рестарта.
Ещё один момент: VPA (Vertical Pod Autoscaler) в нашей версии Deckhouse пока что не интегрирован с in-place resize полностью. VPA умеет выдавать рекомендации и применять их, но в нашем тесте он по умолчанию всё равно шёл через eviction и пересоздание пода. Нужно явно настраивать VPA в режиме updateMode: InPlaceOrRecreate, который появился именно в 1.35. Это важный нюанс для тех, кто хочет совместить VPA и in-place без рестартов - надо явно указать режим, само не случится.
Где сейчас
На тестовом стенде картина такая: для CPU-intensive stateful-нагрузок in-place resize работает чисто и availability не трогает. Для сценариев с уменьшением памяти - поведение зависит от конкретного runtime и требует проверки. Мы зафиксировали это как ограничение, которое нужно учитывать при проектировании VPA-политик.
В production на клиентских кластерах будем переходить после выхода stable-версии Deckhouse с 1.35. До этого - только тестовые стенды. После обновления Deckhouse до 1.68 и Kubernetes 1.34 у нас уже есть опыт накатки свежих релизов на stateful-инфраструктуру, так что процесс отработан. Но in-place resize - достаточно серьёзное изменение в поведении kubelet, чтобы не торопиться.
Если резюмировать текущее понимание: graduated - это хороший сигнал, CPU resizing работает, для памяти нужно проверять. Для stateful-нагрузок с репликацией это уже практически применимо, для монолитных stateful-сервисов с большим потреблением памяти - проверяйте на стенде перед включением VPA с in-place режимом.