Кейс
Логи переживают недоступность ELK: disk-assisted очередь rsyslog
Центральный rsyslog пересылал логи сетевого парка в ELK по UDP и не замечал недоступность приёмника. Переходим на TCP с disk-assisted очередью: во время сбоя строки копятся в пределах выделенного места, а после восстановления досылаются в порядке поступления.
Схема читается сверху вниз: сначала обычный ход, затем то, ради чего очередь и заводится.
01Обычный ход
Строки идут по TCP, очередь на коллекторе почти пуста
02Приёмник недоступен
Очередь пережидает и штатную остановку коллектора: то, что было в памяти, сохраняется на диск
Весь сетевой парк — роутеры, файрволы, точки доступа — шлёт syslog на один центральный rsyslog-коллектор. Он складывает логи локально по хостам и заодно пересылает всё в ELK, где их удобно искать и строить дашборды. Пересылка была настроена одной привычной строкой:
*.* @elk-host:PORT
Один символ @ — это UDP. И именно он тихо ломал всю затею с наблюдаемостью:
стоило ELK или сети на пути моргнуть, как логи за это время просто исчезали.
Не «задерживались», а терялись без следа. Задача звучала так: сделать буфер, чтобы
при недоступности ELK строки копились, а после восстановления досылались.
Почему UDP не сообщает, что ELK недоступен
Первый инстинкт — «добавить очередь к текущей пересылке». Но с UDP это не сработает,
и причина фундаментальная. UDP — протокол без соединения: sendto() отдаёт пакет в
сеть и почти всегда возвращает успех, даже если на том конце никто не слушает.
Надёжного подтверждения доставки отправитель не получает.
А очередь rsyslog наполняется только тогда, когда действие переходит в состояние
suspended — то есть когда доставка осознанно не удалась. Обычная UDP-пересылка
не получает от приёмника такого сигнала: с точки зрения rsyslog строка отправлена.
Значит resumeRetryCount и action queue не защищают именно от падения ELK — для
отправителя по-прежнему «всё хорошо».
Вывод, который определил решение: буфер требует TCP. Только на TCP обрыв соединения — это явная ошибка доставки, после которой очередь начинает работать.
Параметр, который включает диск
На TCP всё встаёт на места. Заменяем legacy-синтаксис на полноценное действие с очередью:
*.* action(
type="omfwd"
target="elk-host"
port="PORT"
protocol="tcp"
queue.type="LinkedList"
queue.filename="fwd_to_elk" # включает disk-assisted режим
queue.spoolDirectory="/var/spool/rsyslog"
queue.maxDiskSpace="5g" # предел места для файлов очереди
queue.saveOnShutdown="on" # сохранить память при штатной остановке
queue.size="100000" # размер первичной очереди, в сообщениях
queue.highWatermark="90000" # с этого порога включается запись на диск
queue.lowWatermark="70000" # здесь очередь вернётся в memory-only
action.resumeRetryCount="-1" # не прекращать попытки соединения
action.resumeInterval="10" # пауза между попытками, сек
)
Ключевой здесь — queue.filename. Это не «имя лог-файла», а переключатель:
очередь LinkedList становится disk-assisted. В нормальном режиме она работает в
памяти, а при достижении queue.highWatermark начинает выгружать сообщения на диск.
При снижении до queue.lowWatermark снова остаётся только в памяти.
За штатный рестарт отвечает отдельный параметр queue.saveOnShutdown="on": перед
остановкой rsyslog сохраняет на диск и ту часть очереди, которая ещё оставалась в
памяти. Это не защита от SIGKILL, OOM, сбоя ОС или отключения питания. Если нужно,
чтобы каждое принятое сообщение переживало такие события, требуется pure disk queue
с отдельно настроенными checkpoint и синхронизацией файлов.
queue.maxDiskSpace — обязательный предохранитель от заполнения всего раздела, но
это не команда «удалять самые старые строки». Когда выделенное место заканчивается,
результат зависит от источников, queue.timeoutEnqueue и явно заданных
queue.discardMark/queue.discardSeverity: часть источников получит backpressure,
а новые сообщения после тайм-аута могут быть отброшены. Поэтому вместе с лимитом
нужны наблюдение за самой очередью и заранее выбранная политика перегрузки.
Как это ведёт себя вживую
Вся логика сводится к трём состояниям, и переключение между ними — автоматическое:
- ELK доступен — строки идут по TCP, очередь пустая или почти пустая.
- ELK упал — соединение рвётся, действие уходит в suspend, строки копятся в
spoolDirectoryв пределах заданного места. - Связь вернулась — очередь дренируется сама, в порядке поступления (FIFO при штатной однопоточной обработке).
Такая схема закрывает обычную недоступность приёмника, пока хватает буфера. Но plain TCP syslog не даёт end-to-end exactly-once: при разрыве соединения остаётся окно неопределённости для последнего сообщения. Если приёмник должен подтверждать каждое событие, вместо обычного TCP нужен RELP.
Грабли, которые стоили времени
- В нашем окружении валидатор споткнулся о легаси-директиву. Ручной прогон
rsyslogd -N1возвращал ненулевой код из-за старой строки в основном конфиге — поэтому рвалась вся&&-цепочка деплоя, хотя новый файл был корректен. Игнорировать-N1целиком нельзя: новый фрагмент проверяем изолированно (rsyslogd -N1 -f /путь/к/файлу.conf), а старую ошибку разбираем отдельно. - Каталог очереди должен принадлежать сервису. Если
spoolDirectoryне существует или недоступен на запись, дисковая часть очереди не сможет работать. В зависимости от версии и режима rsyslog зафиксирует ошибку и может остаться в аварийном режиме только в памяти. Поэтому после рестарта проверяем не только статус сервиса, но и его журнал, наличие файлов очереди и права на каталог. - «Порт открыт» ≠ «это syslog-вход». Целевой порт TCP может принимать соединение, но говорить на другом протоколе (частый случай — вход для агентов-сборщиков вместо raw-syslog). Тогда TCP-сессия устанавливается, а строки на той стороне не разбираются. Единственная честная проверка — сквозная: послать заведомо уникальное событие и найти его уже разобранным на приёмнике.
Проверка, а не вера
После рестарта — три подтверждения. Первое: на коллекторе видно установленное
TCP-соединение к приёмнику (ss -tn), и оно держится, а не рвётся каждую секунду.
Второе, решающее — маркер:
logger -t elk-buffer-test "проверка $(date)"
Если это событие находится в ELK разобранным по полям — сквозная цепочка подтверждена: TCP, парсинг, индексация. Если TCP-сессия живёт, а события в индексе нет — значит на порту не тот вход, и это разговор уже с командой ELK, а не с конфигом rsyslog.
Третье подтверждение — состояние очереди через impstats: следим за size, full,
discarded.full и discarded.nf. Без этих счётчиков лимит 5 ГБ лишь ограничивает
размер файлов, но не предупреждает, что запас уже заканчивается.
Урок здесь не про rsyslog как таковой, а про разницу между «отправили» и «доставили». UDP-пересылка красиво работает на демонстрации и молча теряет данные ровно тогда, когда они нужнее всего — в момент инцидента, когда что-то на пути и падает. Практичная защита от таких провалов начинается с TCP, отдельной очереди, лимита и мониторинга её заполнения. Пока хватает выделенного места, логи могут подождать на диске и уехать после восстановления ELK. Наблюдаемость, которая учитывает собственные сбои, — это не только дашборд, но и эта неприметная очередь под ним.