Kubernetes, бросающий OOM для стручков, управляющих JVM - PullRequest
0 голосов
/ 01 октября 2018

Я использую контейнеры Docker, содержащие JVM (java8u31).Эти контейнеры развертываются как модули в кластере kubernetes.Часто я получаю ООМ за капсулы, а Кубернетес убивает капсулы и перезапускает их.У меня проблемы с поиском основной причины этих OOM, поскольку я новичок в Kubernetes.

  1. Вот параметры JVM

    -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap -Xms700M -Xmx1000M  -XX:MaxRAM=1536M  -XX:MaxMetaspaceSize=250M 
    
  2. Эти контейнеры развертываются как набор с сохранением состояния, и далее следует распределение ресурсов

    resources:
        requests:
            memory: "1.5G"
            cpu: 1
        limits:
            memory: "1.5G"
            cpu: 1
    

    , поэтому общая память, выделенная для контейнера, соответствует MaxRam

  3. Если я использую -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/etc/opt/jmx/java_pid%p.hprof это не помогает, потому что модуль убивается, воссоздается и запускается, как только появляется OOM, поэтому все внутри модуля теряется

    Единственный способ получить поток или дамп HEAP - это SSHв модуль, который я также не могу взять, потому что модуль воссоздается после OOM, поэтому я не получаю след памяти во время OOM.Я использую SSH после OOM, что не очень помогает.

  4. Я также профилировал код, используя visualVM, jHat, но не смог найти значительный объем памяти, который мог привести к выводу слишком большого объема памятипотребление потоками, работающими в JVM, или возможная утечка.

Любая помощь приветствуется для разрешения OOM, выброшенного Kubernetes.

Ответы [ 3 ]

0 голосов
/ 03 октября 2018

Спасибо @VAS за ваши комментарии.Спасибо за ссылки на kubernetes.

После нескольких тестов я думаю, что не рекомендуется указывать XMX, если вы используете -XX: + UseCGroupMemoryLimitForHeap, поскольку XMX переопределяет его.Я все еще делаю еще несколько тестов и профилирование.

Поскольку мое требование - запуск JVM внутри док-контейнера.Я сделал несколько тестов, как упомянуто в сообщениях @Eugene.Учитывая, что каждому приложению, работающему внутри JVM, потребуются HEAP и некоторая собственная память, я думаю, что нам нужно указать -XX: + UnlockExperimentalVMOptions, XX: + UseCGroupMemoryLimitForHeap, -XX: MaxRAMFraction = 1 (учитывая только JVM, работающую внутри контейнера, вв то же время это рискованно) -XX: MaxRAM (я думаю, что мы должны указать это, если MaxRAMFraction равен 1, так что вы оставляете часть для собственной памяти)

Несколько тестов:

Согласно конфигурации докера ниже,докеру выделяется 1 ГБ, учитывая, что внутри контейнера работает только JVM.Учитывая распределение докера под 1G, и я также хочу выделить часть памяти процесса / собственной памяти, я думаю, что я должен использовать MaxRam = 700M, чтобы у меня было 300 МБ для собственной.

$ docker run -m 1GB openjdk:8u131 java -XX: + UnlockExperimentalVMOptions -XX: + UseCGroupMemoryLimitForHeap -XX: MaxRAMFraction = 1 -XX: MaxRAM = 700M -XshowSettings: vm -version Настройки VM: макс.Размер кучи (расчетный): 622,50M Эргономика Класс компьютера: сервер Использование виртуальной машины: 64-разрядный сервер OpenJDK Виртуальная машина

Теперь указание XX: MaxRAMFraction = 1 может убивать:

ссылки: https://twitter.com/csanchez/status/940228501222936576?lang=en Является ли -XX: MaxRAMFraction = 1 безопасным для производства в загрязненной среде?

Было бы лучше, обратите внимание, что я удалил MaxRAM, поскольку MaxRAMFraction> 1:

$ docker run -m 1GB openjdk: 8u131 java -XX: + UnlockExperimentalVMOptions -XX: + UseCGroupMemoryLimitForHeap -XX: MaxRAMFraction = 2 -XshowSettings: vm -version Параметры виртуальной машины: Макс.Размер кучи (расчетный): 455.50M Эргономика Машинный класс: сервер Использование виртуальной машины: 64-битный сервер OpenJDK ВМ

Это дает оставшиеся 500M для собственного, например, можно использовать для MetaSpace, указав -XX: MaxMetaspaceSize:

$ docker run -m 1ГБ openjdk: 8u131 java -XX: + UnlockExperimentalVMOptions -XX: + UseCGroupMemoryLimitForHeap -XX: MaxRAMFraction = 2 -XX: MaxMetaspaceSize = 200M -XshowSettings: параметры настройки: vm.Размер кучи (расчетный): 455.50M Эргономика Класс компьютера: сервер Использование виртуальной машины: 64-битный сервер OpenJDK VM

Логически, а также в соответствии с приведенными выше ссылками имеет смысл указать -XX: MaxRAMFraction> 1.Это также зависит от выполненного профилирования приложения.

Я все еще делаю еще несколько тестов, обновлю эти результаты или опубликую.Спасибо

0 голосов
/ 13 ноября 2018

Если вы можете работать на Java 11 (или 10) вместо 8, параметры ограничения памяти будут значительно улучшены (плюс JVM поддерживает cgroups).Просто используйте -XX:MaxRAMPercentage (диапазон 0.0, 100.0):

$ docker run -m 1GB openjdk:11 java -XshowSettings:vm -XX:MaxRAMPercentage=80 -version
VM settings:
    Max. Heap Size (Estimated): 792.69M
    Using VM: OpenJDK 64-Bit Server VM

openjdk version "11.0.1" 2018-10-16
OpenJDK Runtime Environment (build 11.0.1+13-Debian-2)
OpenJDK 64-Bit Server VM (build 11.0.1+13-Debian-2, mixed mode, sharing)

Таким образом, вы можете легко указать 80% доступной памяти контейнера для кучи, что было невозможно при старых параметрах.

0 голосов
/ 02 октября 2018

Когда ваше приложение в модуле достигает пределов памяти, которые вы установили с помощью resources.limits.memory или ограничения пространства имен, Kubernetes перезапускает модуль.

Часть ограничения ресурсов Kubernetes описана в следующих статьях:

Объем памяти, используемой приложением Java, не ограничен размером кучи, которую можно установитьуказав параметры:

-Xmssize Specifies the initial heap size.
-Xmxsize Specifies the maximum heap size.

Приложению Java требуется дополнительная память для метас пространства, пространства классов, размера стека, а самой JVM требуется еще больше памяти для выполнения таких задач, как сборка мусора, оптимизация JIT, автономная кучараспределения, код JNI.Трудно предсказать общее использование памяти JVM с разумной точностью, поэтому лучший способ - измерить ее при реальном развертывании с обычной нагрузкой.

Я бы порекомендовал установить ограничение на количество модулей в Kubernetes, равное двойному Xmx размер, проверьте, не получаете ли вы больше OOM, а затем постепенно уменьшайте его до того уровня, когда вы начинаете получать OOM.Окончательное значение должно быть посередине между этими точками.
Вы можете получить более точное значение из статистики использования памяти в системе мониторинга, такой как Prometheus.

С другой стороны, вы можете попытаться ограничить Java-памятьиспользование путем указания количества доступных параметров, например:

-Xms<heap size>[g|m|k] -Xmx<heap size>[g|m|k]
-XX:MaxMetaspaceSize=<metaspace size>[g|m|k]
-Xmn<young size>[g|m|k]
-XX:SurvivorRatio=<ratio>

Подробнее об этом можно найти в следующих статьях:

Второй способ ограничения использования памяти JVMрассчитать размер кучи на основе объема оперативной памяти (или MaxRAM).Хорошее объяснение того, как это работает, можно найти в статье :

Размеры по умолчанию основаны на объеме памяти на машине, который можно установить с помощью -XX:MaxRAM=N флаг.Обычно это значение вычисляется JVM путем проверки объема памяти на машине.Однако JVM ограничивает MaxRAM до 1 GB для клиентского компилятора, 4 GB для 32-разрядных серверных компиляторов и 128 GB для 64-разрядных компиляторов.Максимальный размер кучи составляет одну четверть MaxRAM.Вот почему размер кучи по умолчанию может варьироваться: если физическая память на машине меньше MaxRAM, размер кучи по умолчанию составляет одну четверть от этого.Но даже если доступны сотни гигабайт оперативной памяти, JVM будет использовать по умолчанию 32 GB: четверть 128 GB.Расчет максимального значения кучи по умолчанию на самом деле таков:

Default Xmx = MaxRAM / MaxRAMFraction

Следовательно, максимальный размер кучи по умолчанию также можно установить, изменив значение флага - XX:MaxRAMFraction=N, по умолчанию 4.Наконец, просто для интереса можно также установить флаг -XX:ErgoHeapSizeLimit=N на максимальное значение по умолчанию, которое должна использовать JVM.Это значение 0 по умолчанию (то есть игнорировать его);в противном случае этот предел используется, если он меньше MaxRAM / MaxRAMFraction.

Исходный выбор размера кучи аналогичен, хотя и имеет меньше сложностей.Начальное значение размера кучи определяется следующим образом:

Default Xms = MaxRAM / InitialRAMFraction

Как можно заключить из минимальных размеров кучи по умолчанию, значение по умолчанию для флага InitialRAMFraction равно 64.Одно предупреждение здесь возникает, если это значение меньше 5 MB - или, строго говоря, меньше значений, указанных -XX:OldSize=N (по умолчанию 4 MB) плюс - XX:NewSize=N (по умолчанию 1 MB),В этом случае в качестве начального размера кучи используется сумма старого и нового размеров.

В этой статье вы можете начать настройку JVM для веб-приложения:

...