> Вводная часть (со ссылками на все статьи)

Сложные системы (распределённые/крупные/со сложной логикой/сложной системой данных) – как живой организм: подвижный, изменчивый и самостоятельный. Всё это требует постоянного контроля со стороны разработчиков/администраторов/DevOps-инженеров.

К этому выводу я пришёл, когда система несколько раз «загибалась» в ходе её разработки, настройки сервера и эксплуатации. Это натолкнуло меня на мысль, что мониторинг должен осуществляться не только на этапе производственной эксплуатации, но и на этапе разработки.

Обо всём по порядку…

Когда я пришёл к выводу о необходимости мониторинга за проектом (как минимум серверной части) я решил, что идеальным вариантом для этого будет схема: «коллектор данных > TSDB > веб-клиент для отображения данных».

image

Выбор TSDB


На текущий момент очень много статей посвящённых настройке Graphite в качестве TSDB, я же выбрал более современное и legacy-free решение на базе InfluxDB. Об InfluxDB уже писалось на Хабре в блоге компании Selectel. Не хочу копировать чужой текст, единственное могу сказать, что часть информации уже не соответствует действительности, но основа всё так же верна – система производительна, гибка, доступна для работы для разных языков и поддерживает разные протоколы других TSDB и агентов. Graphite же меня отпугнул наличием нескольких связанных друг с другом демонов, написанных на Python (излишняя сложность и дополнительные компоненты).

Puppet-скрипт для установки и настройки InfluxDB
class storyline_infra::influxdb () {
        include stdlib

        $params = lookup({"name" => "storyline_infra.influxdb",
            "merge" => {"strategy" => "deep"}})
        $port_http = $params['port_http']
        $port_rpc = $params['port_rpc']
        $pid_file = $params['pid_file']
        $init_script = $params['init_script']
        $dir_data = $params['dir_data']
        $dir_logs = $params['dir_logs']
        $enabled_auth = $params['enabled_auth']
        $enabled_startup = $params['enabled_startup']
        $enabled_running = $params['enabled_running']
        $version = $params['version']

        $dist_name = $facts['os']['name']

        user { 'influxdb':
                ensure => "present",
                managehome => true,
        }
        exec { "influxdb-mkdir":
                command => "/bin/mkdir -p /data/db && /bin/mkdir -p /data/logs",
                cwd => "/",
                unless => '/usr/bin/test -d /data/db -a -d /data/logs',
        } ->
        # working dir
        file { $dir_logs:
                ensure => "directory",
                recurse => "true",
                owner => "influxdb",
                group=> "influxdb",
                require => Exec['influxdb-mkdir'],
        }
        file { $dir_data:
                ensure => "directory",
                recurse => "true",
                owner => "influxdb",
                group=> "influxdb",
                require => Exec['influxdb-mkdir'],
        }
        # see by "gpg --verify keyfile"
        apt::key { 'influxdb-key':
                id => '05CE15085FC09D18E99EFB22684A14CF2582E0C5',
                source  => 'https://repos.influxdata.com/influxdb.key',
        } ->
        # echo "deb https://repos.influxdata.com/${DISTRIB_ID,,} ${DISTRIB_CODENAME} stable" | sudo tee /etc/apt/sources.list.d/influxdb.list
        apt::source { 'influxdb-repo':
                comment  => 'influxdb repo',
                location => "https://repos.influxdata.com/${downcase($dist_name)}",
                release => "${facts['os']['distro']['codename']}",
                repos    => 'stable',
                include  => {
                        'deb' => true,
                },
        } ->
        package {  'influxdb':
                ensure => $version,
        } ->
        file { "/etc/influxdb/influxdb.conf":
                replace => true,
                content => epp('storyline_infra/influxdb.epp'),
                owner => "influxdb",
                group=> "influxdb",
                notify => Service['influxdb'],
        }->
        file { $init_script:
                replace => true,
                content => epp('storyline_infra/influxdb_startup.epp'),
                mode=>"ug=rwx,o=r",
                notify => Service['influxdb'],
        }->
        service { 'influxdb':
                ensure => $enabled_running,
                enable    => $enabled_startup,
                start           => "${init_script} start",
                stop            => "${init_script} stop",
                status          => "${init_script} status",
                restart         => "${init_script} restart",
                hasrestart => true,
                hasstatus => true,
        }
        if $enabled_startup != true {
                exec { "disable_influxdb":
                        command => "/bin/systemctl disable influxdb",
                        cwd => "/",
                }
        }
        logrotate::rule { 'influxdb':
                path                    => "${dir_logs}/*.log",
                rotate          => 10,
                missingok               => true,
                copytruncate    => true,
                dateext                 => true,
                size            => '10M',
                rotate_every    => 'day',
        }
}


Grafana


Выбор веб-клиента для отображения данных был сделан давно (видел его в действии давненько и всегда хотел использовать в своём проекте). Вот несколько скриншотов с моего проекта:

image

image

image

Особенностями grafana являются:

  • Приятный внешний вид
  • Динамичное обновление всех данных
  • Визуальный конструктор
  • Подключение большого количества типов источников данных (Graphite, InfluxDB, Prometheus, Elasticsearch…)
  • Множество способов аутентификации
  • Возможность отправки Alert`ов ( Slack, PagerDuty, VictorOps, OpsGenie...)
  • Большое количество plugin`ов для расширения функционала

И ссылка на готовые dashboard'ы — есть чудесная возможность посмотреть как другие формируют графики и подчерпнуть что-то для себя (полезное и/или красивое). Я подчерпнул :)

Клиент, идущий в комплекте с самим InfluxDB – Chronograf пока не настолько хорош в части функционала.

Puppet-скрипт для установки и настройки Grafana
class storyline_infra::grafana () {
        include stdlib

        $params = lookup({"name" => "storyline_infra.grafana",
            "merge" => {"strategy" => "deep"}})
        $port = $params['port']
        $pid_file = $params['pid_file']
        $init_script = $params['init_script']
        $dir_data = $params['dir_data']
        $dir_logs = $params['dir_logs']
        $enabled_startup = $params['enabled_startup']
        $enabled_running = $params['enabled_running']
        $version = $params['version']

        user { 'grafana':
                ensure => "present",
                managehome => true,
        }
        exec { "grafana-mkdir":
                command => "/bin/mkdir -p /data/db && /bin/mkdir -p /data/logs",
                cwd => "/",
                unless => '/usr/bin/test -d /data/db -a -d /data/logs',
        } ->
        # working dir
        file { $dir_logs:
                ensure => "directory",
                recurse => "true",
                owner => "grafana",
                group=> "grafana",
                require => Exec['grafana-mkdir'],
        }
        file { $dir_data:
                ensure => "directory",
                recurse => "true",
                owner => "grafana",
                group=> "grafana",
                require => Exec['grafana-mkdir'],
        }
        # see by "gpg --verify keyfile"
        apt::key { 'grafana-key':
                id => '418A7F2FB0E1E6E7EABF6FE8C2E73424D59097AB',
                source  => 'https://packagecloud.io/gpg.key',
        } ->
        # deb https://packagecloud.io/grafana/stable/debian/ jessie main
        apt::source { 'grafana-repo':
                comment  => 'grafana repo',
                location => "https://packagecloud.io/grafana/stable/debian/",
                release => "jessie",
                repos    => 'main',
                include  => {
                        'deb' => true,
                },
        } ->
        package {  'grafana':
                ensure => 'present',
        }
        file { '/etc/init.d/grafana-server':
                ensure => 'absent',
        } ->
        file { '/etc/grafana':
                ensure => "directory",
        } ->
        file { "/etc/grafana/grafana.ini":
                replace => true,
                content => epp('storyline_infra/grafana.epp'),
                owner => "grafana",
                group=> "grafana",
                notify => Service['grafana'],
        } ->
        file { $init_script:
                replace => true,
                content => epp('storyline_infra/grafana_startup.epp'),
                mode=>"ug=rwx,o=r",
                notify => Service['grafana'],
        }->
        service { 'grafana':
                ensure => $enabled_running,
                enable    => $enabled_startup,
                start           => "${init_script} start",
                stop            => "${init_script} stop",
                status          => "${init_script} status",
                restart         => "${init_script} restart",
                hasrestart => true,
                hasstatus => true,
        }
        if $enabled_startup != true {
                exec { "disable_grafana":
                        command => "/bin/systemctl disable grafana",
                        cwd => "/",
                }
        }
}


О сборе данных


Основным источником отображения данных являются time-series данные из InfluxDB, а в неё они попадают из 2-х источников: демона collectd и java-библиотеки «com.github.davidb:metrics-influxdb».

Collectd


Collectd — демон, написанный на C, способный передавать данные своему сетевому аналогу, протокол которого InfluxDB и может эмулировать. «Из коробки» он может собирать достаточно большое количество метрик по серверному окружению и по части сервисов, возможность расширения достигается посредством модулей, написанных на python или Java.

Функционал collectd по сбору информации о функционировании сервера (физического и виртуального) меня вполне устраивает, однако дополнительные настройки требуемые для сбора данных со сторонних сервисов (в моём случае Elsticsearch, MongoDB и Apache Storm) достаточно нетривиальны и не всегда функциональны в полном объёме (например для Elsticsearch не корректно собирается информация по скорости выполнения запроса при наличии нескольких shard на разных репликах). Скорее всего следует посмотреть в сторону родного клиент InfluxDB — Telegraf.

Puppet-скрипт для установки и настройки Collectd
class storyline_infra::collectd () {
        include stdlib

        $params = lookup({"name" => "storyline_infra.collectd",
            "merge" => {"strategy" => "deep"}})
        $server_port = $params['server_port']
        $server_address = $params['server_address']
        $pid_file = $params['pid_file']
        $init_script = $params['init_script']
        $dir_data = $params['dir_data']
        $dir_logs = $params['dir_logs']
        $enabled_startup = $params['enabled_startup']
        $enabled_running = $params['enabled_running']
        $version = $params['version']
# mongo db
        $enabled_mongodb = $params['enabled_mongodb']
        $mongodb_user = $params['mongodb_user']
        $mongodb_password = $params['mongodb_password']
# storm db
        $enabled_storm = $params['enabled_storm']
        $storm_ui_url = $params['storm_ui_url']
        # elasticsearch
        $enabled_elasticsearch = $params['enabled_elasticsearch']
        $elasticsearch_host = $params['elasticsearch_host']
        $elasticsearch_port = $params['elasticsearch_port']
        $elasticsearch_cluster = $params['elasticsearch_cluster']

        exec { "collectd-mkdir":
                command => "/bin/mkdir -p /data/db && /bin/mkdir -p /data/logs",
                cwd => "/",
                unless => '/usr/bin/test -d /data/db -a -d /data/logs',
        } ->
        # working dir
        file { $dir_logs:
                ensure => "directory",
                recurse => "true",
                require => Exec['collectd-mkdir'],
        }
        file { $dir_data:
                ensure => "directory",
                recurse => "true",
                require => Exec['collectd-mkdir'],
        }
        package {  'collectd':
                #       ensure => $version,
                ensure => "present",
        } ->
        file { "/etc/collectd/collectd.conf":
                replace => true,
                content => epp('storyline_infra/collectd.epp'),
                notify => Service['collectd'],
        }->
        file { $init_script:
                replace => true,
                content => epp('storyline_infra/collectd_startup.epp'),
                mode=>"ug=rwx,o=r",
                notify => Service['collectd'],
        }->
        service { 'collectd':
                ensure => $enabled_running,
                enable    => $enabled_startup,
                start           => "${init_script} start",
                stop            => "${init_script} stop",
                status          => "${init_script} status",
                restart         => "${init_script} restart",
                hasrestart => true,
                hasstatus => true,
        }
        if $enabled_startup != true {
                exec { "disable_collectd":
                        command => "/bin/systemctl disable collectd & /bin/systemctl disable collectd.service",
                        cwd => "/",
                }
        }
        if $enabled_mongodb {
                package {  'python-pip':
                        ensure => "present",
                } ->
                exec { "install-pymongo":
                        command => "/usr/bin/python -m pip install pymongo",
                        cwd => "/",
                        unless => '/usr/bin/python -m pip show pymongo',
                } ->
                file { "/usr/share/collectd/mongodb":
                        ensure => "directory",
                }->
                file { "/usr/share/collectd/mongodb.py":
                        replace => true,
                        content => epp('storyline_infra/collectd_mongodb_py.epp'),
                }->
                file { "/usr/share/collectd/mongodb/types.db":
                        replace => true,
                        content => epp('storyline_infra/collectd_mongodb_types_db.epp'),
                }->
                file { "/etc/collectd/collectd.conf.d/mongodb.conf":
                        replace => true,
                        content => epp('storyline_infra/collectd_mongodb_conf.epp'),
                        notify => Service['collectd'],
                }
        } # if $enabled_mongodb {
        # https://github.com/srotya/storm-collectd
        if $enabled_storm {
                file { "/usr/share/collectd/java/storm-collectd.jar":
                        replace => true,
                        ensure => file,
                        source => "puppet:///modules/storyline_infra/storm-collectd.jar",
                }->
                file { "/etc/collectd/collectd.conf.d/storm.conf":
                        replace => true,
                        content => epp('storyline_infra/collectd_storm_conf.epp'),
                        notify => Service['collectd'],
                }
        } # if $enabled_mongodb {
        # https://github.com/signalfx/integrations/tree/master/collectd-elasticsearch
        # https://github.com/signalfx/collectd-elasticsearch
        if $enabled_elasticsearch {
                file { "/usr/share/collectd/elasticsearch.py":
                        replace => true,
                        content => epp('storyline_infra/collectd_elasticsearch_py.epp'),
                }->
                file { "/etc/collectd/collectd.conf.d/elasticsearch.conf":
                        replace => true,
                        content => epp('storyline_infra/collectd_elasticsearch_conf.epp'),
                        notify => Service['collectd'],
                }
        } # if $enabled_mongodb {
}


com.github.davidb:metrics-influxdb


Указанная библиотека фактически является адаптером для широко известной java-библиотеки метрик Metrics. Поддерживает протокол версии 0.9 для InfluxDB и позволяет передавать необходимую информацию в полном объёме.

Инициализация осуществляется примерно так:

if (metricsConfiguration.enabled) {
String hostName = InetAddress.getLocalHost().getCanonicalHostName();
final ScheduledReporter reporterInfluxDB = InfluxdbReporter.forRegistry(metricRegistry)
    .protocol(new HttpInfluxdbProtocol("http", metricsConfiguration.influxdbHost,
        metricsConfiguration.influxdbPort, metricsConfiguration.influxdbUser,
        metricsConfiguration.influxdbPassword, metricsConfiguration.influxdbDB))
    // rate + dim conversions
    .convertRatesTo(TimeUnit.SECONDS).convertDurationsTo(TimeUnit.MILLISECONDS)
    // filter
    .filter(MetricFilter.ALL)
    // don't skip
    .skipIdleMetrics(false)
    // hostname tag
    .tag("host", hostName)
    // !!! converter
    // al metrics must be of form: "processed_links.site_ru .crawling" -> "crawling
    // source=site_ru, param=processed_links value=0.1"
    .transformer(new CategoriesMetricMeasurementTransformer("param", "source"))
    .build();
reporterInfluxDB.start(metricsConfiguration.reportingPeriod, TimeUnit.SECONDS);
}

В дальнейшем используется обычный Metrics API, что позволяет повысить прозрачность того что и как делается в моём ПО в любой момент времени.

Указанная система позволила не только следить за работой системы в рабочем режиме, но и отслеживать изменения, вызванные внесенными изменениями – как увеличилось потребление памяти, скорость обработки, объем данных и т.д. Сейчас, в принципе, страничка с общим дашбоардом является закладкой на кухонном ноутбуке и утренний завтрак всегда сопровождается просмотром событий за последние 9 часов.

Спасибо за внимание!

Комментарии (6)


  1. Sovigod
    29.08.2017 16:53

    InfluxDB для метрик отличное решение. Но если вам нужно метрик больше чем за пару последних недель — вам обязательно нужно использовать Continuous Queries для уплотнения данных. Тогда графики даже за несколько лет будут моментально отображаться.


    1. fedor_malyshkin Автор
      29.08.2017 20:35

      Полностью согласен. Continuous Queries вместе с Retention Policy даёт возможность хранить данные за почти произвольный период. Однако нужно решить как долго хранить и что. Для незнакомых с CQ и RP: Разные метрики нужно downsampl’ить по разному – где-то подойдёт среднее, где-то максимум. Я сейчас просто удаляю данные старше 2-х недель без downsamplin’га.


  1. rudenkovk
    30.08.2017 11:41

    Почему collectd, а не нативный для стека influx telegraf?


    1. fedor_malyshkin Автор
      30.08.2017 14:47

      Ранее был опыт с работы с ним. Однако его поддержка новых продуктов не на высоте, хотя стабильность и низкие требования к ресурсам нареканий не вызывает. По тексту я уже писал, что к Telegraf придётся вернуться.


      1. rudenkovk
        30.08.2017 15:08
        +1

        Сорян, не заметил :)


        они достаточно бодро пишут плагины. Правда на паре проектов я держу свои ветки, где делаем свои датчики сбрасывающий информации. Это куда удобнее чем писать на С для collectd. Или запускать питоновские скрипты, через обертку collectd.


        1. fedor_malyshkin Автор
          30.08.2017 15:09

          Понял. Как говорится: «хозяйке на заметку».