python

Сбор и визуализация метрик приложения в Graphite и Graph-Explorer

  • понедельник, 22 июня 2015 г. в 02:11:14
http://habrahabr.ru/post/260753/

Зачастую возникает необходимость отслеживать различные параметры работы приложения/сервиса. Например, интерес представляет количество запросов в секунду, среднее время ответа сервера, количество ответов сервера с различным HTTP-статусом (технические метрики), количество регистраций пользователей в час, количество платежных транзакций в минуту (бизнес-метрики) и пр. Без системы сбора метрик разработка и сопровождение продукта происходит практически вслепую.



Данная статья является руководством по настройке системы сбора и анализа метрик приложения на базе Graphite и vimeo/graph-explorer.

Мотивация


Система сбора метрик не является монолитом. При ее развертывании приходится иметь дело со значительным числом компонентов, каждый из которых каким-то образом взаимодействует с остальными, имеет свой конфигурационный файл и неповторимый способ запуска. Даже Graphite, сам по себе, состоит минимум из трех подсисем — демон сбора метрик (carbon), БД с метриками (whisper и др.) и веб-приложение для визуализации. Когда же необходимо добавить поддержку graph-explorer, все становится еще интереснее. Каждая из подсистем имеет свою обособленную документацию, но нигде нет документа, описывающего картину вцелом.

Метрики


Метрика — это последовательность (числовых) значений во времени. Очень простая вещь по сути. Фактически есть некоторый строковый ключ и соответствующий ему ряд (sample1, time1), (sample2, time2),… Типичным для Graphite способом именования метрик является разделение строковых ключей на части при помощи символа ".", например, stats.web.request.GET.time. Graphite позволяет группировать метрики с общим префиксом, используя символ "*" при построении графиков. Очевидно, что это далеко не самый гибкий способ работы с ключами. Если понадобится добавить еще какой-либо компонент к ключу, это может поломать построение графиков. Например, приведение ключа из примера выше к виду stats.web.server1.request.GET.time нарушит общий префикс для исторических данных. Вторым существенным недостатком такого именования метрик является потенциальная неоднозначность их трактования. Куда более самодостаточными были бы метрики, обладающие ключами вида service=web server=server1 what=request_time unit=ms с дальнейшей возможностью построения комбинированных графиков по общим тегам, а не только общим префиксам. К счастью, ребята из vimeo придумали metrics 2.0 и запили свой graph-explorer, работающий поверх graphite. Основная идея — это логическое представление метрик, как сущностей с набором пар тег-значение. Каждая метрика в формате 2.0 все равно в конечном итоге преобразуется в обычный строковый ключ, разделенный точками и попадает в carbon, но предварительно в отдельном хранилище создается «индекс», хранящий информацию о соответствии этих ключей и пар тег-значение. Таким образом, пользуясь информацией из индексов, graph-explorer и реализует комбинирование различных метрик на одном графике.

Общий взгляд


В общем виде система сбора метрик может быть представлена следующий диаграммой:

Таким образом, приложение (веб-сервис, демон, etc.), написанное не любом языке, через некоторый интерфейс (прослойку) отправляет метрики в коллектор, коллектор их частично агрегирует, вычисляет частоту обновления (опционально) и раз в некоторый промежуток времени отправляет их в carbon, который постепенно складывает их в хранилище. Веб-приложение тянет данные из хранилища (и частично из самого carbon) и строит для нас графики. Демон carbon на самом деле — это целых 3 демона: carbon-cache, carbon-relay и carbon-aggregator. В простейшем случае можно использовать carbon-cache. Реализацию carbon-relay можно использовать с целью шардирования (распределение нагрузки между несколькими carbon-cache) или реплицирования (отправка одних и тех же метрик на несолько carbon-cache). Демон carbon-aggregator умеет выполнять промежуточную обработку метрик перед отправкой их в хранилище. Данные метрик в carbon могут быть переданы по одному из двух форматов: в plain text (т.н. line protocol) на порт 2003 и сериализованные в pickle на порт 2004. При этом carbon-relay на выход отдает данные только в pickle (важно!).

Надстройка graph-explorer добавляет еще одно хранилище для т.н. индексов метрик. В качестве такого хранилища используется elastic search. Очевидно, что в каком-то месте представленной на диаграмме системы необходимо добавить звено, которое будет осуществлять «индексацию» метрик. Таким звеном является carbon-tagger. В итоге, система принимает следующий вид:


Стек технологий


Далее идет уже конкретика, в вашем случае возможно какой-то из компонентов будет заменен на другое решение.

Система предназначена для сбора именно метрик 2.0 с последующим их использованием в graph-explorer.

Установка


Установка будет происходить в директорию /opt/graphite, являющуюся директорией по умолчанию. Часть компонентов написана на Go, так что его также придется предварительно установить и прописать соответствующие переменные окружения. Установка подразумевает, что Go в системе один. Если у вас установлено несколько версий Go, то этот шаг можете пропустить и настроить нужную версию по своему усмотрению.

cd /opt
wget https://storage.googleapis.com/golang/go1.4.2.linux-amd64.tar.gz
tar -C /usr/local -xzf go1.4.2.linux-amd64.tar.gz
echo 'export PATH=$PATH:/usr/local/go/bin' >> /etc/profile
echo 'export GOPATH=/opt/go' >> /etc/profile
echo 'export GOBIN="$GOPATH/bin"' >> /etc/profile
echo 'export PATH=$PATH:$GOBIN' >> /etc/profile
source /etc/profile

go env 
# Вывод должен содержать следующие строчки
#GOBIN="/opt/go/bin"
#GOPATH="/opt/go"
#GOROOT="/usr/local/go"

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

statsd python клиент


В качестве клиента, занимающегося отправкой метрик из приложения, выбран наиболее популярный statsd. Его реализация бесконечно проста. Фактически, это отправка текстовых данных на указанный UDP/TCP-порт с добавлением минимальных протокольных служебных данных.

# внутри виртуального окружения приложения
pip install statsd

Пример использования в коде приложения:

import statsd

client = statsd.StatsClient(host='statsdaemon.local', port=8125)
# отправка метрики в metrics 2.0 формате tag_is_value
client.gauge('service_is_myapp.server_is_web1.what_is_http_request.unit_is_ms', <execution_time>)

statsd


В «классической» схеме установки graphite зачастую в качестве промежуточного коллектора используется statsd. В нашем случае использован statsdaemon, так как он из коробки умеет работать с метриками 2.0, при этом сохраняя обратную совместимость с протоколом statsd. Он написан на Go и его установка чрезвычайно проста (осторожно, сейчас в README.md досадная ошибка в команде установки):

go get github.com/Vimeo/statsdaemon/statsdaemon

После этого в директории /opt/go/bin должен появиться исполняемый файл statsdaemon. Настройки этого демона достаточно просты:
statsdaemon.ini
# --- /etc/statsdaemon.ini ---
listen_addr = ":8125"  # сюда будет слать метрики statsd-клиент (приложение)
admin_addr = ":8126"
graphite_addr = "carbon.local:2013" # адрес carbon для агрегированных метрик, раз в flush_interval сек
flush_interval = 30
    
prefix_rates = "stats."
prefix_timers = "stats.timers."
prefix_gauges = "stats.gauges."
    
percentile_thresholds = "90,75"
max_timers_per_s = 1000


Запуск statsdaemon:

statsdaemon -config_file="/etc/statsdaemon.ini" -debug=true

На этом этапе уже можно запустить statsdaemon и послать в него несколько пакетов из приложения с помощью statsd-клиент. Вывод в консоль будет говорить сам за себя.

Graphite


Актуальное руководство по установке находится здесь. Установку лучше проводить внутри virtual environment, располагающегося в /opt/graphite.

sudo apt-get install python-pip python-dev
pip install pip --upgrade
pip install virtualenv

mkdir /opt/graphite
virtualenv /opt/graphite
cd /opt/graphite
source bin/activate

sudo apt-get install libcairo2 python-cairo libffi-dev # установка нужных для graphite пакетов

pip install https://github.com/graphite-project/ceres/tarball/master
pip install whisper
pip install carbon # pip install carbon --install-option="--prefix=/opt/graphite" --install-option="--install-lib=/opt/graphite/lib"
pip install graphite-web # pip install graphite-web --install-option="--prefix=/opt/graphite" --install-option="--install-lib=/opt/graphite/webapp"

# нужно для Graphite WebApp
pip install uwsgi 
pip install django
pip install cairocffi
pip install django-tagging

# инициализация webapp
(cd /opt/graphite/webapp/graphite; python manage.py syncdb)

После установки, graphite будет располагаться в /opt/graphite. Далее необходимо выполнить его конфигурацию. Пример файлов с настройками находятся в /opt/graphite/conf. Минимум, что необходимо сделать, это создать файл настроек carbon и whisper.

cp /opt/graphite/conf/carbon.conf.example /opt/graphite/conf/carbon.conf
# carbon.conf содержит настройки для carbon-cache, carbon-relay и carbon-aggregator.
# Необходимо настроить как минимум следующие значения в секции carbon-cache:
# LINE_RECEIVER_INTERFACE = 127.0.0.1
# LINE_RECEIVER_PORT = 2003

cp /opt/graphite/conf/storage-schemas.conf.example /opt/graphite/storage-schemas.conf
# storage-schemas.conf содержит настройки whisper, который по сути - fixed-size db.
# Аллокация места под метрики происходит 1 раз, поэтому нужно явно задать (по
# ключу метрики), с какой частотой дескритизации и за какой период хранить данные.
...

Далее необходимо запустить carbon-cache:

carbon-cache.py --conf=conf/carbon.conf start # --debug
tail -f /opt/graphite/storage/log/carbon-cache/carbon-cache-a/*.log

И graphite webapp с помощью uwsgi + какой-либо web-сервер (например, nginx):

cp /opt/graphite/webapp/graphite/local_settings.py.example /opt/graphite/webapp/graphite/local_settings.py
# в local_settings.py необходимо изменить SECRET_KEY и TIME_ZONE.
/opt/graphite/bin/uwsgi --socket localhost:6001 --master --processes 4 --home /opt/graphite --pythonpath /opt/graphite/webapp/graphite --wsgi-file=/opt/graphite/conf/graphite.wsgi.example --daemonize=/var/log/graphite-uwsgi.log

Настройки nginx:
graphite.conf
upstream graphite_upstream {
    server 127.0.0.1:6001;
}
    
server {
    listen 8085;
    server_name graphite.local;
    
    location / {
        include            uwsgi_params;
        uwsgi_pass         graphite_upstream;
    
        add_header 'Access-Control-Allow-Origin' '*';
        add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
        add_header 'Access-Control-Allow-Headers' 'origin, authorization, accept';
        add_header 'Access-Control-Allow-Credentials' 'true';
    
        proxy_redirect     off;
        proxy_set_header   Host $host;
        proxy_set_header   X-Real-IP $remote_addr;
        proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header   X-Forwarded-Host $server_name;
    }
}


Остается установить только carbon-tagger (именно он заполняет базу индексов для graph-explorer) и настроить дублирующую отправку метрик в carbon-cache и carbon-tagger при помощи carbon-relay. Но к сожалению, carbon-tagger не умеет работать по протоколу pickle, а carbon-relay отдает данные только в таком формате. Поэтому необходимо установить drop-in замену carbon-relay от vimeo — carbon-relay-ng:

go get -d github.com/graphite-ng/carbon-relay-ng
go get github.com/jteeuwen/go-bindata/...
cd "/opt/go/src/github.com/graphite-ng/carbon-relay-ng"
make
cp carbon-relay-ng /opt/go/bin/carbon-relay-ng
touch /opt/graphite/conf/carbon-relay-ng.ini
cd /opt/graphite
carbon-relay-ng conf/carbon-relay-ng.ini

carbon-relay-ng.ini
instance = "default"
listen_addr = "127.0.0.1:2013"
admin_addr = "127.0.0.1:2014"
http_addr = "127.0.0.1:8081"
spool_dir = "spool"
log_level = "notice"
bad_metrics_max_age = "24h"
    
init = [
     'addRoute sendAllMatch carbon-default  127.0.0.1:2003 spool=true pickle=false', # отправляем все в carbon-cache
     'addRoute sendAllMatch carbon-tagger  127.0.0.1:2023 spool=true pickle=false'  # отправляем все в carbon-tagger
]
    
[instrumentation]
graphite_addr = ""
graphite_interval = 1000 


carbon-tagger


Демон carbon-tagger написан на Go и занимается отправкой индексов метрик в Elastic Search для последующего их использования в graph-explorer. Прежде всего на сервере необходимо установить java и Elastic Search. Установка carbon-tagger:

go get github.com/Vimeo/carbon-tagger
go get github.com/mjibson/party
go build github.com/Vimeo/carbon-tagger

carbon-tagger.conf
[in]
port = 2023 # сюда присылает метрики carbon-relay-ng
    
[elasticsearch]
host = "esearch.local"
port = 9200
index = "graphite_metrics2"
flush_interval = 2
max_backlog = 10000
max_pending = 5000
    
[stats]
host = "localhost"
port = 2003 # сюда carbon-tagger будет отправлять собственные внутренние метрики (не метрики приложения)
id = "default"
flush_interval = 10
http_addr = "127.0.0.1:8123"


Запуск carbon-tagger:

(cd /opt/go/src/github.com/Vimeo/carbon-tagger/; ./recreate_index.sh) # инициализация индексов в ES
carbon-tagger -config="/opt/graphite/conf/carbon-tagger.conf" -verbose=true

graph-explorer


И наконец, установка гвоздя программы:

pip install graph-explorer

graph-explorer.conf
[graph_explorer]
listen_host = 127.0.0.1 # локальный адрес, чтобы добавить HTTP Basic Auth через nginx
listen_port = 8080
filename_metrics = metrics.json
log_file = /var/log/graph-explorer/graph-explorer.log
    
[graphite]
url_server = http://localhost
url_client = http://graphite.local:8085 # адрес graphite webapp


nginx/graph-explorer.conf
server {
    listen 80;
    server_name metrics.yourproject.net;
    
    location / {
        auth_basic           "Who are you?";
        auth_basic_user_file /etc/nginx/.htpasswd;
        proxy_pass http://localhost:8080;
    }
}


Запуск graph-explorer:

mkdir /var/log/graph-explorer
run_graph_explorer.py /opt/graphite/conf/graph_explorer.conf

После этого веб-интерфейс graph-explorer будет доступен по адресу metrics.yourproject.net.

Вместо заключения


Хватит жить разрабатывать с закрытыми глазами, %habrauser%! Разворачивайте системы сбора метрик и делитесь занимательными графиками из ваших проектов! Спасибо за внимание!