Память, systemd и контейнеры
Один из простейших вопросов, которые вы получаете на собеседовании на ряду с «что такое LA?» – это «как понять, сколько свободной памяти в системе». Вы смело отвечаете что-то вроде «MemFree + Cached + Buffers», и будете правы. Но потом, в «бою», вы, возможно, будете удивлены приходящему OOM Killer при наличии большого количества свободной памяти.
Операционные системы стараются агрессивно кешировать дисковые операции, и это иногда приводит к разнообразным проблемам. Кроме обеспечения скорости чтения при повторениях это ещё и простой механизм доступа к любому байту при чтении с блочных устройств: жёсткий диск и даже SSD до сих пор даёт читать только блоками.
Кстати, начали задумываться о прямом чтении с SSD, но это long term revolution: очень много механизмов придётся переделать, и в мейнстриме мы это увидим, хорошо, если года через два. Слишком много ПО за 40 лет было спроектировано с оглядкой на поблочное чтение. Например, абсолютно все базы данных хранят данные в страницах, кратных размеру блока.
Но мы отвлеклись!
Все ведь в курсе, что при недостатке памяти ОС будет освобождать память из-под кеша под нужды приложения. И теперь, собственно, анекдот!
В одной весьма известной компании решили уместить на одном хосте примерно несколько сотен контейнеров с простой службой – апачом. Контейнер представлял собой образ операционки, почти не урезанный, благо использовался BTRFS со снапшотами. И все сотни контейнеров много места не занимали (если ещё не знаете, погуглите LVM Thin Provision – интересная технология, пригодится).
Но при запуске всех контейнеров к процессам в контейнерах приходит пушной зверёк по имени OOM Killer. Cache size показывался в районе половины памяти. Сначала не поверили, посчитали awk'ом сумму резидентной памяти всех процессов. Совпадает.
Пособирали atop'ом потребление ресурсов системы, нашли много интересного (например, действительно вылезающий в своп апач) и даже немного расслабились. Но проблема не ушла. Попробовали сбросить кеши:
echo 3 > /proc/sys/vm/drop_caches
И увидели что память из-под кеша не освобождается, хотя должна.
И тут стали думать
Проблема была ещё в том, что проявлялось это не сразу, а через час/два/три после рестарта фермы. Долго ли, коротко ли, но проблему мы нащупали. Посмотрели на различия с предыдущей реализации контейнеров.
Новые контейнеры стали стильные-модные-молодёжные - с systemd. С приходом systemd поменялась раскладка файлов по диску (исторический анекдот: «/usr» появился просто потому что диски были маленькие). Кроме объединения «/» и «/usr» стал активно использоваться tmpfs. А в «/tmp» (tmpfs) стал жить ротируемый журнал journald. Ну подумаешь, несколько мегабайт всего, кто заметит. Но эти несколько мегабайт, помноженные на сотни контейнеров — уже значительный кусок памяти. И главное: tmpfs аллоцирует память в кеше.
Решение было тривиальное Поменяли режим сохранения журнала в контейнерах на persistent. И проблемы больше не было, а вы узнали чуть больше о том, что такое «свободная память».
Есть вопрос? Напишите в комментариях!