MAATRIX / Блог / tmpfs на /run вырос до 8 ГБ и положил сервер за сутки

tmpfs на /run вырос до 8 ГБ и положил сервер за сутки

MAATRIX

Сервер работал нормально, а потом за сутки съел всю оперативную память и начал агрессивно убивать процессы одним за другим. Обычная проверка «кто ест память» ничего не показывала — ни один процесс не рос заметно, top и htop выглядели спокойно. Разгадка нашлась не в списке процессов, а в df -h: директория /run была смонтирована как tmpfs и физически лежала в оперативной памяти — и именно она разрослась до 8 гигабайт. Разберём, как это диагностировать и как защититься, чтобы один процесс не мог случайно съесть весь сервер через безобидную с виду директорию.

Первые симптомы: память тает, а виновника нет

Инцидент начался буднично: мониторинг прислал алерт про рост использования памяти, но без единого триггера — просто линия на графике free -h ползла вверх час за часом. К вечеру свободной памяти оставалось меньше 10%, ночью система начала подкидывать в лог Out of memory: Killed process, а под утро упал бэкенд-процесс, который к утечке не имел никакого отношения — ему просто не хватило памяти в момент запроса.

Первая реакция была стандартной — искать процесс, который жрёт RAM:

free -h
ps aux --sort=-%mem | head -20
top -o %MEM

Картина была подозрительно нормальной. Ни один процесс не занимал сколько-нибудь заметную долю памяти, суммарный RSS всех процессов из ps aux не сходился с тем, что показывал free -h как «used». Разница была в несколько гигабайт, и списывать её на кэш страниц (buff/cache) не получалось — free -h честно показывал, что именно "used", а не "cached", растёт день ото дня.

Это классический признак того, что память уходит не в процесс, а куда-то мимо привычных инструментов — либо в ядерные структуры, либо в файловую систему, которая физически живёт в RAM. Второй вариант почти всегда забывают проверить первым, потому что «файловая система» интуитивно ассоциируется с диском.

Почему стандартная диагностика по процессам ничего не нашла

Проблема с диагностикой «по процессам» в том, что она видит только память, которую держит конкретный процесс через свой адресный список (RSS/PSS). Если процесс пишет данные в файл — даже если этот файл физически лежит в оперативной памяти, а не на диске, — эта память с точки зрения ps и top принадлежит файловой системе, а не процессу-писателю. Процесс может давно завершиться, а данные останутся лежать и продолжать занимать RAM.

Именно поэтому важно на старте инцидента проверять не только память процессов, но и общую картину использования RAM ядром:

cat /proc/meminfo | egrep 'MemTotal|MemFree|MemAvailable|Shmem|Cached'

Строка Shmem (shared memory) — это первая подсказка. tmpfs учитывается ядром именно как shared memory, и если Shmem в /proc/meminfo неожиданно велика, это почти наверняка означает, что где-то смонтирован tmpfs-раздел, который распух. В нашем случае Shmem показывал порядка 8 ГБ — ровно столько, сколько потом нашлось в /run.

Штатные средства мониторинга (Zabbix, Prometheus node_exporter с дефолтными дашбордами, стандартные виджеты панелей хостинга) в большинстве конфигураций показывают общую занятую память одним числом и памятью по процессам — но далеко не всегда разбивают её на «страничный кэш / tmpfs / реальные процессы». Поэтому «память кончается, а виновника нет» — это не баг мониторинга, а типичный слепой участок настройки по умолчанию.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

df -h находит то, что не видит htop

Ключевая проверка, которая закрыла вопрос — обычный df -h, только с оглядкой на то, что в списке разделов присутствуют не только диски:

df -h
Filesystem      Size  Used Avail Use% Mounted on
/dev/sda1        40G   18G   20G  48% /
tmpfs           7.8G  7.6G  200M  97% /run
tmpfs           7.8G     0  7.8G   0% /dev/shm
tmpfs           1.6G  1.2M  1.6G   1% /run/user/1000

/run был заполнен на 97% — и это была не диспропорция диска, а буквально та самая недостающая память. df -h показывает tmpfs-разделы в общем списке наравне с обычными дисковыми, но при беглом просмотре легко пропустить строку «tmpfs … /run» глазами, которые привыкли искать /dev/sda или /dev/nvme.

Дальше — найти, что именно занимает место внутри /run:

du -sh /run/* | sort -rh | head -20

Нашёлся один подкаталог, в котором накопилось несколько гигабайт файлов — журналы одного сервиса, который писал туда логи ротацией «раз в час», но без ограничения на суммарный объём и без реальной ротации со сжатием/удалением старых файлов. Каждый час появлялся новый файл, старые не удалялись, и за сутки набежало 8 ГБ.

Техническая суть: почему /run — это оперативная память, а не диск

Здесь стоит остановиться и объяснить механизм, потому что именно непонимание этого момента и стало корнем инцидента. /run (а в некоторых конфигурациях и /tmp, и /dev/shm) во многих дистрибутивах Linux смонтирован не как обычная файловая система на диске, а как tmpfs — специальная псевдо-файловая система, которая хранит все свои «файлы» непосредственно в оперативной памяти сервера, а не на блочном устройстве.

Проверить, что именно смонтировано как tmpfs на конкретной системе, можно так:

mount | grep tmpfs
# или эквивалентно
cat /proc/mounts | grep tmpfs

Типичный вывод:

tmpfs on /run type tmpfs (rw,nosuid,nodev,size=8123456k,mode=755)
tmpfs on /dev/shm type tmpfs (rw,nosuid,nodev)
tmpfs on /tmp type tmpfs (rw,nosuid,nodev,size=2097152k)

Назначение tmpfs — вполне осмысленное. /run хранит PID-файлы служб, сокеты, блокировки и прочие мелкие служебные данные, которые нужны системе только на время работы и должны исчезать при перезагрузке. Хранить их в RAM логично: это даёт очень быстрый доступ и не изнашивает диск бессмысленной мелкой записью. Проблема начинается не с самой идеи tmpfs, а с того, что любые данные, записанные в tmpfs каким-либо процессом, напрямую и совершенно незаметно потребляют реальную оперативную память сервера — рост «файлов» в такой директории в буквальном смысле есть рост потребления RAM, один в один.

Разница с обычным диском принципиальна:

Обычный раздел (ext4/xfs на диске)tmpfs
Где физически хранятся данныеНа блочном устройствеВ оперативной памяти
Что происходит при переполненииДиск заполняется, RAM не затрагиваетсяRAM заполняется напрямую
Что происходит при перезагрузкеДанные сохраняютсяВсе данные исчезают
Заметность роста в мониторинге RAMНе проявляетсяПроявляется как рост used/Shmem

Именно эта незаметность и стала причиной, по которой расследование заняло часы, а не минуты: инженер, привыкший что «диск — это диск, а память — это память», не подозревает, что запись в директорию /run физически идёт мимо диска прямиком в тот же пул RAM, который делят между собой все процессы сервера.

Корневая причина: сервис писал большие данные не в ту директорию

Восстановив цепочку событий, причина оказалась простой и обидной. Один из сервисов на сервере был сконфигурирован писать рабочие логи и временные файлы в путь вида /run/<service>/logs/, потому что так исторически сложилось в его конфиге (или так его настроил предыдущий администратор — конфиг просто скопировали с другого сервера, где та же директория была смонтирована как обычный диск, а не tmpfs). Сервис не делал ничего криминального с точки зрения своей логики — просто писал логи и не удалял старые. На сервере с диском на /run это осталось бы незамеченным годами: логи копились бы на диске, потихоньку съедая место, и это увидели бы через привычный «диск заполнился на 90%».

Но на этом сервере /run был tmpfs — и та же самая логика «пишем логи, не следим за суммарным объёмом» напрямую конвертировалась в исчерпание оперативной памяти. Ни один процесс индивидуально не выглядел виновником утечки, потому что технически утечки памяти в процессе не было вовсе — процесс исправно завершал запись и освобождал свою собственную RSS. Память утекала не в процесс, а в файлы, которые процесс оставлял после себя в tmpfs.

Это принципиально другой класс проблемы, чем классическая утечка памяти в приложении — там нужно ловить утечку по графику потребления самого процесса, а здесь виновата не сама программа, а директория, в которую она пишет. Похожая ловушка встречается и с обычными дисками, когда логи незаметно съедают всё место — разница лишь в том, что там страдает диск, а здесь напрямую страдает RAM, и убивает сервер это заметно быстрее.

Как это чинили и как защититься от повтора

Первое и самое срочное действие в момент инцидента — освободить память, удалив накопленные файлы прямо из tmpfs (это безопасно, потому что данные в /run и так не переживают перезагрузку, они не предназначены для долгого хранения):

# посмотреть, что конкретно жрёт место
du -sh /run/<service>/* | sort -rh | head

# удалить устаревшие файлы конкретного сервиса
rm -f /run/<service>/logs/*.log.old

После освобождения места сервис нужно перенастроить писать логи туда, где им положено быть — на обычный диск с нормальной ротацией:

# пример: переносим путь логов сервиса на диск
# было:  log_dir = /run/myservice/logs
# стало: log_dir = /var/log/myservice

и подключить logrotate, если его не было:

/var/log/myservice/*.log {
    daily
    rotate 7
    compress
    missingok
    notifempty
}

Второй и более важный шаг — не дать одному ошибочно настроенному сервису повторить это в будущем, ограничив максимальный размер tmpfs-раздела при монтировании. Для /run это можно сделать через /etc/fstab:

tmpfs   /run   tmpfs   defaults,size=256M,mode=755   0   0

и применить без перезагрузки:

mount -o remount,size=256M /run

На системах с systemd раздел /run часто монтируется собственным юнитом run.mount, а не строкой в fstab — тогда лимит задаётся через параметр RuntimeDirectorySize= в /etc/systemd/system.conf (действует на директории, которые systemd создаёт под /run для сервисов) либо явным переопределением unit-файла точки монтирования. В любом случае идея одна: заранее ограничить, сколько оперативной памяти может съесть конкретная tmpfs-директория, вместо того чтобы полагаться на «там и так мало данных должно быть» — именно это допущение и подвело в разобранном инциденте.

Разумный размер лимита зависит от того, что реально живёт в /run на вашем сервере — обычно это десятки мегабайт на PID-файлы, сокеты и блокировки штатных служб, поэтому лимит в 256-512 МБ уже даёт солидный запас и одновременно жёстко ограничивает ущерб от ошибочной записи. Слишком маленький лимит опасен по-другому: если /run переполнится, часть служб может не подняться из-за невозможности создать PID-файл или сокет — так что лимит стоит выставлять с запасом, а не «в притык».

Мониторинг tmpfs как часть общего мониторинга памяти

Главный вывод инцидента не технический, а организационный: мониторинг памяти «одним числом свободной RAM» недостаточен, если на сервере есть tmpfs-разделы сколько-нибудь значимого размера. Стоит явно добавить в мониторинг заполнение конкретных tmpfs-точек монтирования, а не только агрегированную память:

# быстрая проверка вручную
df -h | grep tmpfs

# для скрипта проверки/алерта — процент заполнения конкретного tmpfs
df --output=pcent /run | tail -1

Простой скрипт для крон-проверки с алертом, если конкретный tmpfs-раздел заполнен больше порога:

#!/bin/bash
THRESHOLD=80
for mnt in /run /tmp /dev/shm; do
  if mountpoint -q "$mnt"; then
    usage=$(df --output=pcent "$mnt" 2>/dev/null | tail -1 | tr -dc '0-9')
    if [ -n "$usage" ] && [ "$usage" -ge "$THRESHOLD" ]; then
      echo "WARNING: $mnt заполнен на ${usage}% (tmpfs, влияет на RAM)"
    fi
  fi
done

Такую проверку стоит повесить рядом с обычным мониторингом свободной памяти и добавить в чеклист диагностики «память кончается» пунктом номер два (сразу после free -h и ps aux --sort=-%mem) — она занимает секунды, а находит именно тот класс проблем, который OOM killer в итоге решает грубой силой, выбирая жертву среди процессов, которые вообще ни при чём. Полезно заодно свериться с тем, как ядро выбирает жертву для OOM killer — в момент паники система убивает не источник проблемы, а случайный процесс с наибольшим oom_score, и это может быть совершенно не тот сервис, что переполнил tmpfs.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

Нужны сами нейросети для контента?

Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.

Частые вопросы

Как быстро проверить, есть ли на сервере проблема с tmpfs, не разбираясь глубоко?

Запустите df -h | grep tmpfs и посмотрите на колонку Use%. Если какой-то tmpfs-раздел (обычно /run, /tmp или /dev/shm) заполнен на десятки процентов и это число растёт со временем — это тревожный признак, даже если процессы по top выглядят спокойно.

Всегда ли /tmp — это tmpfs?

Нет, это зависит от дистрибутива и конкретной конфигурации сервера — на многих системах /tmp остаётся обычной директорией на диске, а как tmpfs монтируется только /run и /dev/shm. Проверяйте конкретно свой сервер командой mount | grep tmpfs, не полагайтесь на предположения по умолчанию.

Можно ли просто увеличить размер tmpfs, чтобы проблема не повторялась?

Это лечит симптом, но не причину, и опасно тем, что переносит риск исчерпания RAM на более поздний срок с большим объёмом накопленных данных. Правильный порядок — сначала найти и перенастроить процесс, который пишет данные не туда, а лимит на tmpfs оставить именно как защитный барьер на случай следующей похожей ошибки, а не как основное решение.

Что произойдёт, если tmpfs-раздел переполнится полностью?

Процессы, пытающиеся писать в него, получат ошибку No space left on device — как при заполнении обычного диска, только источником проблемы будет нехватка оперативной памяти под лимит tmpfs, а не физическое место на накопителе. Это отдельная поломка от общего исчерпания RAM всего сервера, хотя обе могут наступить почти одновременно.

Нужно ли ограничивать размер tmpfs, если на сервере много оперативной памяти и её «и так хватает с запасом»?

Да, потому что смысл лимита не в экономии обычно используемой памяти, а в защите от одного сбойного процесса, который может за часы съесть весь запас через одну-единственную директорию — как и произошло в разобранном инциденте. Запас RAM снижает вероятность, но не исключает сценарий полностью.

Обсудить статью, задать вопрос или начать новую тему

Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.

Перейти в сообщество →