Thank you for reading this post, don't forget to subscribe!
Задача – добавить дашборд для отображения различной статистики с бекенда.
Ниже описывается процесс создания дашборды, рассматриваются примеры запросов из Grafana к Prometheus для получения данных, настройки различных типов панелей, примеры метрик, которые можно использовать.
Для примеров запросов использовалась в основном борда 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
- CPU
- статистика 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
- RequestCount / Latency
Настройки 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
.
Сначала – получим время с момента запуска системы:
1 2 |
root@bm-backed-app-dev:/opt/prometheus-client# cat /proc/uptime 2168671.61 2147672.65 |
Тут:
- первая колонка – аптйам системы в секундах
- вторая – время в сукундах, проведённое в IDLE
Проверяем:
1 2 |
root@bm-backed-app-dev:/opt/prometheus-client# uptime 14:21:45 up 25 days, 2:37 |
Считаем – кол-во секунд из /proc/uptime
делим на часы и кол-во часов в сутках:
1 2 |
root@bm-backed-app-dev:/opt/prometheus-client# echo 2168671 / 3600 / 24 | bc 25 |
Хорошо, тут всё сходится.
Теперь считаем stats
:
1 2 3 4 5 |
root@bm-backed-app-dev:/opt/prometheus-client# cat /proc/stat ... cpu0 1435156 1188 466232 213091141 58015 0 29879 134098 0 0 ... btime 1529657067 |
Тут колонки:
user
: normal processes executing in user modenice
: niced processes executing in user modesystem
: processes executing in kernel modeidle
: twiddling thumbsiowait
: waiting for I/O to completeirq
: servicing interruptssoftirq
: servicing softirqs
Время в сотых секунды.
btime
– время загрузки системы в секундах с момента January 1, 1970 (UNIX epoch).
Теперь попробуем посчитать:
1 2 |
root@bm-backed-app-dev:/opt/prometheus-client# echo "(1435156+1188+466232+213091141+58015+0+29879+134098) / 100 / 3600 / 24" | bc 24 |
Выполняем сложение всех счётчиков, делим на 100 – получаем общее кол-во секунд.
Потом, аналогично вычислениям с uptime
– получаем кол-во дней, вышло 24 – один день (точнее 4 часа) “потерялся”, но не критично – в целом значения сошлись.
Теперь выведем метрики node_exporter
, и сравним их с данными из /proc/stat
(на самом деле метрики я вывел немного раньше, поэтому будет разница):
1 2 3 4 5 6 7 8 9 10 11 |
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 |
И сравниваем с stat
:
user
: stat = 14351.56, exporter = 14346.37system
: stat = 4662.32, exporter = 4660.78
Окей – тут тоже всё более-менее сходится, и значение данных из node_cpu_seconds_total
понятно.
Запрос
Теперь рассмотрим запрос, который будем использовать для получения CPU Busy %:
1 |
(((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
Для наглядности – проверяем на серверах:
1 2 |
root@bm-backed-app-dev:/opt/prometheus-client# grep -c ^processor /proc/cpuinfo 1 |
И прод:
1 2 |
root@mobilebackend-production:/data/projects# grep -c ^processor /proc/cpuinfo 8 |
Время ядер в 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 секунду:
1 2 |
>>> (8.0 - 7.95) * 100 / 8.0 0.6249999999999978 |
0.6% времени проведено в других режимах – system
, user
etc.
Если бы, к примеру, все ядра вместе провели 8 секунд в idle
(т.е. каждое ядро за 1 целую секунду провело 1 целую секунду в idle
):
1 2 |
>>> (8.0 - 8.0) * 100 / 8.0 0.0 |
То нагрузка на ЦПУ была бы 0%.
И наоборот, если бы половина времени, т.е. 4 секунды в общем, были бы потрачены в idle
, то результат:
1 2 |
>>> (8.0 - 4.0) * 100 / 8.0 50.0 |
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.
Можно было бы вывести просто значение node_load1{env=~"$environment"}
– но на Dev сервере одно ядро, и значение node_load1
== 1 будет являться условными 100% для одного ядра, а на Production с его 8 ядрами node_load1
== 1 будет всего:
1 2 |
>>> 1.0 / 8 * 100 12.5 |
12.5%
Значит, что бы корректно отрисовывать шкалу – нам потребуется получить значение LA, поделить его на кол-во ядер и умножить на 100 – получим % от “максимального” (в кавычках, потому что LA может быть и выше 1 для 1 ядра или 8 для 8 ядер) значения.
Следовательно – используем такой запрос:
1 |
avg(node_load1{env=~"$environment"}) / count(count(node_cpu_seconds_total{env=~"$environment"}) by (cpu)) * 100 |
Настраиваем шкалу аналогично CPU Busy:
Перетаскиваем её в Row, меняем размер:
Проверим поведение.
Устанавливаем стресс-тест для CPU:
root@bm-backed-app-dev:/opt/prometheus-client# apt install stress
Запускаем его:
И данные из uptime
:
1 2 |
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 |
Memory usage
Далее добавим шкалу с отображением занятой памяти.
Проверим free
на проде:
1 2 3 |
root@mobilebackend-production:/data/projects# free -h total used free shared buff/cache available Mem: 15G 2.8G 7.5G 93M 4.8G 11G |
15 ГБ всего, 2.8 занято активными процессами, 4.8 – кеш, свободной памяти 7.5. ОК.
Составим запрос:
1 |
((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 выполняем запрос для проверки:
1 |
node_filesystem_avail_bytes{env="mobilebackend-dev",fstype="ext4"} |
Добавляем панель, добавляем в неё запрос на получение метрик о /rootfs
и вычисляем % занятого места:
1 |
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 указываем:
1 |
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
:
1 2 3 4 5 |
... memcached-exporter: image: quay.io/prometheus/memcached-exporter network_mode: "host" ... |
memcahed
на сервере один, потому можем создать простой запрос на получение кол-ва сервисов в up
:
1 |
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.
Создаём пользователя для доступа експортёра:
1 2 |
root@bm-backed-app-dev:/opt/prometheus-client# rabbitmqctl add_user prometheus password Creating user "prometheus" ... |
Добавляем ему тег “мониторинг”:
1 |
root@bm-backed-app-dev:/opt/prometheus-client# rabbitmqctl set_user_tags prometheus monitoring |
Добавим его в переменные контейнера:
1 2 3 4 5 6 7 8 9 |
... rabbitmq-exporter: image: kbudde/rabbitmq-exporter network_mode: "host" environment: - PUBLISH_PORT=9419 - RABBIT_USER=prometheus - RABBIT_PASSWORD=password ... |
Перезапускаем сервис:
1 |
root@bm-backed-app-dev:/opt/prometheus-client# systemctl restart prometheus-client.service |
Проверяем метрики:
1 2 |
root@bm-backed-app-dev:/home/admin# curl -s localhost:9419/metrics | grep -v \# | grep _up rabbitmq_up 1 |
И добавляем ещё одну Singlestat панель:
PHP-FPM stats
Повторяем для NGINX – для него всё, как в примерах выше, и добавляем ещё один статус – для PHP-FPM.
Единственное отличие тут – это количество.
Если все сервисы запущены по одному на дев/прод сервер, то php-fpm
запущено 6 пулов:
1 2 3 4 5 6 7 8 |
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 |
Соответственно, php_fpm_up
вернёт не 1, а 6 результатов – по 1 на каждый пул.
Учитывая это – формируем запрос в Metrics и используем count()
:
1 |
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 файл добавляем их:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
... 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 с зашифрованными паролями.
Добавляем джобы в конфиг Прометеус сервера:
1 2 3 4 5 6 7 8 9 10 |
... - job_name: 'mysql_exporter_db1' static_configs: - targets: - 'localhost:9104' - job_name: 'mysql_exporter_db2' static_configs: - targets: - 'localhost:9105' |
Деплоим, проверяем DB1:
1 2 |
root@mobilebackend-dev:/opt/prometheus-client# curl -s localhost:9104/metrics | grep -v \# | grep -w mysql_up mysql_up 1 |
И DB2 (второй екпортёр слушает на порту 9105):
1 2 |
root@mobilebackend-dev:/opt/prometheus-client# curl -s localhost:9105/metrics | grep -v \# | grep -w mysql_up mysql_up 1 |
Добавляем в Grafana:
1 |
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).
Добавляем переменную, в запросе указываем:
1 |
aws_applicationelb_request_count_sum{job="aws_applicationelb"} |
В регулярном выражении – получаем только имя ALB:
1 |
/.*app\/(.*)"/ |
Добавляем Row, и в ней – новую панель, но на этот раз типа Graph, в Metrics добавим четыре запроса – на коды 2хх, 3хх, 4хх и 5хх от targets:
1 |
aws_applicationelb_httpcode_target_2_xx_count_sum{load_balancer="app/$load_balancer"} |
Аналогично можно добавить статистику по времени ответа бекенда.
Добавляем панель Connections/response time – тут будем выводить кол-во активный сессий и время ответа targets.
Добавляем метрики:
1 |
aws_applicationelb_target_response_time_sum{load_balancer="app/$load_balancer"} |
И активные сессии:
1 |
aws_applicationelb_active_connection_count_sum{load_balancer="app/$load_balancer"} |
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.
В самом простом виде запросы выглядел бы так:
1 |
sum by (instance)(rate(node_cpu_seconds_total{mode="system",env="$environment"}[5m])) * 100 |
Получаем % от времени, которое CPU проёвл в режиме system/user/idle и т.д.
Но в случае, когда у нас несколько ядер – добавляем вычисление % от кол-ва ядер, аналоигчно тому, как мы это делали для отрисовки шкалы с % LA и CPU Busy:
1 |
(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.
Выводим память всего:
1 |
node_memory_MemTotal_bytes{env="$environment"} |
Использованной памяти:
1 |
node_memory_MemTotal_bytes{env="$environment"} - node_memory_MemFree_bytes{env="$environment"} - (node_memory_Cached_bytes{env="$environment"} + node_memory_Buffers_bytes{env="$environment"}) |
Свободной памяти:
1 |
node_memory_MemFree_bytes{env="$environment"} |
Диск
Добавим отображение времени на read-write операции:
1 2 |
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
:
1 |
node_procs_running{env="$environment"} |
И blocked
:
1 |
node_procs_blocked{env="$environment"} |
И всё вместе теперь выглядит так: