Олег Карнауховweb developerОбсудить проект

Блог

Как не дать OOM-killer убить сайт на общем сервере

На одной машине живут сайт, Docker с чужим WordPress и раннер CI. Кого ядро убьёт первым при нехватке памяти — и как объяснить ему, что не сайт.

У сайта на VPS редко бывает вся машина. У меня рядом крутится Docker с наследством от прошлого проекта и self-hosted раннер, который собирает сборку. Когда память кончается, ядро выбирает жертву — и по умолчанию выбор ему подсказывают не приоритеты владельца.

Как ядро выбирает жертву

OOM-killer считает для каждого процесса оценку и убивает того, у кого она выше. Оценка растёт с потреблением памяти. Node-процесс с сайтом, который держит кэш документов и рендерит страницы, в этом соревновании — крепкий кандидат.

Особенно неприятно, что сборка на раннере — короткий пик потребления. То есть сайт умирает в момент выкладки нового сайта.

deploy/allgenl-portfolio.servicebash
[Service]
# Память, которую ядро не отбирает под давлением.
MemoryMin=256M

# Смещение оценки OOM: ниже — значит убивать позже.
OOMScoreAdjust=-800

Restart=always
RestartSec=2
MemoryMin защищает нижнюю границу, OOMScoreAdjust снижает вероятность выбора. Первое требует cgroup v2, что на современных дистрибутивах уже по умолчанию.

OOMScoreAdjust принимает значения от -1000 до 1000. Чем меньше значение, тем ниже вероятность, что процесс будет выбран.

systemd.exec — документация по параметрам исполнения

Чем смотреть, а не догадываться

Факт убийства виден в журнале ядра: journalctl -k покажет строку про Out of memory и имя выбранного процесса. Если сайт падал по этой причине, там будет прямое подтверждение, а не догадка.

Потребление под нагрузкой честнее смотреть в момент сборки: запустить выкладку и параллельно следить за free. Средние значения в спокойный час ничего не скажут о пике.