Введение в ELK: собираем, фильтруем и анализируем большие данные

Illustration of a person sitting on the floor leaning against a wall while reading a book, with a stylized shadow casting over part of the image. They are wearing a scarf, a jacket, and orange sneakers. Illustration of a person sitting on the floor leaning against a wall while reading a book, with a stylized shadow casting over part of the image. They are wearing a scarf, a jacket, and orange sneakers.

Внимание! Статье уже четыре года и многое в используемых технологиях могло измениться. Не забываем консультироваться с офф. документацией.

Что, если я скажу вам, что логи могут быть не только полезными и содержать тонну важной информации, но и что работа с ними может быть классной, интересной и увлекательной? Настолько же увлекательной, интересной и классной, как кликанье через удобный интерфейс в браузере, позволяющий в считанные секунды вывести любой график и сравнить его с другим графиком? Да чего там: что, если я скажу вам, что после прочтения этой статьи вы сможете развернуть полноценный аналог Google Analytics для ваших логов? А я это и скажу. Точнее, я это уже сказал. Погнали!

Подготовим vagrant-коробку

Прежде чем перейдём к самой сути, нам необходимо провести небольшую подготовительную работу. Возможно, вы уже использовали Vagrant. Если нет – обязательно узнайте что это такое и начните использовать. Знание Vagrant не нужно для этой статьи, но будет классно (и позволит избежать возможных несоответствий в результате), если вы будете запускать примеры кода ниже используя такой же как у меня конфиг vagrant-бокса.

Итак, создаём новую папку, выполняем в ней vagrant init, открываем Vagrantfile и помещаем туда этот конфиг:

# Vagrantfile API/syntax version. Don't touch unless you know what you're doing!
VAGRANTFILE_API_VERSION = "2"

Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
  config.vm.box = "hashicorp/precise64"
  config.ssh.forward_agent = true

  config.vm.network "forwarded_port", guest: 9200, host: 9200
  config.vm.network "forwarded_port", guest: 9292, host: 9292
  config.vm.network "forwarded_port", guest: 5601, host: 5601
end

Затем выполняем vagrant up и, пока наша виртуальная машинка создаётся, настраивается и запускается, читаем дальше.

ELK stack

ELK расшифровывается как elasticsearch, logstash и kibana. Раньше это были три самостоятельных продукта, но в какой-то момент они стали принадлежать одной компании и развиваться в одном направлении. Каждый из этих инструментов (с небольшими оговорками ниже) является полноценным независимым open source продуктом, а все вместе они составляют мощное решение для широкого спектра задач сбора, хранения и анализа данных. Теперь по порядку о каждом из них.

logstash

logstash – это утилита для сборки, фильтрации и последующего перенаправления в конечное хранилище данных. Вы могли слышать о fluentd – logstash решает ту же самую задачу, но написан на jruby и чуть более лучше дружит с elasticsearch (потому что теперь это продукт одной и той же компании, помните?).

Типичная конфигурация logstash представляет из себя несколько входящих потоков информации (input), несколько фильтров для этой информации (filter) и несколько исходящих потоков (output). Выглядит это как один конфигурационный файл, который в простейшем варианте (который не делает вообще ничего) выглядит вот так:

input {

}
filter {

}
output {

}

Не волнуйтесь, мы скоро перейдём к настоящим примерам.

– Так, так, стоп, Кирилл. Чё ещё за входящие потоки, какие ещё фильтры? Может пример какой приведёшь для этих терминов?

Привожу: допустим, у вас есть лог веб-сервера (nginx). Это входящий поток информации, input. Допустим, вы хотите каждую запись лога не только превратить в json-объект, но ещё и добавить гео-информацию о ней, основываясь на ip. Это фильтр, filter. А после того, как запись лога обработана и обогащена гео-данными, вы хотите отправить её в elasticsearch (скоро поймём почему). Это исходящий поток информации, output.

При этом у вас может быть сколько захочется input'ов, сколько приспичит фильтров (но не забудьте, что чем больше фильтров, тем больше ресурсов понадобится на обработку каждой записи) и сколько душе угодно output'ов. Logstash предоставляет из коробки внушительный набор готовых решений, поэтому вам не придётся писать свой фильтр для гео-данных, например. Он уже есть из коробки. Это, кстати, выгодно отличает logstash от fluentd.

elasticsearch

Изначально, elasticsearch – это решение для полнотекстового поиска, построенное поверх Apache Lucene, но с дополнительными удобствами, типа лёгкого масштабирования, репликации и прочих радостей, которые сделали elasticsearch очень удобным и хорошим решением для высоконагруженных проектов с большими объёмами данных.

Особенно доставляет в elasticsearch его простота и работоспособность из коробки. Конфигурация по-умолчанию скорее всего будет работать как надо для проектов средней и относительно высокой нагруженности. При этом вокруг ES сложилось отличное сообщество, которое всегда подскажет, как правильно настроить ваш ES-кластер для вашей конкретной задачи.

В какой-то момент elasticsearch стал настолько хорош, что использовать его только для поиска по товарам в интернет-магазинах (ну или там поиску по Basecamp) стало глупо и множество компаний начали основывать на ES свои решения по централизованному хранению логов и различной аналитики.

kibana

И вот у нас есть logstash, который собирает и обрабатывает данные со всех ваших тысяч серверов и elasticsearch, который изо всех сил эти данные хранит и позволяет искать по ним. Чего не хватает? Верно, не хватает Angular.js.

Поэтому в какой-то момент товарищ Rashid Khan написал для elasticsearch красивое Angular.js приложение kibana, позволяющее брать\искать данные по elasticsearch и строить множество красивых графиков. Ребята из elasticsearch не дураки, поэтому, увидев всё удобство этого решения, они забрали разработчика kibana к себе на борт.

Помните, я сказал, что все элементы ELK – независимые продукты? На самом деле, kibana бесполезна без elasticsearch. Это очень (очень) удобный интерфейс, позволяющий любому в вашей компании построить себе красивую панельку, на которую выводить аналитику всего, что logstash отправил в elasticsearch.

Пора практиковаться!

У меня возникла проблема: в какой-то момент в mkdev.me произошла какая-то беда, и чтобы разобраться в ней мне нужно было внимательно изучить что-то вроде сотни с лишним мегабайт логов. Удовольствие от использования grep для этой задачи сомнительное, поэтому я решил взять лог с сервера, залить его в elasticsearch при помощи logstash и спокойно проанализировать проблему через браузер при помощи kibana.

Внимание! Возможно, у вас нет пары сотен с лишним мегабайт логов production приложения и вы скажете "э! а мне то чего пихать в elasticsearch"? Честно – не знаю. Но я почти уверен, что у вас локально лежит какое нибудь Ruby on Rails приложение, над которым вы работаете. А значит в папке log этого приложения есть необходимый вам файлик. Вот его и возьмите. А если нет такого файла, то можете попробовать найти в интернете чужие логи.

Не отдам же я вам логи mkdev, в самом-то деле.

Заходим в виртуалку: vagrant ssh.

Установка logstash

Я использовал logstash 2.2.0, документация по его установке валяется на оф. сайте. Прежде чем перейти к установке необходимо дополнительно установить на машине java. Выполняем одно за другим:

sudo apt-get update
sudo apt-get install openjdk-7-jdk curl
curl -O https://download.elasticsearch.org/logstash/logstash/logstash-2.2.0.tar.gz
tar zxvf logstash-2.2.0.tar.gz
cd logstash-2.2.0

Всё, готово. Установили.

Установка elasticsearch

Версия elasticsearch тоже 2.2.0

sudo apt-get install unzip
curl -L -O https://download.elasticsearch.org/elasticsearch/elasticsearch/elasticsearch-2.2.0.zip
unzip elasticsearch-2.2.0.zip
cd elasticsearch-2.2.0

Установка kibana

Используемая версия Kibana: 4.4.2. В реальных условиях, конечно, kibana должна быть отделена от logstash и elasticsearch и крутиться на отдельном сервере.

curl -O https://download.elastic.co/kibana/kibana/kibana-4.4.2-linux-x64.tar.gz
tar zxvf kibana-4.4.2-linux-x64.tar.gz

Конфигурируем logstash

У нас будет всего один input – stdin, стандартный ввод. Он позволит нам загрузить все данные из нашего лог-файла стандартной программой cat. Обратите внимание на type – к каждой итоговой записи, попадающей в output будет добавлено это поле.

input {
  stdin {
    type => "rails"
  }
}

Самая сложная часть – фильтры. Я использую multiline, потому что каждая запись в логах rails занимает несколько строчек. Опция pattern указывает на начало каждой записи.

filter {
  multiline {
      pattern => "(?m)Started"
      negate => true
      what => "previous"
  }
  # ...

grok – это регулярные выражения на стероидах. Позволяет определять конкретные шаблоны регулярных выражений, которые могут содержать в себе другие шаблоны. В logstash куча встроенных шаблонов grok. К сожалению, для rails там встроенного шаблона нет. После недолгих поисков в интернете я нашёл почти готовый шаблон, немного отредактировал его и он отлично сработал для логов mkdev.me (см. чуть ниже).

  # ...
  grok {
    match => [
      "message", "%{RAILS}"
    ]
  }
  if "_grokparsefailure" in [tags] {
    drop { }
  }
  # ...

Найдите в папке logstash-2.2.0 папку patterns, закиньте туда файл rails со следующим содержанием:

RCONTROLLER (?<controller>[^#]+)#(?<action>\w+)
RAILS (?m)Started %{WORD:verb} \"%{URIPATHPARAM:request}\" for %{IPORHOST:clientip} at (?<timestamp>%{YEAR}-%{MONTHNUM}-%{MONTHDAY} %{HOUR}:%{MINUTE}:%{SECOND} %{ISO8601_TIMEZONE}).*Processing by %{RCONTROLLER} as (?<format>\S+)(?:\W*Parameters: {%{DATA:params}}\W*)?

Объяснение структуры этого файла не входит в мои планы, но обратите внимание на такие вещи, как, например, IPORHOST:clientip – встроенный шаблон для распознавания ip-адресов. Выглядят grok-шаблоны уродливо, но с ними приходит великая сила. Есть отдельный сервис, на котором можно тестировать свои шаблоны: http://grokdebug.herokuapp.com/.

Если какая-то запись не соответствует шаблону grok, то logstash добавит к ней тег _grokparsefailure. Если у записи есть такой тег, то мы её просто выкидываем. Заметьте, что фильтры выполняются сверху вниз.

Последний фильтр – date. Парсит дату, записывает её в поле @timestamp (формат, понятный kibana), а затем выкидывает оригинальное поле timestamp (чтобы избежать дубликации). Без этого фильтра все записи будут иметь сегодняшнюю дату, а не дату события из лога.

  # ...
  date {
    match => [ "timestamp", "YYYY-MM-dd HH:mm:ss Z"]
    target => "@timestamp"
    remove_field => "timestamp"
  }
}

Целиком секция с фильтрами выглядит следующим образом:

filter {
  multiline {
      pattern => "(?m)Started"
      negate => true
      what => "previous"
  }
  grok {
    match => [
      "message", "%{RAILS}"
    ]
  }
  if "_grokparsefailure" in [tags] {
    drop { }
  }
  date {
    match => [ "timestamp", "YYYY-MM-dd HH:mm:ss Z"]
    target => "@timestamp"
    remove_field => "timestamp"
  }
}

output у нас один – elasticsearch.

output {
  elasticsearch_http {
    host => "localhost"
  }
}

Итоговый logstash.conf я загрузил в gist.

Конфигурируем elastisearch

Прежде чем мы перейдём к настоящим чудесам, нам необходимо добавить кое-что в конфиг elasticsearch. Открываем elastichsearch-2.2.0/config/elasticsearch.yml и добавляем в самый конец строчку http.cors.enabled: true. Это позволит проводить CORS-запросы из kibana в ES.

Заливаем данные в elasticsearch

Переходим непосредственно к насыщенному экшену. Сначала запустим (из папки elasticsearch-2.2.0, созданной ранее при разархивировании) elasticsearch командой bin/elasticsearch --config=./config/elasticsearch.yml. Затем заходим в папку с logstash и выполняем:

cat production.log | nice bin/logstash -f logstash.conf

Вместо production.log подставьте путь к своему лог-файлу. На обработку и сохранение данных уйдёт какое-то время, особенно с учётом того, что мы всё это дело запускаем в виртуалке и того, что и logstash и elasticsearch любят брать много памяти (java же всё таки).

Смотрим данные

После того как обработка данных закончена мы можем их смотреть и анализировать. Это самая приятная часть.

Сначала покажем Kibana где у нас Elasticsearch:

config/kibana.yml
elasticsearch.url: "http://localhost:9200”

Затем запускаем kibana командой bin/kibana. И открываем в браузере localhost:5601. Не выключайте elasticsearch, так как kibana берёт все данные оттуда.

Интерфейс kibana должен быть относительно простым и интуитивным. Можно добавлять много разных панелей и проводить любые поддерживаемые elasticsearch поисковые запросы. Немного поигравшись, я построил вот такую панель, выводящую распределение запросов содержащих dashboard и admin по времени, а так же табличку с наиболее популярными контроллерами. Стоит ещё сказать, что это kibana 3. На момент написания статьи kibana 4 ещё не вышла из беты.

Замечания

Естественно, приведённая выше конфигурация не является идеальной и не рекомендуется для production окружения. grok-шаблон, например, распознает далеко не все записи из рельсовых логов. Тем не менее, принцип работы остаётся тем же: logstash берёт данные, обрабатывает их и посылает в elasticsearch, к которому затем цепляется kibana.

Если вас интересует как развернуть и автоматизировать production-ready elasticsearch кластер со всеми сопутствующими сервисами вокруг него и при помощи chef – обращайтесь ко мне на почту.

Круто?!

Теперь, дорогие читатели, вы можете стать настоящими властелинами данных. Области применения ELK стэка ограничены лишь вашей фантазией. Централизованное хранилище логов – лишь простейший пример. Другая популярная задача – сбор различных бизнес-событий из приложений и их аналитика. В конце концов, лог – это любая строчка текста, к которой прикреплены дата и время.

Не стоит забывать, что использовать ELK для небольших проектов не имеет особого смысла и затратно. Разворачивать на единственном маленьком сервере помимо самого приложение ещё и elasticsearch с logstash точно не стоит – как минимум память закончится очень быстро.

Настоятельно рекомендую попробовать разные типы input'ов, фильтров и output'ов logstash. Буду рад ответить на любые вопросы в комментариях.