Я уже довольно давно и успешно использую Logstash + Elasticsearch с визуализацией данных в Kibana (общепринятое сокращение для этой тройки - ELK) для сбора, хранения и обработки логов, но для обретения счастья с этой связкой мне потребовалось несколько итераций.
Поскольку в мире СПО все постоянно развивается и становится лучше, сразу хочу отметить, что все описанное актуально для Logstash версиий 1.4-1.5 и Elasticsearch версий 1.4 - 1.7 при нагрузке в ~20kk сообщений в день.
Итак, передо мной стояла задача собирать логи из разных источников, обрабатывать их, и хранить в удобном для обработки виде, с продвинутым веб-интерфейсом. Ранее я уже использовал для схожей задачи Graylog2, который тоже использует Elasticsearch для хранения логов, минусы этого решения я знал, поэтому было решено попробовать использовать второго большого игрока на этом поприще - ELK.
Первый вариант использования Logstash и Elasticsearch был очень наивным:
источники логов -> Logstash -> Elasticsearch
То есть Logstash принимает логи в различных форматах по сети, обрабатывает их, и напрямую кладет в Elasticsearch.
Практически сразу была выявлена проблема - при нагрузке на Elasticsearch (например, кто-то решил через Kibana получить из логов самые популярные файлы на нашем CDN за пару недель) Logstash затыкался, кушал CPU и переставал принимать новые сообщения. Связано это поведение с тем, что Logstash написан на JRuby и на данный момент имеет один общий Loop обработки, так что при таймауте на операции записи в Elasticsearch этот Loop ломался. При этом со стороны процесс Logstash выглядел вполне живым, слушал все нужные порты, потреблял ресурсы системы, и определить поломку можно было только с помощью скрипта мониторинга, проверяющего свежесть данных в Elasticsearch.
На этом же этапе была выявлена еще одна особенность Elasticsearch. Дело в том,
что он весьма прожорлив до оперативки, поскольку вынужден хранить индексы
для все активных данных в памяти. Более того, поскольку JVM мы запускаем с
определенными ограничениями по потребляемой памяти (-Xmx NN
), Elasticsearch
легко может накушаться памяти до внутреннего OOM Java, и грустно издохнуть.
При этом Logstash тоже ожидаемо залипает и перестает принимать сообщения.
Для ограничения жора памяти на сложных запросах в Elasticsearch есть специальный
механизм Circuit Breaker,
который пытается угадать тебуемое количество памяти до выполнения запроса, и
прервать его выполнение пре превышении определенных границ. По моему
опыту суровая правда заключается в том, что не надо пытаться запихнуть в ELK
больше данных, чем ваш сервер может переварить. То есть Circuit Breaker можно
тюнить вполне успешно, тогда Elastiсsearch на интересные большие запросы просто
откажется отвечать, но хотя-бы не упадет.
Вообще у Elasticsearch весьма хорошая документация по ограничению использования оперативной памяти, и она мне очень пригодилась, и даже до добавления в сервера оперативки удалось добиться того, чтобы Elasticsearch не падал с OOM, но главная проблема с затыкающимся Logstash так и осталась.
Для решения этой проблемы я выбрал простую и достаточно популярную схему с буферизацией входящих логов в Redis. Таким образом, схема работы стала выглядеть так:
источники логов -> Logstash-front -> Redis -> Logstash-back -> Elasticsearch
Таким образом, Redis выступает в роли эластичного буфера, сглаживая пики нагрузки, и храня сообщения при перезапусках или падения Logstash-back и Elasticsearch. При это Logstash-front сделан максимально тупым, и не выполняет никакой обработки сообщений - он просто кладет их в Redis.
Именно такая схема и пошла в продакшн, и на достаточно слабых серверах успешно держит 20 миллионов сообщений в день, при этом Elasticsearch в связке с Kibana дает возможность мониторить многие вещи практически в реальном времени. Ну и при дебаге любых проблем Kibana стала незаменимым инструментом.
А еще хочется отметить очень значительный прогресс в разработке ELK, за пол-года активного использования:
- производительность Logstash в новых релизах выросла процентов на 20;
- используемое место на диске сократилось вдвое (Logstash стал использовать более разумный маппинг для данных по-умолчанию);
- потребление оперативной памяти Elasticsearch уменьшилось на треть;
- скорость восстановления кластера Elasticsearch выросла на порядок.