Java і обмеження пам'яті в контейнерах: LXC, Docker та OpenVZ

Нещодавно була опублікована інформативна стаття Метта Вільямса про Java в Docker і існуючих обмеженнях пам'яті. Автор піднімає цікаву тему про приховану проблему обмеження пам'яті, з якої користувачі можуть зіткнутися під час роботи з контейнерами.

Велика кількість репостов і лайків показує, що дана тема досить популярна серед Java-розробників.image

Тому, хотілося б більш детально проаналізувати дану проблему і визначити можливі шляхи її вирішення.

Проблема

Метт описує своє нічний «подорож» в контейнері Docker зі стандартною поведінкою пам'яті JVM. Він виявив, щообмеження RAM відображаються некоректно всередині контейнера. В результаті, додаток Java, або будь-яке інше, бачить загальний обсяг ресурсів оперативної пам'яті, виділеної для всієї хост-машини, а JVM не може вказати, скільки ресурсів було надано батьківського контейнера для роботи. Це призводить до помилки OutOfMemoryError, викликана неправильною поведінкою динамічної пам'яті JVM в контейнері.

Фабіо Кунг, з Heroku, докладно описав основні причини виникнення цієї проблеми у своїй недавній статті "Пам'ять усередині контейнерів Linux. Або чому в контейнері Linux не працює free top?"

Більшість інструментів Linux, надають метрики ресурсів системи, були створені в той час, коли cgroups ще не існували (наприклад: free top, як у procps). Вони зазвичай читають метрики пам'яті з файлової системи proc: /proc/meminfo, /proc/vmstat, /proc/PID/smaps та інших.
На жаль, /proc/meminfo, /proc/vmstat і пр. не знаходяться в контейнерах. Це означає, що вони не управляються cgroup. Вони завжди відображають кількість пам'яті хост-системи (фізичної або віртуальної машини) в цілому, що є марним для сучасних контейнерів Linux (Heroku, Docker тощо). Процеси всередині контейнера, необхідні для визначення кількості пам'яті, необхідної їм для роботи, не можуть покладатися на free, top та ін; вони підлягають обмеженням, що накладаються cgroups і не можуть використовувати всю наявну пам'ять хост-системи.
Автор підкреслює важливість видимості меж реальної пам'яті. Це дозволяє оптимізувати роботу додатків і усунути проблеми всередині контейнерів: витік пам'яті, використання підкачки, зниження продуктивності і т. д. Крім того, в деяких випадках покладаються на вертикальне масштабування для оптимізації використання ресурсів всередині контейнерів шляхом автоматичної зміни кількості робочих програм, процесів або потоків. Вертикальне масштабування зазвичай залежить від кількості пам'яті, наявної в конкретному контейнері, тому обмеження повинні бути видні всередині контейнера.

Рішення

Спільнота «Відкриті контейнери» ініціює роботи поліпшенню runC для заміщення файлів /proc. LXC також створює файлову систему lxcfs, яка дозволяє контейнерів мати віртуалізовані файлові системи cgroup і виртуализованный вид файлів /proc. Так що це питання знаходиться під пильною увагою системних адміністраторів контейнера. Я вважаю, що згадані удосконалення можуть допомогти вирішити цю проблему на базовому рівні.

Ми також зіткнулися з тією ж проблемою в Jelastic і вже знайшли способи її рішення для наших користувачів. Тому ми хотіли б розповісти деталі реалізації.

Перш за все, давайте повернемося до майстра установки Jelastic, виберемо провайдера послуг для тестової облікового запису і створимо контейнер Java Docker з наперед заданими обмеженнями пам'яті — наприклад, 8 клаудлет, які еквівалентні 1 Гб оперативної пам'яті.

image

Перейдіть до Jelastic SSH gate (1), вибрати раніше створену тестову середу (2), і виберіть контейнер (3). Перебуваючи всередині, можете перевірити доступну пам'ять з допомогою інструменту free (4).

image

Як ми можемо бачити, обмеження пам'яті дорівнює 1 Гб, певного раніше. Тепер перевіримо інструмент top.

image

Все працює належним чином. Для подвійної перевірки, ми повторимо тест Метта, пов'язаного з питанням евристичного поведінки Java, описаного в його статті.

image

Як і слід було очікувати, ми отримуємо MaxHeapSize = 268435546 (~ 256 Мб), що становить 1/4 від оперативної пам'яті контейнера згідно зі стандартною поведінкою динамічної пам'яті Java.

У чому секрет нашого рішення? Звичайно ж, у правильному поєднанні «інгредієнтів». В нашому випадку, це поєднання технологій OpenVZ і Docker, яке дає більший контроль з точки зору безпеки та ізоляції, а також можливість використовувати такі функції як жива міграція і гібернація контейнерів. Нижче наведена високорівнева схема контейнера Docker в Jelastic.

image

У OpenVZ кожен контейнер має віртуалізований вид псевдо-файлової системи /proc. Зокрема, /proc/meminfo всередині контейнера є «спеціальною» версією, що показує інформацію про кожному контейнері, а не хоста. Тому, коли такі інструменти, якtop та free працюють усередині контейнера, вони показують оперативну пам'ять і використання своп з обмеженнями, специфічними для даного конкретного контейнера.

Варто відзначити, що своп всередині контейнерів не реальний, а віртуальний (звідси і назва всієї технології — VSwap). Основна ідея полягає в тому, що коли контейнер з активованим VSwap перевищує задане обмеження оперативної пам'яті, деяка частина з його пам'яті переходить у так званий кеш свопу. Ніякого реального перекачування не відбувається, а це означає, що немає необхідності у введенні/виведенні, якщо, звичайно ж, немає глобальної нестачі оперативної пам'яті. Крім того, контейнер, який використовує VSwap, і має перевищення обмеження оперативної пам'яті, «карається» уповільненням, зсередини це виглядає, як ніби відбувається реальна підкачка. Ця технологія призводить до контролю пам'яті контейнера і використання свопу.

Така реалізація дозволяє запускати Java та інші системи без необхідності адаптувати програми під Jelastic PaaS. Але якщо ви не використовуєте Jelastic, можливим обхідним шляхом буде вказувати розмір динамічної пам'яті для віртуальної машини Java і не залежати від евристики (відповідно радам Метта). Для інших мов потрібно більш глибоке дослідження. Будь ласка, зв'яжіться з нами, якщо ви можете поділитися своїм досвідом у цьому напрямку, і ми будемо раді розширити цю статтю.
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.