На моем последнем проекте я столкнулся с проблемой: приложения Java внутри Linux Docker-контейнеров потребляли очень много памяти. Например, некоторые из наших микросервисов (на основе Spring Boot) внутри контейнера могли использовать до двух гигабайт памяти, тогда как при обычном запуске вне контейнера показатель был в районе 200-300 мегабайт.
После недолгого гугления оказалось, что когда мы запускаем приложения как "java -jar mypplication-fat.jar", то JVM по умолчанию приспосабливается к среде выполнения, в которой она работает и таким образом обеспечивается максимально возможное быстродействие java-программ.
CGroups и старые приложения
В Docker-контейнерах, как и в виртуальных машинах (Virtual Machines, VM), можно разграничивать ресурсы, выделять количество ядер и виртуальной памяти. При этом контейнеры обладают отличной изоляцией ресурсов (CPU, память, файловые системы), и один контейнер совершенно ничего не знает о других. Таким образом контейнеры не мешают работе друг друга, и мы можем запускать десятки контейнеров с разными переменными средами (environment) и конфигурациями. Такая изоляция возможно только благодаря возможности ядра Linux — cgroups.
Однако некоторые из приложений были разработаны задолго до появления cgroups. Такие инструменты, как 'top', 'ps', 'free', а также и JVM, не оптимизированы для эффективной работы в контейнерах.
Ограничим память
Первое, что может прийти на ум — это ограничить размер самого Docker-контейнера. Создадим простое приложение на Spring Boot и запустим контейнер, поставив ограничение памяти 150 мегабайт. Вот пример команды на хосте с одним гигабайтом памяти:
В приложении заранее был создан rest-метод, который загружает память до отказа объектами типа String. Вызовем его:
Вызванный метод возвращает что-то вроде "Выделено более чем 80% памяти (219.8 MiB) из максимально доступных JVM (241.7 MiB) ".
Но отсюда два вопроса:
1. Почему максимальный размер для JVM 241 мегабайт?
2. Если мы выше указали, что контейнеру доступно только 150 мегабайт памяти, то почему приложение потребляет почти 220?
Первое, что нужно помнить о максимальном размере кучи (max heap size) у JVM, это то, что размер по умолчанию будет занимать 1/4 памяти. И второе — когда мы при запуске контейнера выше указывали флаг "-m 150M", то докер зарезервировал 150 мегабайт в оперативной памяти и 150 мегабайт в Swap. Поэтому-то приложение и не рухнуло и отработало как нужно. Но при этом вы должны понимать, что увеличение памяти в контейнере — это не решение проблемы. Всегда может случиться ситуация, когда Java-приложение может выйти за рамки лимита, и тогда процесс будет убит.
Решение проблемы
Решений проблем может быть несколько, но мы поговорим о лучшем, на мой взгляд, способе.
Начиная с JDK 8u131+ и JDK 9 нам дали возможность использовать экспериментальные возможности JVM, которые позволяют нам обращаться к возможностям вышеупомянутых CGroups. Одна из этих возможностей — это настроить JVM так, чтобы она автоматически определяла максимальное возможное потребление памяти с помощью опции -XX:+UseCGroupMemoryLimitForHeap. Попробуем:
JVM прочитала, что максимум доступно памяти 600 мегабайт, и создала JVM с максимальным размером памяти примерно 150 мегабайт. Ровно четверть, как и говорилось выше.
Ну и как пример, мое приложение на работе без ограничений использовало 1.7 гигабайта памяти, после того, как я использовал экспериментальные фичи и ограничил размер контейнера до 512 мегабайт — 331 мегабайт.
Кроме того, ограничение памяти контейнеров таким образом помогает легко и быстро проводить нагрузочное тестирование, чтобы узнать о возможностях своего детища.
Материал подготовлен и частично переведен при помощи статьи с developers.readhat.com
Так и должно быть)
мне кажется такой контент на хабр лучше. не?
мне просто было пофиг, скучно, поддержал мертвый раздел на tj
имхо он мертворожденный. если было бы что-то околопрограмминга, то может и зашло бы
Можно мне, как ламеру, сказать, чем отличаются докеры от каких-нибудь флетпаков и снапов?
Комментарий недоступен
Комментарий недоступен
тем что он везде
и я трахаюсь с ним уже третий день пытаясь развернуть cockpit cms на azure web app
Как я понял, снапы работают и взаимодействуют с хостом напрямую, имеют доступы к памяти, фс и прочие плюшки. В то время как контейнеры сами в себе- работают в докере, ограничены во всем, довольствуются только своей песочницей.
Комментарий недоступен
Ну я упрощенно.
на линухе уже давно контейнеры нативными стали, изолирован лишь контекст.
а ресурсы и возможности - общие
А кто чистый докер в продакшене юзает?
это не продакшен, кто сказал такое?