Введение в Riemann: мониторинг и анализ событий

в 8:39, , рубрики: clojure, riemann, selectel, Блог компании Селектел, метрики, селектел, системное администрирование, события, метки:

Riemann

В предыдущих статьях мы уже не раз затрагивали проблематику мониторинга, сбора и
хранения метрик (см., например, здесь и здесь). Сегодня мы хотели бы снова вернуться к этой теме и рассказать о необычном, но весьма интересном инструменте — 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-dash

Подведём к чёрной надписи Riemann в самом центре, нажмём клавишу Ctrl (на Mac — cmd) и кликнем по ней. Надпись будет выделена серым цветом. После этого нажмём на клавишу E, чтобы приступить к редактированию:

riemann-dash

В выпадающем меню title выберем пункт Grid, а в поле query напишем true:

riemann-dash

Установив необходимые настройки, нажмём на кнопку Apply:

riemann-dash

Дашборд получается не особо эстетичный и удобный, но вполне наглядный. Неудобство, однако, компенсируется тем, что с 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 на практике, приглашаем поделиться опытом в комментариях.

Если вы по тем или иным причинам не можете оставлять комментарии здесь — добро пожаловать в наш корпоративный блог.

Автор: Селектел

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js