Grafana: создание dashboard

Автор: | 23/07/2018

Задача – добавить дашборд для отображения различной статистики с бекенда.

Ниже описывается процесс создания дашборды, рассматриваются примеры запросов из Grafana к Prometheus для получения данных, настройки различных типов панелей, примеры метрик, которые можно использовать.

Основная цель поста – записать для себя примеры использования Grafana, т.к. подобных примеров не встречал, хотя у Grafana отличная документация.

Для примеров запросов использовалась в основном борда Node Exporter Full 0.16 для метрик EC2, и борда AWS ELB Application Load Balancer для ALB.

Добавлять будем:

  • вверху – статусы хоста:
    • часы (время с момента получения последней метрики)
    • CPU usage %
    • Load Average %
    • memory usage %
    • root, /data disks % usage
  • статусы сервисов:
    • nginx
    • php-fpm
    • rabbitmq status
    • memcached status
    • redis status
    • RDS статус
  • статистика EC2 (node_exporter):
    • CPU
      • CPU System/User/IOwait/Idle
    • Memory:
      • RAM total
      • RAM used
      • RAM free
    • Disk
      • IO time
    • Processes
      • Blocked I/O
      • Running
  • статистика Load Balancer
    • RequestCount / Latency
      • TargetResponseTime_Average
      • ActiveConnectionCount_Average
    • HTTPCode
      • HTTPCode_Target_2XX_Count_Average
      • HTTPCode_Target_3XX_Count_Average
      • HTTPCode_Target_4XX_Count_Average
      • HTTPCode_Target_5XX_Count_Average

Настройки dashboard

Переходим в Settings, задаём имя дашборды:

Переменные

Документация – тут>>> и тут>>>.

У нас есть два бекеда – Dev и Production.

Соответственно – в дашборде хочется выводить статистику и того, и другого. Для этого – добавим переменную, с помощью которой сможем переключаться между ними.

Переходим в Variables:

  • в Name задаём имя, которое будет использоваться в запросах
  • Type – оставляем Query
  • Label – имя, как оно будет отображаться в дашборде для выбора
  • Data source – Prometheus
  • Refresh – при загрузке дашборда
  • Query – собственно, сам запрос, который вернёт нам значения, и из которого будем получать список окружений
    в данном случае Prometheus-сервер, который запущен на EC2, добавляет external_label в виде env=mobilebackend-dev, его и используем
    для получения значений – используем метрику node_boot_time_seconds, фильтруем вывод по метке job="node-exporter"
    запрос получается:
    label_values(node_boot_time_seconds{job="node-exporter"}, env)

Сохраняем – Add внизу.

Статусы хоста

Первым добавим блок, в котором убдут выводить % использования CPU, Load Avareage, память и диски.

CPU Busy

node_cpu_seconds_total

Прежде, чем заниматься настройкой отображения CPU Busy – давайте вспомним /proc/stats.

Сначала – получим время с момента запуска системы:

[simterm]

root@bm-backed-app-dev:/opt/prometheus-client# cat /proc/uptime
2168671.61 2147672.65

[/simterm]

Тут:

  • первая колонка – аптйам системы в секундах
  • вторая – время в сукундах, проведённое в IDLE

Проверяем:

[simterm]

root@bm-backed-app-dev:/opt/prometheus-client# uptime 
 14:21:45 up 25 days,  2:37

[/simterm]

Считаем – кол-во секунд из /proc/uptime делим на часы и кол-во часов в сутках:

[simterm]

root@bm-backed-app-dev:/opt/prometheus-client# echo 2168671 / 3600 / 24 | bc
25

[/simterm]

Хорошо, тут всё сходится.

Теперь считаем stats:

[simterm]

root@bm-backed-app-dev:/opt/prometheus-client# cat /proc/stat 
...
cpu0 1435156 1188 466232 213091141 58015 0 29879 134098 0 0
...
btime 1529657067

[/simterm]

Тут колонки:

  • user: normal processes executing in user mode
  • nice: niced processes executing in user mode
  • system: processes executing in kernel mode
  • idle: twiddling thumbs
  • iowait: waiting for I/O to complete
  • irq: servicing interrupts
  • softirq: servicing softirqs

Время в сотых секунды.

btime – время загрузки системы в секундах с момента January 1, 1970 (UNIX epoch).

Теперь попробуем посчитать:

[simterm]

root@bm-backed-app-dev:/opt/prometheus-client# echo "(1435156+1188+466232+213091141+58015+0+29879+134098) / 100 / 3600 / 24" | bc
24

[/simterm]

Выполняем сложение всех счётчиков, делим на 100 – получаем общее кол-во секунд.

Потом, аналогично вычислениям с uptime – получаем кол-во дней, вышло 24 – один день (точнее 4 часа) “потерялся”, но не критично – в целом значения сошлись.

Теперь выведем метрики node_exporter, и сравним их с данными из /proc/stat (на самом деле метрики я вывел немного раньше, поэтому будет разница):

[simterm]

root@bm-backed-app-dev:/opt/prometheus-client# curl -s localhost:9100/metrics | grep node_cpu_seconds_total
# HELP node_cpu_seconds_total Seconds the cpus spent in each mode.
# TYPE node_cpu_seconds_total counter
node_cpu_seconds_total{cpu="0",mode="idle"} 2.13035164e+06
node_cpu_seconds_total{cpu="0",mode="iowait"} 579.98
node_cpu_seconds_total{cpu="0",mode="irq"} 0
node_cpu_seconds_total{cpu="0",mode="nice"} 11.88
node_cpu_seconds_total{cpu="0",mode="softirq"} 298.71
node_cpu_seconds_total{cpu="0",mode="steal"} 1340.68
node_cpu_seconds_total{cpu="0",mode="system"} 4660.78
node_cpu_seconds_total{cpu="0",mode="user"} 14346.37

[/simterm]

И сравниваем с stat:

  • user: stat = 14351.56, exporter = 14346.37
  • system: stat = 4662.32, exporter = 4660.78

Окей – тут тоже всё более-менее сходится, и значение данных из node_cpu_seconds_total понятно.

Запрос

Теперь рассмотрим запрос, который будем использовать для получения CPU Busy %:

(((count(count(node_cpu_seconds_total{env="$environment"}) by (cpu))) - avg(sum by (mode)(irate(node_cpu_seconds_total{mode='idle',env="$environment"}[5m])))) * 100) / count(count(node_cpu_seconds_total{env="$environment"}) by (cpu))

Тут:

  • count – считаем кол-во элементов, полученных из запроса
  • avg: общее среднее значение, документация тут>>>
  • irate –  считает значение в секунду, основываясь на двух последних данных
  • node_cpu_seconds_total – секунды в каждом режиме (system, user, idle etc)
  • {env=~"$environment"} – выборка по значению переменной $environment
Подсчёт кол-ва ядер

Кол-во ядер мы получаем запросом ((count(count(node_cpu_seconds_total{env="$environment"}) by (cpu))).

Рассмотрим его детальнее.

Сначала сделаем выборку по node_cpu_seconds_total{env=~"mobilebackend-dev"}:

Так мы получим значения node_cpu_seconds_total по каждому типу – iowait, user, system, nice etc.

В count(node_cpu_seconds_total{env=~"mobilebackend-dev"}) считаем общее кол-во элементов (iowait, user, system, nice etc), хотя оно нам не надо – мы просто используем этот массив для следующего запроса.

А следующий запрос – count(node_cpu_seconds_total{env=~"mobilebackend-dev"}) by (cpu) возвращает нам общее кол-во node_cpu_seconds_total по типам для каждого ядра:

Например для Production это будет выглядеть так:

И в конце-концов добавив ещё один счётчик – count, и превратив запрос в count(count(node_cpu_seconds_total{env=~"mobilebackend-dev"}) by (cpu)) – мы получим кол-во ядер:

Production:

Для наглядности – проверяем на серверах:

[simterm]

root@bm-backed-app-dev:/opt/prometheus-client# grep -c ^processor /proc/cpuinfo   
1

[/simterm]

И прод:

[simterm]

root@mobilebackend-production:/data/projects# grep -c ^processor /proc/cpuinfo   
8

[/simterm]

Время ядер в idle

По теме – Understanding Machine CPU usage.

Следующая часть запроса – avg(sum by (mode) (irate(node_cpu_seconds_total{mode='idle',env="$environment"}[5m]))).

Сначала выполняем irate(node_cpu_seconds_total{mode='idle',env="mobilebackend-dev"}[5m]):

Так мы получаем среднее значение времени в секундах, которое cpu0 провёл в статусе idle, т.е. бездельничал.

А “завернув” этот запрос в avg(sum by (mode)() – получим среднее значение для всех ядер:

И если для Dev разницы нет, то на Prod с его 8-ю ядрами значение будет более наглядным:

Т.е. вместе все 8 ядер провели 7.95 секунд в режиме idle.

Если принять 8 за 100%, то вычислим %, который ядра провели не в idle за 1 секунду:

[simterm]

>>> (8.0 - 7.95) * 100 / 8.0
0.6249999999999978

[/simterm]

0.6% времени проведено в других режимах – system, user etc.

Если бы, к примеру, все ядра вместе провели 8 секунд в idle (т.е. каждое ядро за 1 целую секунду провело 1 целую секунду в idle):

[simterm]

>>> (8.0 - 8.0) * 100 / 8.0
0.0

[/simterm]

То нагрузка на ЦПУ была бы 0%.

И наоборот, если бы половина времени, т.е. 4 секунды в общем, были бы потрачены в idle, то результат:

[simterm]

>>> (8.0 - 4.0) * 100 / 8.0
50.0

[/simterm]

50% idle, 50% – остальные режимы.

Вообще есть хорошая страничка тут – Как найти процент от числа для тех, кто как я не силён в математике 🙂

Настройка панели

Теперь переходим к панели.

Жмём на зголовок панели – Edit:

В General задаём имя:

В Metrics добавляем наш запрос:

В Options настраиваем вид:

  • Gauge – включаем отображение шкалы
  • Spark lines – строка “истории”
  • Coloring – включаем красивую подсветку по значениям, и в Thresholds задаём значения, при которых цвет будет меняться – на оранжевый при 75%, и на красный – при 90%
  • Stat – Current
  • Unit – выбираем None > percent 1-100

Получается такое:

Возвращаемся к дашборе, добавляем ещё один елемент – Row:

Задаём имя, меняем размер панельки с CPU Busy:

Load Average

Следующая панелька будет выводить Load Average.

По теме – Linux: CPU Load – когда пора волноваться или что значит Load Average.

Можно было бы вывести просто значение node_load1{env=~"$environment"} – но на Dev сервере одно ядро, и значение node_load1 == 1 будет являться условными 100% для одного ядра, а на Production с его 8 ядрами node_load1 == 1 будет всего:

[simterm]

>>> 1.0 / 8 * 100
12.5

[/simterm]

12.5%

Значит, что бы корректно отрисовывать шкалу – нам потребуется получить значение LA, поделить его на кол-во ядер и умножить на 100 – получим % от “максимального” (в кавычках, потому что LA может быть и выше 1 для 1 ядра или 8 для 8 ядер) значения.

Следовательно – используем такой запрос:

avg(node_load1{env=~"$environment"}) / count(count(node_cpu_seconds_total{env=~"$environment"}) by (cpu)) * 100

В count(count(node_cpu_seconds_total{env=~"$environment"}) by (cpu)) – получаем кол-во ядер, уже рассматривали выше.

Добавляем Singlestat панель, добавляем метрику:

Настраиваем шкалу аналогично CPU Busy:

Перетаскиваем её в Row, меняем размер:

Проверим поведение.

Устанавливаем стресс-тест для CPU:

[simterm]

root@bm-backed-app-dev:/opt/prometheus-client# apt install stress

[/simterm]

Запускаем его:

[simterm]

root@bm-backed-app-dev:/opt/prometheus-client# stress --cpu 8 --timeout 20

[/simterm]

И данные из uptime:

[simterm]

root@bm-backed-app-dev:/opt/prometheus-client# uptime 
 18:03:18 up 25 days,  6:18,  1 user,  load average: 1.65, 1.13, 0.64

[/simterm]

Memory usage

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

Проверим free на проде:

[simterm]

root@mobilebackend-production:/data/projects# free -h
              total        used        free      shared  buff/cache   available
Mem:            15G        2.8G        7.5G         93M        4.8G         11G

[/simterm]

15 ГБ всего, 2.8 занято активными процессами, 4.8 – кеш, свободной памяти 7.5. ОК.

Составим запрос:

((node_memory_MemTotal_bytes{env="$environment"} - node_memory_MemFree_bytes{env="$environment"}) / (node_memory_MemTotal_bytes{env="$environment"} )) * 100
  • в (node_memory_MemTotal_bytes{env="$environment"} - node_memory_MemFree_bytes{env="$environment"})  считаем общее кол-во занятой памяти (active + cache), назовём её busy
  • и считаем busy / total * 100 – получаем % от свободной памяти

В Options включаем шкалу, настраиваем аналогично примерам выше:

Получается теперь:

Disk usage

В Prometheus выполняем запрос для проверки:

node_filesystem_avail_bytes{env="mobilebackend-dev",fstype="ext4"}

Добавляем панель, добавляем в неё запрос на получение метрик о /rootfs и вычисляем % занятого места:

100 - ((node_filesystem_avail_bytes{env="$environment",mountpoint="/rootfs",fstype="ext4"} * 100) / node_filesystem_size_bytes{env="$environment",mountpoint="/rootfs",fstype="ext4"})

Настраиваем шкалу аналогично предыдущим:

Повторяем для второго диска – /rootfs/data, получаем такую картинку в дашборде:

Текущеее время

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

Для вывода времени – опять используем Singlestat панели, и функцию timestamp(), которой передадим метрику up (можно любую – нам только требуется получить из метрики время).

Создаём панель, в Metrics указываем:

timestamp(up{instance="localhost:9090",job="prometheus"}) * 1000

В Options в Units выбираем YYYY-MM-DD HH:mm:ss и получаем текущее время (правда дата не выводится, но она и не нужна):

Сортируем по желанию, и получаем первую часть дашборды:

Статусы сервисов

Теперь – добавим простое отображение стаусов Up/Down для сервисов, которые работают на хосте и необходимы для работы приложения.

У нас это:

  • nginx
  • php-fpm
  • memcahed
  • redis
  • rabbitmq
  • AWS RDS
  • AWS ALB

Memcahed статус

Начнём с вывода статуса memcahed.

Создаём новую Row, в ней – новую Singlestat панель.

На сервере запущен quay.io/prometheus/memcached-exporter, который среди прочих метрик возвращает memcahed_up:

...
  memcached-exporter:
    image: quay.io/prometheus/memcached-exporter
    network_mode: "host"
...

memcahed на сервере один, потому можем создать простой запрос на получение кол-ва сервисов в up:

memcached_up{env="$environment",instance="localhost:9150",job="memcached-exporter"}

В Value mappings добавляем отображение статуса – Up, если memcahed_up == 1, или DOWN – если ноль:

В Options > Threshold задаём значения 1,1 (меньше одного – сразу красным) и Stat Current, получаем симпатичный статус:

RabbitMQ статус

Запущен https://github.com/kbudde/rabbitmq_exporter.

Создаём пользователя для доступа експортёра:

[simterm]

root@bm-backed-app-dev:/opt/prometheus-client# rabbitmqctl add_user prometheus password
Creating user "prometheus" ...

[/simterm]

Добавляем ему тег “мониторинг”:

[simterm]

root@bm-backed-app-dev:/opt/prometheus-client# rabbitmqctl set_user_tags prometheus monitoring

[/simterm]

Добавлем его в переменные контейнера:

...
  rabbitmq-exporter:
    image: kbudde/rabbitmq-exporter
    network_mode: "host"
    environment:
    - PUBLISH_PORT=9419
    - RABBIT_USER=prometheus
    - RABBIT_PASSWORD=password
...

Перезапускаем сервис:

[simterm]

root@bm-backed-app-dev:/opt/prometheus-client# systemctl restart prometheus-client.service

[/simterm]

Проверяем метрики:

[simterm]

root@bm-backed-app-dev:/home/admin# curl -s localhost:9419/metrics | grep -v \# | grep _up
rabbitmq_up 1

[/simterm]

И добавляем ещё одну Singlestat панель:

PHP-FPM stats

Повторяем для NGINX – для него всё, как в примерах выше, и добавляем ещё один статус – для PHP-FPM.

Единственное отличие тут – это количество.

Если все сервисы запущены по одному на дев/прод сервер, то php-fpm запущено 6 пулов:

[simterm]

root@bm-backed-app-dev:/opt/prometheus-client# ps aux | grep fpm: | grep -v grep | grep -v master | awk '{print $11, $12, $13}' | uniq
php-fpm: pool dev.admin.domain1.com
php-fpm: pool dev.admin.domain2.com
php-fpm: pool dev.domain1.com
php-fpm: pool dev.domain2.com
php-fpm: pool dev.domain3.com
php-fpm: pool dev.admin.domain3.com

[/simterm]

Соответственно, php_fpm_up вернёт не 1, а 6 результатов – по 1 на каждый пул.

Учитывая это – формируем запрос в Metrics и используем count():

count(php_fpm_up{env="$environment",instance="localhost:9253",job="php-fpm-exporter"})

В Value mappings в Type вместо Value to text выбираем range to text:

MySQL stats

Т.к. mysql_exporter ещё не запущен – то тут кратенько пример его запуска.

В роли серверов баз данных используется AWS RDS, два мастер-инстанса.

Значит – надо запустить два екпортёра, по одному на каждый RDS. В Ansible шаблон Compose файл добавляем их:

...
  mysql_exporter_db1:
    image: prom/mysqld-exporter
    networks:
    - prometheus-client
    ports:
    - 9104:9104
    environment:
    - DATA_SOURCE_NAME={{ mysql_monitoring_db1_user }}:{{ mysql_monitoring_db1_pass }}@({{ mysql_monitoring_db1_host }}:3306)/

  mysql_exporter_db2:
    image: prom/mysqld-exporter
    networks:
    - prometheus-client
    ports:
    - 9105:9104
    environment:
    - DATA_SOURCE_NAME={{ mysql_monitoring_db2_user }}:{{ mysql_monitoring_db2_pass }}@({{ mysql_monitoring_db2_host }}:3306)/

Переменные берутся из Ansible vault с зашифрованными паролями.

Добавляем джобы в конфиг Прометеус сервера:

...
  - job_name: 'mysql_exporter_db1'
    static_configs:
      - targets:
        - 'localhost:9104'

  - job_name: 'mysql_exporter_db2'
    static_configs:
      - targets:
        - 'localhost:9105'

Деплоим, проверяем DB1:

[simterm]

root@mobilebackend-dev:/opt/prometheus-client# curl -s localhost:9104/metrics | grep -v \# | grep -w mysql_up
mysql_up 1

[/simterm]

И DB2 (второй екпортёр слушает на порту 9105):

[simterm]

root@mobilebackend-dev:/opt/prometheus-client# curl -s localhost:9105/metrics | grep -v \# | grep -w mysql_up
mysql_up 1

[/simterm]

Добавляем в Grafana:

mysql_up{env="$environment",instance="localhost:9104",job="mysql_exporter_db1"}

Load Balancer statistics

Добавим ещё несколько графиков – статистику с AWS Application Load Balancer.

Тут надо добавить ещё одну переменную – Load balancer.

К сожалению – cloudformation_exporter не умеет получать теги, поэтому пока придётся использовать просто имена ALB (надо будет посмотреть – может на стороне Prometheus можно будет сделать им relabel).

Добавляем переменную, в запросе указываем:

aws_applicationelb_request_count_sum{job="aws_applicationelb"}

В регулярном выражении – получаем только имя ALB:

/.*app\/(.*)"/

Добавляем Row, и в ней – новую панель, но на этот раз типа Graph, в Metrics добавим четыре запроса – на коды 2хх, 3хх, 4хх и 5хх от targets:

aws_applicationelb_httpcode_target_2_xx_count_sum{load_balancer="app/$load_balancer"}

Аналогично можно добавить статистику по времени ответа бекенда.

Добавляем панель Connections/response time – тут будем выводить кол-во активный сессий и время ответа targets.

Добавляем метрики:

aws_applicationelb_target_response_time_sum{load_balancer="app/$load_balancer"}

И активные сессии:

aws_applicationelb_active_connection_count_sum{load_balancer="app/$load_balancer"}

Screenshot_20180724_115527.png

Series overrides

Сейчас на графике отображаются юниты по оси Y слева и внизу.

Но – на этом графике используются метрики двух типов – в одном выводится count – кол-во сессий, а на втором – время ответа от бекенда до ALB в мс.

Хочется, что слева в шкале выводились числа, снизу время получения метрики, а слева – кол-во милисекнуд ответа бекенда.

Для этого – используем ещё одну интересную возможность Grafana – Series overrides. Интересный пост на эту тему есть тут – Advanced Graphing (Part1): Style Overrides.

Переходим в Axes, и включаем отображение шкалы Y справа, в юнитах используем милисекунды:

Далее переходим в Display > Series overrides > Add override, и в Alias or regex указываем алиас метрики, в данном случае мы хотим выводить время, основываясь на данных из `aws_applicationelb_target_response_time_sum`, который в метриках указывается как response_time ms:

Кликаем на “+” – добавляем желамое действие. Тут указываем отображание времени в Y Right и заодно – можно поиграть со цветом:

И всё вместе теперь выглядит так:

EC2 statistics

И последним – добавим графики EC2.

В принципе – тут ничего такого, что уже не рассматривалось выше.

CPU

Сначала – статистика использования CPU – System, User, IOwait, idle.

В самом простом виде запросы выглядел бы так:

sum by (instance)(rate(node_cpu_seconds_total{mode="system",env="$environment"}[5m])) * 100

Получаем % от времени, которое CPU проёвл в режиме system/user/idle и т.д.

Но в случае, когда у нас несколько ядер – добавляем вычисление % от кол-ва ядер, аналоигчно тому, как мы это делали для отрисовки шкалы с % LA и CPU Busy:

(avg(sum by (instance)(rate(node_cpu_seconds_total{mode="idle",env="$environment"}[5m])) * 100)) / count(count(node_cpu_seconds_total{env="$environment",instance="localhost:9100"}) by (cpu))

Memory

Добавляем панель RAM.

Выводим память всего:

node_memory_MemTotal_bytes{env="$environment"}

Использованной памяти:

node_memory_MemTotal_bytes{env="$environment"} - node_memory_MemFree_bytes{env="$environment"} - (node_memory_Cached_bytes{env="$environment"} + node_memory_Buffers_bytes{env="$environment"})

Свободной памяти:

node_memory_MemFree_bytes{env="$environment"}

Диск

Добавим отображение времени на read-write операции:

irate(node_disk_read_time_seconds_total{env="$environment"}[5m])
irate(node_disk_write_time_seconds_total{env="$environment"}[5m])

В Legend используем {{device}}, куда будет подставлено значение из label “device“:

Получается так:

Процессы

И последняя уже таблица – процессы в системе.

Выводим кол-во процессов в статусе run:

node_procs_running{env="$environment"}

И blocked:

node_procs_blocked{env="$environment"}

И всё вместе теперь выглядит так:

Готово.