journald и rsyslog: почему они работают параллельно и как это использовать для ELK
После перехода на systemd обнаружили: journald и syslog не заменяют друг друга. Настраиваем форвардинг в rsyslog, разбираемся с ротацией и сохраняем совместимость с ELK.
Переход на systemd меняет практику работы с логами: journald пишет бинарный журнал, но rsyslog при этом продолжает работать параллельно
Когда мы переводили первые серверы на CentOS 7, про journald написали вскользь: мол, journalctl -u nginx удобнее grep, но /var/log/messages никуда не делся. Разобраться глубже руки не доходили - работало, и ладно. Но после того как мы развернули ELK-стек для централизованных логов и начали переводить туда серверы на Debian 8 и CentOS 7, вопрос встал конкретнее: а что именно попадает в Logstash, из journald или из rsyslog, и одно и то же ли это?
Оказалось - не совсем одно и то же. И разобраться стоило заранее.
Два журнала живут рядом
На свежем CentOS 7 или Debian 8 с systemd одновременно работают два механизма сбора логов.
journald - часть systemd, пишет бинарный журнал в /var/log/journal/. Собирает stdout/stderr всех сервисов, управляемых через unit-файлы, плюс сообщения ядра, плюс то что приложения шлют через syslog-сокет. Читается через journalctl. Формат бинарный - руками не откроешь, зато хранит структурированные поля, приоритет, юнит-источник, PID, метку времени с микросекундами.
rsyslog - тот же rsyslog что был до systemd. На CentOS 7 он стоит и запущен по умолчанию. Читает сообщения из того же сокета /run/systemd/journal/syslog, который journald специально предоставляет для совместимости, и пишет текстовые файлы в /var/log/.
То есть схема такая: приложение пишет в сокет -> journald принимает и сохраняет в бинарный журнал -> параллельно форвардит в rsyslog через /run/systemd/journal/syslog -> rsyslog пишет в /var/log/messages и всё остальное. Это не дублирование ошибки в архитектуре, это осознанная совместимость. Но несколько вещей из этой схемы неочевидны.
Что теряется при форвардинге
Journald хранит структурированные поля: _SYSTEMD_UNIT, _PID, PRIORITY, SYSLOG_IDENTIFIER и ещё десяток. Когда rsyslog получает эти сообщения через syslog-сокет, он видит только то, что умещается в стандартный syslog-формат - приоритет, метку времени, имя хоста, тег и текст сообщения. Остальные поля journald теряются.
Для большинства задач это не критично. Но если нужно в ELK фильтровать по конкретному systemd-юниту через rsyslog - это поле уже не придёт. Придётся парсить тег или имя процесса.
Второй момент: по умолчанию в journald настройка ForwardToSyslog=yes в /etc/systemd/journald.conf. Значит форвардинг включён. Но это можно выключить, и тогда rsyslog перестанет получать системные события вообще. Проверить текущее состояние:
grep ForwardToSyslog /etc/systemd/journald.conf
Форвардинг из rsyslog в Logstash
Для ELK мы используем rsyslog на хостах как форвардер: он читает системные логи и шлёт в Logstash по TCP. Конфиг простой, добавляем файл в /etc/rsyslog.d/:
# /etc/rsyslog.d/50-logstash.conf
*.* @@logstash.internal:5514;RSYSLOG_SyslogProtocol23Format
@@ - TCP (одна @ - UDP), RSYSLOG_SyslogProtocol23Format - RFC 5424, который Logstash понимает из коробки через встроенный syslog-input.
На стороне Logstash input-секция:
input {
syslog {
port => 5514
type => "syslog"
}
}
Всё. Работает. Единственная тонкость которую поймали: если на хосте rsyslog форвардит через UDP и Logstash на другом конце перегружен, сообщения просто теряются без ошибок. На TCP хотя бы будет очередь и backpressure. Для логов которые важны - только TCP.
journald как источник напрямую
Есть альтернатива rsyslog - читать из journald напрямую через journalbeat или systemd-journal-gatewayd. Мы пробовали journalbeat (неофициальный, написан сообществом) - работает, но инструмент молодой и без нормальной документации. На нескольких хостах поставили как эксперимент, на остальных остался rsyslog - надёжнее и предсказуемее.
Ротация: logrotate или journald - не одно и то же
Это отдельная история. logrotate управляет текстовыми файлами в /var/log/ - это rsyslog-мир. journald управляет своим бинарным журналом сам, через параметры в journald.conf.
Типичная ошибка: настроить logrotate для /var/log/journal/ - это не работает так как ожидается. journald не следит за этой директорией через inotify в смысле «кто-то ротировал файл, давай переоткроем». Ротация journald делается через:
journalctl --vacuum-size=500M # удалить старые файлы пока журнал не станет <= 500M
journalctl --vacuum-time=30d # удалить записи старше 30 дней
Или настраивается постоянно через journald.conf:
[Journal]
SystemMaxUse=500M
MaxRetentionSec=30day
После изменения конфига - systemctl restart systemd-journald. Мы это прописали в Ansible-роль для базового провизионирования сразу: без ограничения journald может спокойно занять 10% диска - это дефолт, и на сервере с 50 ГБ под /var это 5 ГБ под логи без предупреждения.
logrotate при этом никуда не уходит - он продолжает ротировать /var/log/messages, /var/log/secure, nginx-логи, postgresql-логи. Просто это два отдельных инструмента для двух отдельных хранилищ.
Наша текущая схема на серверах с CentOS 7:
- journald:
SystemMaxUse=500M,MaxRetentionSec=14day- оперативная диагностика черезjournalctl - rsyslog: форвардит всё в Logstash по TCP, плюс пишет локальные файлы
- logrotate: ротирует текстовые файлы в
/var/log/по старой схеме - daily, 7 копий, gzip - ELK: получает логи через rsyslog-форвардинг, хранит 30 дней
Дублирование? Немного. Зато journald доступен локально без сетевых зависимостей - если ELK недоступен или Logstash упал, journalctl на хосте всё равно показывает что происходило. Это не лишнее.
Что осталось неудобным
Честно: journald как чёрный ящик всё ещё немного раздражает. Текстовый файл можно открыть, передать по scp, проанализировать любым инструментом. С бинарным журналом без journalctl делать нечего. На хостах где нет доступа к сети и надо передать логи - экспорт через journalctl --output=export или --output=json, потом уже работаешь с текстом.
Привыкаем постепенно. Пока - rsyslog остаётся как страховка.