В предыдущих статьях мы уже не раз затрагивали проблематику мониторинга, сбора и
хранения метрик (см., например, здесь и здесь). Сегодня мы хотели бы снова вернуться к этой теме и рассказать о необычном, но весьма интересном инструменте — Riemann.
По сравнению с другими системами мониторинга он отличается повышенной сложностью,
и в то же время — гораздо большей гибкостью и отказоуcтойчивостью. На просторах Интернета нам доводилось встречать публикации, где Riemann характеризуют как «самую гибкую систему мониторинга в мире». Riemann хорошо подходит для сбора информации о работе сложных высоконагруженных систем в реальном масштабе времени.
Собственно говоря, системой мониторинга в строгом смысле Riemann не является. Правильнее было бы его называть инструментом обработки событий (event processor).
Он собирает информацию о событиях с хостов и приложений, объединяет события в поток и передаёт их другим приложениям для дальнейшей обработки или хранения. Также Riemann отслеживает состояние событий, что позволяет создавать проверки и рассылать уведомления.
Riemann распространяется бесплатно по лицензии Eclipse. Большая часть кода написана Кайлом Кингсбери, известном также под псевдонимом Aphyr (кстати, рекомендуем почитать его блог: там часто бывают интересные материалы).
Обработка событий в реальном масштабе времени
Рост интереса к проблематике мониторинга, сбора, хранение и анализа метрик, который мы наблюдаем в последнее время, вполне объясним: вычислительные системы становятся всё более сложными и более высоконагруженными. В случае с высоконагруженными системами особую важность приобретает возможность отслеживания событий в реальном масштабе времени. Собственно, Riemann и был создан для того, чтобы решить эту проблему.
Идея обработки событий в режиме, приближенном к реальному времени не нова: первые попытки её осуществления предпринимались ещё в конце 1980-х годов. В качестве примера можно назвать так называемые Active Database Systems (активные системы баз данных), которые выполняли определённый набор инструкций, если поступающие в базу данные соответствовали заданному набору условий.
В 1990-х годах появились системы управления потоками данных (Data Stream Management Systems), которые уже могли обрабатывать поступающие данные в реальном масштабе времени, и системы обработки сложных событий (Complex Event Processing, сокращённо CEP). Такие системы могли как обнаруживать события на основе внешних данных и заложенной внутренней логики, так и осуществлять определённые аналитические операции (например, подсчитывать количество событий за определённые период времени).
Примерами современных инструментов обработки сложных событий могут служить, в частности, Storm (см. также статью о нём на русском языке) и Esper. Они ориентированы на обработку данных без хранения. Riemann — продукт такого же класса. В отличии от того же Storm он гораздо более прост и логичен: вcя логика обработки событий может быть описана всего лишь в одном конфигурационном файле.
Многих системных администраторов-практиков эта особенность может и отпугнуть: конфигурационный файл по сути представляет собой код на языке Clojure, но котором написан и Riemann.
Clojure относится к функциональным (а говоря ещё точнее — лиспообразным) языкам программирования, что само по себе уже настораживает. Однако в этом нет ничего страшного: при всём своём своеобразии Clojure не так сложен, как кажется на первый взгляд. Рассмотрим его особенности более подробно.
Немного о Clojure
Clojure представляет собой функциональный язык, созданный на базе LISP. Программы, написанные на Clojure, работают на платфоре JVM. Первая версия этого языка появилась в 2007 году. Совсем недавно вышла в свет последняя на сегодняшний день версия — 1.8.0.
Clojure используется в проектах таких компаний, как Facebook, Spotify, SoundCloud, Amazon и других (полный список см. на официальном сайте).
В отличие от других реализаций LISP для JVM (например, ABCL или Kawa), Clojure не совместим полностью ни с Common Lisp, ни со Scheme, однако из этих языков в нём очень много позаимствовано. Есть в Clojure и некоторые усовершенствования, которых нет в других современных диалектах LISP: неизменность данных, конкуретное выполнение кода и т.п.
Так как Clojure был изначально спроектирован для работы с JVM, в нём можно работать с многочисленными библиотеками, существующими для этой платформы. Взаимодействие с Java реализовано в обе стороны. можно вызывать код, написанный для Java. Возможна также реализация классов, доступных для вызова из Java и других языков программирования, работающих на базе JVM — например, для Scala. Более подробно о Clojure и его возможностях можно прочитать в этой статье, а также на официальном сайте Riemann. Рекомендуем также ознакомиться ещё с одним кратким, но весьма информативным введением в Clojure.
Установка и первый запуск
Чтобы работать с Riemann, нам сначала понадобится установить все необходимые
зависимости: Java и Ruby (на нём написаны некоторые дополнительные компоненты, о которых речь ещё пойдёт ниже):
$ sudo apt-get -y install default-jre ruby-dev build-essential
Далее загрузим и установим последнюю версию Riemann:
$ wget https://aphyr.com/riemann/riemann-0.2.10_all.deb
$ dpkg -i riemann-0.2.10_all.deb
Далее выполним:
$ sudo service riemann start
Для полноценной работы нам понадобиться также установить написанные на Ruby компоненты для сбора и метрик:
$ gem install riemann-client riemann-tools
Вот и всё. Для начала работы с Riemann всё готово. Прежде чем перейти к практической части, сделаем небольшое теоретическое отступление и проясним смысл важнейших понятий: события, потоки и индекс.
События, потоки и индекс
Базовым понятием в Riemann является событие. События можно обрабатывать, подсчитывать, собирать и экспортировать в другие программы. Событие может выглядеть, например, так:
{:host riemann, :service riemann streams rate, :state ok, :description nil, :metric 0.0, :tags [riemann], :time 355740372471/250, :ttl 20}
Приведённое событие состоит из следующих полей:
- :host — имя хоста;
- :service — имя наблюдаемого сервиса;
- :state — состояние события (ok, warning, critical);
- :tags — метки события;
- :time — время наступления события в формате Unix Timestamp;
- :description — описание события в произвольной форме;
- :metric — метрика, ассоциированная с событием;
- :ttl — время актуальности события (в секундах).
Некоторые события могут также иметь кастомные поля, которые можно добавлять ак во время создания, так и во время обработки событий (например, поля с дополнительными метриками).
Все события объединяются в потоки. Поток — это функция, которой может быть передано событие.
Вы можете создать неограниченно количество потоков. События проходят через потоки, но не сохраняются в них. Однако очень часто возникает необходимость отслеживать состояние событий — например, утратили они актуальность или нет. Для этого используется индекс — таблица состояний отслеживаемых событий. В индексе события сортируются по группам по хосту и по сервису, например:
:host www, :service apache connections, :state nil, :description nil, :metric 100.0, :tags [www], :time 466741572492, :ttl 20
Это событие, произошедшее на хосте www в сервисе apache connections. В индексе всегда хранится последнее на текущий момент событие. К индексам можно обращаться из потоков и даже из внешних сервисов.
Мы уже видели, что каждое событие содержит поле TTL (time to live). TTL — это промежуток времени, в течение которого событие является актуальным. В только что приведённом примере TTL события составляет 20 секунд. В индекс попадают все события с параметрами :host www и :service apache connections. Если в течение 20 секунд таких событий не происходит, будет создано новое событие со значением expired в поле state. Затем оно будет добавлено в поток.
Конфигурирование
Перейдём от теории к практике и займёмся конфигурированием Riemann. Откроем конфигурационный /etc/riemann/riemann.config. Он представляет собой программу на Clojure и по умолчанию выглядит так:
; -*- mode: clojure; -*-
; vim: filetype=clojure
(logging/init {:file "/var/log/riemann/riemann.log"})
; Listen on the local interface over TCP (5555), UDP (5555), and websockets
; (5556)
(let [host "127.0.0.1"]
(tcp-server {:host host})
(udp-server {:host host})
(ws-server {:host host}))
; Expire old events from the index every 5 seconds.
(periodically-expire 5)
(let [index (index)]
; Inbound events will be passed to these streams:
(streams
(default :ttl 60
; Index all events immediately.
index
; Log expired events.
(expired
(fn [event] (info "expired" event))))))
Этот файл разделён на несколько разделов. Каждый раздел начинается с комментария, обозначаемого, как это принято в Clojure, точкой с запятой (;).
В первом разделе указан файл, в который будут записываться логи. Далее идёт раздел с указанием интерфейсов. Обычно Riemann слушает на TCP-, UDP- и вебсокет-интерфейсе. По умолчанию все они привязаны к локальному хосту (127.0.0.1).
Следующий раздел содержит настройки для событий и индекса:
(periodically-expire 5)
(let [index (index)]
; Inbound events will be passed to these streams:
(streams
(default :ttl 60
; Index all events immediately.
index
Первая функция (periodically-expire) убирает из индекса все события, у которых истёк период актуальности, и присваивает им статус expired. Очистка событий запускается каждые 5 секунд.
По умолчанию Riemann копирует в события с истёкшим сроком актуальности поля :service и :host. Можно с копировать и другие поля; для этого нужно с функцией periodically-expired использовать опцию :key-keys. Вот так, например, мы можем дать указание сохранять не только имя хоста и имя сервиса, но ещё и тэги:
(periodically-expire 5 {:keep-keys [:host :service :tags]})
Далее следует конструкция, в которой мы определяем символ с именем index. Значение этого символа — index, т.е. это функция, которая отправляет события в индекс. Она используется, чтобы указать Riemann, когда индексировать то или иное событие.
С помощью функции streams мы описываем потоки. Каждый поток представляет собой функцию, принимающую в качестве аргумента событие. Функция streams указывает Riemann: «вот список функций, которые нужно вызывать при добавлении новых событий». Внутри этой функции мы устанавливаем TTL для событий — 60 секунд. Для этого мы воспользовались функцией default, которая берёт поле из события и позволяет установить для него значение по умолчанию. События, у которых нет TTL, будет получать статус expired.
Затем дефолтная конфигурация вызывает символ index. Это означает, что все поступающие события будут добавляться в индекс автоматически.
Заключительный раздел содержит указание логгировать события со статусом expired:
; Log expired events.
(expired
(fn [event] (info "expired" event))))))
Внесём в конфигурационный файл некоторые изменения. В разделе, посвящённом сетевым интерфейсам, заменим 127.0.0.1 на 0.0.0.0, чтобы Riemann мог принимать события с любого хоста.
В самый конец файла добавим:
;print events to the log
(streams
prn
#(info %))
Это функция prn, которая будет записывать события в логи и в стандартный вывод. После этого сохраним внесённые изменения и перезапустим Riemann.
В ситуации, когда приходится отслеживать работу множества сервера, можно создавать не общий конфигурационный файл, а целую директорию с отдельными файлами для каждого сервера или группы серверов (см. рекомендации в этой статье).
С подробной инструкцией по написанию конфигурационного файла можно познакомиться здесь.
Отправка данных в Riemann
Попробуем теперь отправить данные в Riemann. Воспользуемся для этого клиентом riemann-health, который входит в уже установленный нами ранее пакет riemann-tools. Откроем ещё одну вкладку терминала и выполним:
$ riemann-health
Эта команда передаёт Riemann данные о состоянии хоста (загрузка CPU, объём занятого дискового пространства, объём используемой памяти).
Riemann начнёт принимать события. Информация об этих событиях будет записываться в файл /var/log/riemann/riemann.log. Она представлена в следующем виде:
#riemann.codec.Event{:host "cs25706", :service "disk /", :state "ok", :description "8% used", :metric 0.08, :tags nil, :time 1456470139, :ttl 10.0}
INFO [2016-02-26 10:02:19,571] defaultEventExecutorGroup-2-1 - riemann.config - #riemann.codec.Event{:host cs25706, :service disk /, :state ok, :description 8% used, :metric 0.08, :tags nil, :time 1456470139, :ttl 10.0}
#riemann.codec.Event{:host "cs25706", :service "load", :state "ok", :description "1-minute load average/core is 0.02", :metric 0.02, :tags nil, :time 1456470139, :ttl 10.0}
Riemann-health — это лишь одна из утилит в пакете riemann-tools. В него входит довольно большое количество утилит для сбора метрик: riemann-net (для мониторинга сетевых интерфейсов), riemann-diskstats (для мониторинга подсистемы ввода-вывода), riemann-proc (для мониторинга процессов в Linux) и другие. С полным списком утилит можно ознакомиться здесь.
Создаём первую проверку
Итак, Riemann установлен и запущен. Попробуем теперь создать первую проверку. Откроем конфигурационный файл и добавим в него такие строки:
(let [index (index)]
(streams
(default :ttl 60
index
;#(info %)
(where (and (service "disk /") (> metric 0.10))
#(info "Disk space on / is over 10%!" %))
Перед функцией (#info) стоит знак комментария — точка с запятой (;). Это сделано, чтобы Riemann не записывал каждое событие в лог. Далее мы описываем поток where. В него попадают события, которые соответствуют заданному критерию. В нашем примере таких критериев два:
- поле :service должно иметь значение disk /;
- значение поля :metric должно быть больше 0.10 или 10%.
Затем они передаются в дочерний поток для дальнейшей обработки. В нашем случае информация о таких событиях будет записываться в файл /var/log/riemann/riemann.log.
Фильтрация: краткая справка
Без фильтрации событий полноценная работа c Riemann невозможна, поэтому о ней стоит сказать несколько слов отдельно.
Начнём с фильтрации событий с помощью регулярных выражений. Рассмотрим следующий пример описания потока where:
where (service #”^nginx”))
В Clojure регулярные выражения обозначаются знаком # и заключаются в двойные кавычки. В нашем примере в поток where будут попадать выражения, у которые содержат имя nginx в поле :service.
События в потоке where можно объединять с помощью логических операторов:
(where (and (tagged "www") (state "ok")))
В этом примере в поток where будут попадать события с тэгом www и значением ok в поле state. Они объединяются с событиями из потока tagged.
Tagged — это сокращённое имя функции tagged-all, которая объединяет все события с заданными тэгами. Еcть ещё функция tagged-any — она объединяет в поток события, отмеченные одним или несколькими из указанных тэгов:
(tagged-any ["www" "app1"] #(info %))
В нашем примере в поток tagged попадут события, отмеченные тэгами www и app1.
По отношению к событиям можно выполнять математические операции, например:
(where (and (tagged "www") (>= (* metric 10) 5)))
В этом примере будут события будут попадать события с тэгом www, у которых значение поля :metric, умноженное на 10, будет больше 5.
Аналогичный синтаксис можно использовать, чтобы выбирать события, у которых значения в поле :metric попадают в указанный диапазон:
(where (and (tagged "www") (< 5 metric 10)))
В приведённом примере в поток where будут попадать события с тэгом www, у которых значение поля :metric находится в диапазоне 5 —10.
Настройка уведомлений
Riemann может рассылать уведомления в случае соответствия заданным условиям проверок. Начнём с настройки уведомлений по электронной почте. В Riemann для этого используется функция email:
[
(def email (mailer {:from "riemann@example.com"}))
(let [index (index)]
; Inbound events will be passed to these streams:
(streams
(default :ttl 60
; Index all events immediately.
index
(changed-state {:init "ok"}
(email "andrei@example.com")))))
Рассылки уведомлений в Riemann осуществляется на базе специальной библиотеки на Clojure — Postal. По умолчанию для рассылки используется локальный почтовый сервер.
Все сообщения будут отправляться с адреса вида riemann@example.com.
Если локальный почтовый сервер не установлен, Riemann будет выдавать сообщения об ошибке вида:
riemann.email$mailer$make_stream threw java.lang.NullPointerException
В приведённом выше примере кода мы использовали ярлык changed-state и тем самым указали, что Riemann должен отслеживать события, состояние которых изменилось. Значение переменной init сообщает Riemann, каким было начальное состояние события. Все события, состояние которых изменилось с ok на какое-либо другое, будут передаваться функции email. Информация о таких событиях будет отправлена на указанный адрес электронной почты.
С более подробными примерами настройки уведомлений можно ознакомиться в статье Джеймса Тернбулла, одного из разработчиков Riemann.
Визуализация метрик: riemann-dash
В Riemann имеется собственный инструмент для визуализации метрик и построения простых дашбордов — riemann-dash. Установить его можно так:
$ git clone git://github.com/aphyr/riemann-dash.git
$ cd riemann-dash
$ bundle
Запускается riemann-dash с помощью команды:
$ riemann-dash
Домашняя страница riemann-dash доступна в браузере по адресу [ip-aдрес сервера]:4567:
Подведём к чёрной надписи Riemann в самом центре, нажмём клавишу Ctrl (на Mac — cmd) и кликнем по ней. Надпись будет выделена серым цветом. После этого нажмём на клавишу E, чтобы приступить к редактированию:
В выпадающем меню title выберем пункт Grid, а в поле query напишем true:
Установив необходимые настройки, нажмём на кнопку Apply:
Дашборд получается не особо эстетичный и удобный, но вполне наглядный. Неудобство, однако, компенсируется тем, что с Riemann можно использовать сторонние инструменты визуализации, d в частости Graphite и Grafana — заитересованный читатель без труда сможет найти соответствующие публикации в Интернете. А процедуру настройки связки Riemann+InfluxDB+Grafana мы опишем в следующем разделе.
Отправка данных в InfluxDB
Несомненным преимуществом Riemann являются широкие возможности интеграции. Собранные с его помощью метрики можно отправлять в сторонние хранилища. Ниже мы покажем, как интегрировать Riemann c InfluxDB и настроить визуализацию данных с помощью Grafana.
Установим InfluxDB:
$ wget https://s3.amazonaws.com/influxdb/influxdb_0.9.6.1_amd64.deb
$ sudo dpkg -i influxdb_0.9.6.1_amd64.deb
О конфигурированиии InfluxDB можно подробнее почитать в официальной документации, а также в одной из наших предыдущих статей.
По завершении установки выполним команду:
$ sudo /etc/init.d/influxdb start
Затем создадим базу для хранения данных из Riemann:
$ sudo influx
>CREATE DATABASE riemann
Создадим для этой базы пользователя и установим для него пароль:
>CREATE USER riemann WITH PASSWORD ‘пароль пользователя riemann’
>GRANT ALL ON riemann TO riemann
Вот и всё, установка и базовая настройка InfluxDB завершены. Теперь нужно прописать необходимые настройки в конфигурационном файле Riemann (код взят отсюда и незначительно модифицирован):
; -*- mode: clojure; -*-
; vim: filetype=clojure
;подключаем capacitor, клиент для работы с InfluxDB
(require 'capacitor.core)
(require 'capacitor.async)
(require 'clojure.core.async)
(defn make-async-influxdb-client [opts]
(let [client (capacitor.core/make-client opts)
events-in (capacitor.async/make-chan)
resp-out (capacitor.async/make-chan)]
(capacitor.async/run! events-in resp-out client 100 10000)
(fn [series payload]
(let [p (merge payload {
:series series
:time (* 1000 (:time payload)) ;; s → ms
})]
(clojure.core.async/put! events-in p)))))
(def influx (make-async-influxdb-client {
:host "localhost"
:port 8086
:username "riemann"
:password "пароль пользователя riemann"
:db "riemann"
}))
(logging/init {:file "/var/log/riemann/riemann.log"})
(let [host "0.0.0.0"]
(tcp-server {:host host})
(udp-server {:host host})
(ws-server {:host host}))
(periodically-expire 60)
(let [index (index)]
(streams
index
(fn [event]
(let [series (format "%s.%s" (:host event) (:service event))]
(influx series {
:time (:time event)
:value (:metric event)
})))))
Сохраним внесённые изменения и перезапустим Riemann.
После этого установим Grafana:
$ wget https://grafanarel.s3.amazonaws.com/builds/grafana_2.6.0_amd64.deb
$ sudo dpkg -i grafana_2.6.0_amd64.deb
Подробных инструкций по настройке Grafana мы приводить не будем, да в этом и нет особой необходимости: соответствующие публикации можно без труда найти в Интернете.
Домашняя страница Grafana будет доступна в браузере по адресу http://[IP-адрес сервера]:3000. Далее потребуется лишь добавить новый источник данных (InfluxDB) и создать дашборд.
Заключение
В этой статье мы представили краткий обзор возможностей Riemann. Мы затронули следующие темы:
- особенности языка Clojure;
- установка и первичная настройка Riemann;
- структура конфигурационного файла и особенности его синтаксиса;
- создание проверок;
- настройка уведомлений;
- визуализация метрик с помощью riemann-dash
- интеграция Riemann c InfluxDB и визуализация метрик с помощью Grafana
Если вам кажется, что мы упустили какие-то важные детали — напишите нам, и мы дополним обзор. А если вы используете Riemann на практике, приглашаем поделиться опытом в комментариях.
Если вы по тем или иным причинам не можете оставлять комментарии здесь — добро пожаловать в наш корпоративный блог.