Диск на боевом сервере забился под завязку, сервисы начали падать, а виновником оказались логи, которые годами никто не ограничивал. История настолько типичная, что давно превратилась в фольклор системного администрирования. И почти всегда в её основе лежит непонимание того, как на современном Linux устроено хранение журналов и почему здесь работают сразу два механизма, а не один.
systemd-journald и rsyslog живут на одной машине бок о бок, и это не дублирование, а разделение труда. Один собирает всё подряд в структурированный бинарный формат с быстрым поиском, второй умеет раскладывать сообщения по текстовым файлам, фильтровать их по правилам и гнать на удалённый сервер. Вопрос для продакшена не в том, кто из них лучше, а в том, кто за что отвечает, сколько это стоит по диску и производительности, и где поставить ограничители, чтобы не повторить историю с забитым разделом. Разберём по порядку.
Почему на одной машине крутятся сразу два механизма логирования
Чтобы принимать решения о хранении, нужно понимать поток данных. journald это первичный сборщик. Он принимает сообщения от ядра, от ранней загрузки до монтирования файловой системы, от стандартных потоков вывода всех сервисов под управлением systemd и от обычных syslog-сообщений. Всё это складывается в индексированный бинарный журнал. journald собирает данные из ядра, ранних сообщений загрузки, стандартных потоков вывода сервисов и syslog-сообщений и хранит всё в структурированном бинарном формате, быстром для запросов.
rsyslog в этой связке стоит ниже по течению. Он читает сообщения, которые journald отдаёт через специальный сокет, прогоняет их через свои правила и пишет в привычные текстовые файлы в каталоге журналов, а также умеет пересылать на удалённый сервер. По мере сбора логов journald одновременно пишет их в сокет, обычно расположенный по пути сокета пересылки systemd, откуда rsyslog забирает их своим входным модулем чтения журнала. Картина потока выглядит так.
Ядро ─┐
Сервисы systemd ─┼─→ journald ─→ бинарный журнал (/var/log/journal)
syslog-сообщения─┘ │
└─→ rsyslog ─→ /var/log/messages
─→ /var/log/secure
─→ удалённый сервер
Главный практический вывод из этой схемы делается сразу. По умолчанию одно и то же сообщение хранится дважды. Один раз в бинарном журнале journald, второй раз в текстовых файлах rsyslog. На сервере с большим объёмом логов это буквально удвоенный расход диска ни за что, и именно отсюда растёт большинство проблем с переполнением.
Чем бинарный журнал быстрее текста и сколько он за это берёт
Преимущество journald не в маркетинге, а в структуре хранения. Данные пишутся в бинарном индексированном виде, и поиск по ним кардинально быстрее, чем перебор простых текстовых файлов. journald использует бинарное хранилище с индексацией, и выборки идут заметно быстрее, чем по плоским текстовым файлам. Структурированность здесь не опция, а закон формата. Каждая запись несёт метаданные, имя сервиса, идентификатор процесса, идентификатор пользователя, уровень важности, и фильтровать по ним можно без регулярных выражений и без grep по гигабайтам.
Под нагрузкой разница становится решающей. journald спроектирован под производительность и держит высокие объёмы сообщений, тогда как классический syslog при тяжёлой нагрузке упирается в узкое место. Это аргумент в пользу journald как первичного приёмника на нагруженном проде. Запрос конкретного сервиса за конкретное окно времени делается одной командой и возвращается мгновенно, без парсинга текста.
# Логи конкретного юнита, без grep по файлам
journalctl -u nginx
# Сразу несколько сервисов
journalctl -u nginx -u php8.3-fpm
# Слежение в реальном времени, аналог tail -f
journalctl -u nginx -f
# Последние 100 записей в обратном порядке
journalctl -n 100 -r
Цена этой скорости двоякая. Во-первых, дисковый объём бинарного журнала без сжатия и без лимитов растёт быстро и неконтролируемо. Во-вторых, journald это монолит, в котором собрано всё сразу, от хранения и ротации до транспорта и поиска, и эта универсальность оборачивается меньшей гибкостью по сравнению с rsyslog, который проще встроить в чужие инструменты и правила маршрутизации. Отсюда практичный вывод. journald хорош для быстрой диагностики на месте, rsyslog незаменим там, где нужны текстовые файлы, кастомные правила или централизованный сбор по инфраструктуре.
Где журнал реально лежит и почему память против диска решает всё
Прежде чем ставить лимиты, нужно понять, куда journald вообще пишет. За это отвечает один параметр в конфигурации, и от его значения зависит, переживут ли логи перезагрузку. Энергозависимое хранилище держит журнал во временной файловой системе в памяти, и при перезагрузке всё содержимое теряется. Постоянное хранилище пишет на диск и переживает рестарт, что и делает его стандартным выбором для серверов и продакшена.
Большинство дистрибутивов по умолчанию стоит в режиме auto. Это означает, что журнал пишется на диск, если каталог постоянного хранилища существует, и остаётся в памяти, если каталога нет. На боевом сервере полагаться на эту неопределённость не стоит, режим лучше задать явно.
# Проверяем текущий объём журнала на диске
journalctl --disk-usage
# Есть ли постоянное хранилище
ls -la /var/log/journal/
# Если каталога нет, журнал волатильный. Включаем постоянное хранение
sudo mkdir -p /var/log/journal
sudo systemd-tmpfiles --create --prefix /var/log/journal
sudo systemctl restart systemd-journald
Здесь же кроется тонкость, которую упускают. Даже при постоянном хранении на диске есть отдельная группа лимитов для волатильной части журнала, которая живёт в памяти. Дисковым объёмом управляет одна пара параметров, потреблением памяти и tmpfs совсем другая. Путать их означает поставить ограничение не туда, где реально течёт.
SystemMaxUse и его напарник, без которых диск однажды кончится
Главный инструмент против переполнения это лимиты в файле конфигурации journald. Ключевых параметра два, и работают они в паре. Первый ограничивает максимальный объём диска, который журнал вправе занять, после чего journald запускает ротацию. По умолчанию это всего десять процентов от физической памяти узла, что на сервере с большим диском почти всегда мало. Второй параметр задаёт, сколько свободного места на разделе нужно оставить для всего остального, по умолчанию пятнадцать процентов.
Тонкость в их взаимодействии решает всё. journald уважает оба лимита и применяет тот, что сработает раньше. Если свободное место падает ниже порога резерва, journald начинает чистку, даже если лимит максимального объёма ещё не достигнут. Это и есть страховка от ситуации, когда логи доедают последние гигабайты и роняют систему. Поэтому задавать нужно обязательно оба, а не один.
# /etc/systemd/journald.conf
[Journal]
# Хранить журнал на диске, переживая перезагрузку
Storage=persistent
# Потолок объёма журнала на диске
SystemMaxUse=2G
# Сколько свободного места оставить разделу в любом случае
SystemKeepFree=10%
# Потолок размера одного файла журнала
SystemMaxFileSize=128M
# Сжатие, экономит около половины места
Compress=yes
После правки сервис нужно перезапустить, чтобы лимиты применились немедленно и началась чистка лишнего. Перезапуск не обрывает активное логирование, он лишь говорит демону применить новые ограничения сразу.
sudo systemctl restart systemd-journald
Есть важная деталь про механику самой чистки, о которую спотыкаются при расчётах. journald при ротации удаляет только архивные, уже закрытые файлы. Это значит, что сразу после операции освобождения места реально занятый объём может всё ещё превышать заданный лимит, пока текущий активный файл не закроется и не станет архивным. Лимит это не мгновенный потолок, а порог, к которому система стремится с задержкой.
Размер это не время, почему нужен ещё и лимит по сроку хранения
Ограничение по объёму закрывает одну дыру, но оставляет другую. Лимиты размера гарантируют, что диск не переполнится, но ничего не говорят о возрасте записей. Если поток логов небольшой, старые записи могут лежать практически вечно, что плохо для аудита и для соответствия требованиям к срокам хранения. Лимит по времени решает обратную задачу, выкидывая записи старше заданного срока независимо от того, сколько места они занимают.
В продакшене обычно задают оба типа лимита одновременно, и здесь критично понимать правило их совместной работы. journald придерживается того ограничения, которое наступит первым. Если задано хранение тридцать дней и потолок объёма пять гигабайт, а пять гигабайт набираются за десять дней, то записи старше десяти дней будут вычищены, и тридцатидневная политика фактически перебивается объёмной. Эта логика обязательна к учёту при планировании ёмкости.
# /etc/systemd/journald.conf
[Journal]
Storage=persistent
SystemMaxUse=5G
SystemKeepFree=10%
# Не хранить записи старше срока
MaxRetentionSec=30day
# Принудительная ротация файла раз в неделю
MaxFileSec=1week
# Защита от лавины сообщений от одного процесса
RateLimitIntervalSec=30s
RateLimitBurst=10000
Отдельного внимания заслуживают два последних параметра. Ограничение частоты не относится к ротации напрямую, но напрямую влияет на объём. Когда какой-то процесс начинает захлёбываться и сыпать тысячами сообщений в секунду, лимит всплеска отсекает лавину, не давая ей за минуты сожрать весь отведённый журналу объём. Без этой защиты один взбесившийся сервис способен обнулить всю аккуратно рассчитанную стратегию хранения.
Когда чистить руками и как не оставить систему без логов
Конфигурация задаёт политику на будущее, но не разгребает то, что уже накопилось. Для немедленной очистки есть ручные операции, которые работают независимо от файла конфигурации и полезны, когда диск нужно освободить прямо сейчас, а ждать естественной ротации некогда.
# Сначала смотрим реальный объём
journalctl --disk-usage
# Удалить записи старше двух недель
sudo journalctl --vacuum-time=2weeks
# Ужать журнал до 500 мегабайт
sudo journalctl --vacuum-size=500M
Для текстовых файлов rsyslog механизм ротации отдельный и называется logrotate. journald про эти файлы ничего не знает, поэтому собственным приложениям, которые пишут в свои логи через rsyslog, нужна явная политика ротации в отдельном файле. Типичная боевая политика для лога приложения выглядит так.
sudo tee /etc/logrotate.d/myapp << 'EOF'
/var/log/myapp.log {
daily
rotate 14
compress
delaycompress
missingok
notifempty
postrotate
/usr/bin/systemctl reload rsyslog > /dev/null 2>&1 || true
endscript
}
EOF
Смысл ключевых директив стоит понимать, а не копировать вслепую. Ротация ежедневная с хранением четырнадцати поколений. Сжатие включено, но отложенное, то есть самый свежий повёрнутый файл не жмётся сразу, потому что его ещё может держать открытым работающий процесс. Опция игнорирования отсутствующего файла не даёт ротации падать с ошибкой, если лога временно нет, а блок после ротации мягко перечитывает конфигурацию демона.
Главная оптимизация прода, как убрать двойное хранение и не сломать аудит
Возвращаемся к проблеме из начала. По умолчанию сообщение лежит и в бинарном журнале, и в текстовых файлах одновременно. На лог-тяжёлом сервере это сотни лишних мегабайт, а то и гигабайты впустую. Самая результативная оптимизация хранения это решить, кто на машине главный, и убрать дублирование. Управляет этим один параметр пересылки в конфигурации journald.
# /etc/systemd/journald.conf
[Journal]
# Не пересылать в rsyslog, журнал становится единственным хранилищем
ForwardToSyslog=no
# Шум в консоль, ядро и wall тоже отключаем на сервере
ForwardToKMsg=no
ForwardToConsole=no
ForwardToWall=no
Отключение пересылки убирает двойное хранение и экономит на лог-тяжёлых серверах сотни мегабайт. Но у этого шага есть цена, и её надо взвесить трезво. Часть приложений и инструментов анализа логов жёстко рассчитывает на наличие привычных текстовых файлов в каталоге журналов. Системы мониторинга, парсеры, security-инструменты могут читать именно файлы, а не бинарный журнал. Поэтому решение зависит от роли машины. Если на сервере вся диагностика идёт через утилиту чтения журнала и логи в любом случае уезжают в централизованное хранилище, текстовое дублирование чистое расточительство и его убирают. Если же на машине живёт legacy-софт, завязанный на текстовые файлы, отключать пересылку нельзя, и тогда экономить нужно сжатием и жёсткими лимитами, а не отказом от rsyslog.
Зеркальный случай тоже встречается. Иногда машину сознательно делают чисто syslog-ной, оставляя journald только как транзитный приёмник без постоянного хранения, а всё реальное хранение и ротацию отдают rsyslog с его гибкими правилами и пересылкой на удалённый сервер. Оба подхода рабочие, неправильно только одно, оставить всё по умолчанию и платить за двойное хранение, не приняв решения осознанно.
Что из этого собирается в рабочую стратегию
Если свести всё в практический итог, картина получается такой. journald берут как быстрый структурированный первичный сборщик для оперативной диагностики на самой машине. rsyslog оставляют там, где нужны текстовые файлы для legacy-инструментов, тонкая маршрутизация по правилам и пересылка в централизованное хранилище для корреляции между серверами и долгого хранения. На любой боевой машине обязательно задают и объёмный лимит, и резерв свободного места, и срок хранения, понимая, что сработает тот порог, который наступит раньше. Дублирование между двумя системами либо осознанно убирают, либо осознанно оставляют под конкретные требования, но никогда не пускают на самотёк.
Главный вывод не про конкретные директивы, а про дисциплину. Логи на проде это не то, что настраивают один раз и забывают. Это ресурс с конечным объёмом, у которого должен быть хозяин, лимит и понятная политика устаревания. Сервер, на котором заранее посчитан объём журнала, проставлены оба лимита и принято решение о дублировании, не падает ночью из-за переполнения раздела. Сервер, на котором логирование оставили в состоянии по умолчанию, рано или поздно повторяет ту самую фольклорную историю про забитый диск, и вопрос только в том, в какую ночь это случится.