Java внутри Docker: что нужно знать, чтобы не было проблем

В закладки

На моем последнем проекте я столкнулся с проблемой: приложения Java внутри Linux Docker-контейнеров потребляли очень много памяти. Например, некоторые из наших микросервисов (на основе Spring Boot) внутри контейнера могли использовать до двух гигабайт памяти, тогда как при обычном запуске вне контейнера показатель был в районе 200-300 мегабайт.

Ситуация, когда java внутри контейнера съедает очень много памяти

После недолгого гугления оказалось, что когда мы запускаем приложения как "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 мегабайт. Вот пример команды на хосте с одним гигабайтом памяти:

$ docker run -it --rm --name mycontainer150 -p 8080:8080 -m 150M rafabene/java-container:openjdk

В приложении заранее был создан rest-метод, который загружает память до отказа объектами типа String. Вызовем его:

$ curl http://`docker-machine ip docker1024`:8080/api/memory

Вызванный метод возвращает что-то вроде "Выделено более чем 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. Попробуем:

$ docker run -d --name mycontainer8g-jdk9 -p 8080:8080 -m 600M rafabene/java-container:openjdk-cgroup $ docker logs mycontainer8g-jdk9|grep MaxHeapSize size_t MaxHeapSize = 157286400 {product} {ergonomic}

JVM прочитала, что максимум доступно памяти 600 мегабайт, и создала JVM с максимальным размером памяти примерно 150 мегабайт. Ровно четверть, как и говорилось выше.

Ну и как пример, мое приложение на работе без ограничений использовало 1.7 гигабайта памяти, после того, как я использовал экспериментальные фичи и ограничил размер контейнера до 512 мегабайт — 331 мегабайт.

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

Материал подготовлен и частично переведен при помощи статьи с developers.readhat.com

{ "author_name": "Dmitry Soroka", "author_type": "self", "tags": [], "comments": 15, "likes": 26, "favorites": 16, "is_advertisement": false, "subsite_label": "dev", "id": 116103, "is_wide": false, "is_ugc": true, "date": "Thu, 12 Sep 2019 18:16:38 +0300", "is_special": false }
Объявление на TJ
0
15 комментариев
Популярные
По порядку
Написать комментарий...

Первоначальный чайник

3

приложения Java потребляли очень много памяти

Так и должно быть)

Ответить
0

мне кажется такой контент на хабр лучше. не? 

Ответить
7

мне просто было пофиг, скучно, поддержал мертвый раздел на tj

Ответить
0

имхо он мертворожденный. если было бы что-то околопрограмминга, то может и зашло бы

Ответить

Первоначальный чайник

0

Можно мне, как ламеру, сказать, чем отличаются докеры от каких-нибудь флетпаков и снапов?

Ответить
3

Комментарий удален по просьбе пользователя

Ответить
0

Комментарий удален по просьбе пользователя

Ответить

Сегодняшний диод

Первоначальный
0

тем что он везде 

и я трахаюсь с ним уже третий день пытаясь развернуть cockpit cms на azure web app

Ответить
0

Как я понял, снапы работают и взаимодействуют с хостом напрямую, имеют доступы к памяти, фс и прочие плюшки. В то время как контейнеры сами в себе- работают в докере, ограничены во всем, довольствуются только своей песочницей.

Ответить
0

Комментарий удален по просьбе пользователя

Ответить
0

Ну я упрощенно.

Ответить
0

на линухе уже давно контейнеры нативными стали, изолирован лишь контекст.

а ресурсы и возможности - общие

Ответить
0

А кто чистый докер в продакшене юзает?

Ответить
1

это не продакшен, кто сказал такое?

Ответить
Обсуждаемое
Дизайн и архитектура
В Иваново отреставрировали железнодорожный вокзал в стиле конструктивизма — и почти вернули исторический вид
Восстановили колонны, фрески и лавочки, а возвращать оригинальное остекление помогали историки.
Новости
NYT: судно, перевозившее взорвавшуюся в Бейруте аммиачную селитру, затонуло в ливанском порту в 2018 году
Корабль российского бизнесмена Rhosus отбуксировали на 300 метров от склада с селитрой. Он простоял пять лет, прежде чем уйти на дно из-за протечек.
Новости
Предварительные итоги выборов в Белоруссии: Лукашенко набрал 80,23%, Тихановская — 9,9%
Эти цифры «вряд ли» изменятся, считают в Центризбиркоме.
Популярное за три дня
Новости
Белорусы вышли на протесты. В Минске и других городах идут задержания, СМИ сообщают о применении светошумовых гранат
В стране блокируется интернет.
Беларусь
В Минске задержали звукорежиссёра, включившего «Перемен!» на организованном властями празднике
Сначала он перестал выходить на связь, а потом оказалось, что его задержали. Где находится второй звукорежиссёр, всё ещё неизвестно.
Беларусь
Профиль: Светлана Тихановская — филолог и домохозяйка, которая никогда не хотела идти в президенты Белоруссии
Как жена блогера, рассказывавшего о реальной жизни людей в стране, стала символом протеста.

Комментарии