Kubernetes 1.33: тестируем InPlace Pod Resizing на JVM без рестарта
Kubernetes 1.33 выводит InPlace Pod Resizing в stable. Проверяем масштабирование памяти JVM-приложений без рестарта пода и смотрим что реально изменилось в uptime.
Kubernetes 1.33: InPlace Pod Resizing стабилен, улучшения планировщика для GPU-нагрузок
Kubernetes 1.33 вышел на прошлой неделе, и для нас главная новость - InPlace Pod Resizing перешёл в stable. Это фича, которую хотелось протестировать давно: возможность изменять CPU и память пода без его пересоздания. Звучит просто, но для JVM-приложений это меняет довольно много - у нас есть живой кейс, где это стало актуально.
Что за фича и почему она долго шла к stable
InPlace Pod Resizing появился в виде alpha в 1.27 и с тех пор несколько раз переделывался. Основная идея: вы меняете resources.requests и resources.limits у работающего пода через kubectl patch или через изменение деплоймента, и kubelet применяет новые значения без перезапуска контейнера. Cgroups пересчитываются на лету, процессы внутри продолжают работать.
Почему это важно именно для JVM? Heap JVM выставляется через -Xmx при старте процесса. Если памяти в поде стало больше, JVM об этом не узнаёт - она продолжает работать с тем же heap. Но если вы используете -XX:+UseContainerSupport (а в современных образах это дефолт), то JVM читает лимиты cgroup при старте и выставляет heap пропорционально. После InPlace Resize cgroup лимит поменялся - и GC получает больше пространства за счёт расширенного cgroup-лимита, хотя сам -Xmx без рестарта не пересчитывается. Именно это сочетание мы и проверяли.
Кейс: сервис обработки очереди с непредсказуемым пиком
У одного из клиентов на managed-сопровождении есть Java-сервис, который обрабатывает очередь входящих документов. Нагрузка неравномерная: большую часть времени - фоновый режим, но периодически прилетает пачка из нескольких тысяч документов и сервис начинает интенсивно парсить и складывать в базу. В такие моменты GC начинал давать о себе знать - паузы росли, throughput проседал.
Раньше управляли через HPA по CPU: под масштабировался горизонтально. Это работало, но не идеально - поднять новый под с JVM-приложением занимает время, пока прогреется кеш и инициализируются пулы соединений. В пиковые моменты первые несколько минут сервис всё равно работал медленнее чем хотелось бы.
Идея была попробовать вертикальное масштабирование памяти на лету: при росте нагрузки - увеличить лимит памяти пода, дать JVM больше heap под кеш и рабочие объекты, и посмотрит ли это на паузы GC.
Как настраивали
Несколько обязательных условий для работы InPlace Resizing в 1.33:
resizePolicyв контейнере - поле, которое говорит kubelet как реагировать на изменение каждого ресурса. Мы поставилиmemory: RestartNotRequiredиcpu: RestartNotRequired. Это значит, что при изменении этих ресурсов kubelet попытается обновить cgroup без рестарта контейнера.containerSupportв JVM - у насeclipse-temurin:17, там он включён по умолчанию. Проверили черезjava -XshowSettings:all -version 2>&1 | grep -i cgroup.- JVM-флаг
-XX:+ActiveProcessorCountне трогали - для CPU resizing JVM тоже умеет подхватывать изменения, но это отдельная история.
Сам resize выглядит просто - патчим pod spec:
kubectl patch pod <pod-name> --subresource=resize -p \
'{"spec":{"containers":[{"name":"app","resources":{"limits":{"memory":"4Gi"}}}]}}'
Статус применения видно через kubectl get pod <pod-name> -o jsonpath='{.status.resize}' - там будет Proposed, InProgress или Accepted.
Что увидели в реальности
Изменение лимита применяется за секунды. Kubelet реагирует быстро, статус переходит в Accepted без задержек. Это приятно.
GC реагирует на изменение cgroup. После увеличения memory.limit_in_bytes в cgroup G1GC получает больше пространства для работы - давление на кучу снижается, паузы становятся короче. MaxHeapSize при этом не меняется без рестарта процесса: -Xmx зафиксирован при старте JVM, и jcmd <pid> VM.flags | grep MaxHeapSize внутри контейнера это подтверждает.
Паузы GC действительно уменьшились в пиковые периоды - GC стал меньше работать в условиях постоянного давления на heap. Без точных цифр говорить сложно, но качественно картина изменилась.
Один неочевидный момент. Уменьшение лимита памяти - обратная операция - работает не так гладко. Если JVM уже использует память близко к новому (меньшему) лимиту, kubelet зафиксирует Infeasible статус и откажется применять изменение. Это корректное поведение: лучше не применить, чем убить pod OOM. Но надо учитывать при автоматизации: нельзя уменьшить лимит ниже текущего фактического потребления.
GPU-планировщик: меньше заметно, но полезно
В 1.33 также поменяли логику планировщика для нод с GPU - теперь он лучше учитывает топологию NUMA и разницу между GPU-моделями при размещении подов. Для нас это пока теоретически: GPU-кластеры у клиентов есть, но там мы недавно перешли на DRA после 1.32. Понаблюдаем за следующий месяц - планировщик должен давать чуть лучшее placement по NUMA.
Что дальше с InPlace Resizing
Стабильный статус означает, что API не изменится. Это важно - можно строить автоматизацию. Очевидный следующий шаг - интеграция с VPA (Vertical Pod Autoscaler): VPA уже умеет рекомендовать значения ресурсов, и InPlace Resizing позволяет применять эти рекомендации без рестарта. На момент 1.33 эта интеграция работает в eksperimental-режиме VPA, надо мониторить.
Для JVM-сервисов с непредсказуемой нагрузкой это комбинация выглядит перспективно: VPA собирает статистику и рекомендует, InPlace Resizing применяет без прерывания обслуживания. Горизонтальное масштабирование остаётся для действительно сильных пиков, вертикальное - для сглаживания обычных колебаний.
Если у вас JVM-сервисы под managed-управлением и интересно попробовать - пишите, обсудим.
- Kubernetes 1.32: DRA стабилен, смотрим что это меняет для GPU-нод · 20 февраля 2025
- Apache Kafka 4.0: мигрируем последний кластер клиента с ZooKeeper на KRaft · 10 апреля 2025