Привет, Хабр! Меня зовут Сева, я работаю backend-разработчиком в Doubletapp, а также занимаюсь некоторыми devops-задачами. В этой статье я расскажу о мониторинге наших backend-приложений: сборе метрик, их визуализации и отправке уведомлений. Покажу примеры конфигов с подробными комментами и дам ссылки на гитхаб. В Doubletapp мы занимаемся аутсорс-разработкой, к нам часто приходят новые проекты, а часть старых уходит в режим поддержки. Из-за этого у нас много сервисов, где активная разработка уже не ведется и которые развернуты на серверах клиентов. Мы не хотим узнавать о поломках от пользователей спустя несколько дней, поэтому нам нужна обзорная система, которая будет собирать метрики со всех проектов и отвечать на вопросы: жив ли сервер/сервис;  как меняется RPS; какое время ответа; хватает ли памяти и CPU; сколько места осталось на диске. Для этого мы выбрали популярное решение: собираем метрики с помощью Prometheus, отображаем их на дашбордах в Grafana и отправляем алерты в Телеграм через Prometheus Alert Manager. Приложения обычно развернуты в облачных сервисах, у каждого клиента своя инфраструктура, поэтому мы не можем воспользоваться service discovery, который позволяет Prometheus автоматически обнаруживать новые объекты: Docker-контейнеры на хосте или поды Kubernetes-кластера. Вместо этого мы используем статическую конфигурацию. Редактировать конфиг приходится вручную, зато можно настроить каждый источник: указать интервал сбора метрик, эндпоинт, данные для авторизации и прочее. prometheus/prometheus.yml:  Чтобы добавить новый сервис, нужно изменить конфиг и перезапустить Prometheus. Можно включить перезагрузку конфигурации в рантайме, но мы просто перезапускаем Prometheus во время выкатки нового конфига через CI/CD. В отличие от Graphite или InfluxDB, Prometheus сам ходит в сервисы и забирает из них телеметрию. Для того, чтобы это работало, сервис должен предоставить эндпоинт с информацией о своем состоянии в формате, понятном Prometheus, — это или их собственный формат, или OpenMetrics. Обычно для этого используется exporter — демон, который смотрит в логи системы, готовит телеметрию и предоставляет ее по HTTP/S. Для большинства существующих сервисов уже написаны готовые экспортеры. В простом случае можно реализовать /metrics в самом приложении, например, если мы хотим проверить только то, что приложение запущено и готово обрабатывать запросы. По умолчанию экспортеры работают по HTTP без авторизации, что позволяет любому желающему получить к ним доступ. Стоит озаботиться настройкой TLS и авторизации. Это можно сделать как с помощью настроек самого экспортера, так и прокси вроде Nginx. На всех наших проектах мы используем Nginx для проксирования запросов к API, поэтому из его логов мы можем строить основные метрики. Есть официальный экспортер от самой Nginx Inc. Для стандартной версии Nginx можно получить информацию об активных соединениях и количестве обработанных запросов, но нам нужно больше информации, как минимум HTTP-коды ответов и время обработки запросов. Официальный экспортер может собирать эти данные из API Nginx, но она доступна только в версии Nginx Plus. Для бесплатного Nginx также существует альтернативный экспортер — martin-helmich/prometheus-nginxlog-exporter. Чтобы его настроить, нужно указать путь и формат лога, который он будет парсить. Также нужно указать границы бакетов для гистограмм, чтобы можно было посмотреть, сколько времени заняла обработка определенной доли запросов. В дефолтном формате лога Nginx нет информации о времени ответа, поэтому его тоже поменяем. nginx/default.conf:  nginx-exporter/config.hcl:  По ссылке — пример приложения с Nginx и экспортером.  Также важно собирать метрики с серверов, на которых приложения запущены, —  следить за нагрузкой на ЦПУ, расходованием оперативной памяти и диска. Для сбора этих метрик есть официальный экспортер от Prometheus — prometheus/node_exporter. Он запускается как демон и периодически собирает метрики из системных файлов и интерфейсов. Ссылка на проект с экспортером. Дашборд в Grafana — это инструмент для визуализации метрик в реальном времени. Дашборд состоит из панелей, каждая из которых отображает определенный набор данных, например в виде графика или таблицы. Можно использовать готовые дашборды или собрать их с нуля в веб-интерфейсе. Для создания панели нужно написать запрос данных на PromQL и настроить их представление.  В Grafana мы сделали 3 дашборда: главный, на котором собраны все приложения и серверы, дашборд конкретного приложения и дашборд конкретного сервера. На главном находятся таблицы серверов и приложений с основными метриками: состояние (up или down), использование процессора, памяти и диска для сервера и RPS, статус, коды и время ответа для приложения. Он позволяет быстро оценить состояние всех наших проектов. Таблица состоит из отдельных панелей для серверов и приложений, они разделены на продовые и тестовые. Можно перейти на дашборд конкретного сервера/приложения, если кликнуть на его название или одну из метрик. State up{instance_type="node", instance_env="prod"} CPU Used 100 * (1 - avg by(instance)(irate(node_cpu_seconds_total{mode='idle', instance_env="prod"}[1m]))) Memory Available avg by(instance)(node_memory_MemAvailable_bytes{instance_env="prod"}) Memory Total avg by(instance)(node_memory_MemTotal_bytes{instance_env="prod"}) Disk Available avg by(instance)(node_filesystem_avail_bytes{mountpoint='/', instance_env="prod"}) Disk Total avg by(instance)(node_filesystem_size_bytes{mountpoint='/', instance_env="prod"}) State up{instance_type="service"} RPS sum by(instance)(rate(nginx_http_response_count_total{status=~'...'}[24h])) RP24H floor(sum by(instance)(increase(nginx_http_response_count_total{status=~'...'}[24h]))) 2хх floor(sum by(instance)(increase(nginx_http_response_count_total{status=~'2..'}[24h]))) 95th histogram_quantile(0.95, sum by (instance,le) (rate(nginx_http_upstream_time_seconds_hist_bucket{status='200'}[10m]))) * 1000 На странице приложений — состояние, RPS, количество и коды ответов, время ответа. up{instance=~'$instance:.+'} sum(rate(nginx_http_response_count_total{instance=~'$instance:.+'}[1m])) Метрика nginx_http_response_count_total{instance=~'$instance:.+',status='200'} содержит информацию о количестве обработанных HTTP-запросов Запрашиваются средняя скорость роста на отрезках по 1 минуте для этой метрики (rate(...)[1m]) Считается сумма по всем HTTP-методам и статусам ответов sum(...) floor(sum(increase(nginx_http_response_count_total{instance=~'$instance:.+'}[24h]))) Метрика nginx_http_response_count_total{instance=~'$instance:.+',status='200'}содержит информацию о количестве обработанных HTTP-запросов Для количества обработанных HTTP-запросов считается их количественное увеличение за 24 часа (increase(...)[24h]) Считается сумма по всем HTTP-методам и статусам ответов (sum(...)) и округляется до целого floor(...) floor(sum(increase(nginx_http_response_count_total{instance=~'$instance:.+',status=~'2..'}[24h]))) Запрос аналогичен предыдущему, но данные запрашиваются по каждому типу ответов. histogram_quantile(0.95, sum by (le) (rate(nginx_http_upstream_time_seconds_hist_bucket{instance=~'$instance:.+',status='200'}[10m]))) * 1000 sum(rate(nginx_http_response_count_total{instance=~'$instance:.+',status=~'...'}[1m])) floor(sum(increase(nginx_http_response_count_total{instance=~'$instance:.+',status=~'2..'}[1m]))) floor(sum by (status) (increase(nginx_http_response_count_total{instance=~'$instance:.+',status=~'4..'}[1m]))) floor(sum by (status) (increase(nginx_http_response_count_total{instance=~'$instance:.+',status=~'5..'}[1m]))) histogram_quantile(0.99, sum by (le) (rate(nginx_http_upstream_time_seconds_hist_bucket{instance=~'$instance:.+',status='200'}[10m]))) * 1000 Метрика nginx_http_upstream_time_seconds_hist_bucket{instance=~'$instance:.+',status='200'} содержит информацию о распределении времени, необходимом приложению для обработки запросов с успешными ответами, по бакетам гистограммы. Запрашиваются средняя скорость роста на отрезках по 10 минут для этой метрики (rate(...)[10m]) Строится несколько графиков на разных перцентилях (0.5, 0.75, 0.9, 0.95, 0.99) histogram_quantile(n, sum by (le)(...)  На странице сервера — графики нагрузки на ЦПУ, расходования памяти и диска сервера. CPU Used 100 * (1 - avg by(instance)(irate(node_cpu_seconds_total{mode='idle',instance=~'$instance.*'}[1m]))) Метрика node_cpu_seconds_total{mode='idle',instance=~'$instance.'} содержит информацию о распределении времени, которое процессор проводит состоянии бездействия Вычисляется посекундная мгновенная скорость увеличения временного ряда на отрезках по 10 минут для этой метрики (irate(...)[10m]) Из этого значения вычисляется процентное соотношение времени, которое процессор проводит в любом состоянии, кроме бездействия (100(1 - …)) Memory Available (node_memory_MemAvailable_bytes{instance=~'$instance.*'}) / (node_memory_MemTotal_bytes{instance=~'$instance.*'}) * 100 Процентное соотношение свободной памяти к общей. Disk Available (node_filesystem_avail_bytes{instance=~'$instance.*', mountpoint='/'}) / (node_filesystem_size_bytes{instance=~'$instance.*', mountpoint='/'}) * 100 Процентное соотношение свободного дискового пространства к общему. График свободной памяти node_memory_MemAvailable_bytes{instance=~'$instance.*'} График использованной памяти node_memory_MemTotal_bytes{instance=~'$instance.*'} - node_memory_MemAvailable_bytes{instance=~'$instance.*'} Панель настроена так, что графики отображены друг над другом. График свободного дискового пространства node_filesystem_avail_bytes{instance=~'$instance.*', mountpoint='/'} График использованного дискового пространства node_filesystem_size_bytes{instance=~'$instance.*', mountpoint='/'} - node_filesystem_avail_bytes{instance=~'$instance.*', mountpoint='/'} Панель настроена аналогично панели памяти. 100 * (1 - avg by(instance)(irate(node_cpu_seconds_total{mode='idle',instance=~'$instance.*'}[1m]))) Запрос PromQL аналогичен запросу CPU Used в обзорной панели, только данные визуализируются как график. 100 * (1 - (irate(node_cpu_seconds_total{mode='idle',instance=~'$instance.*'}[1m]))) Запрос PromQL похож на запрос CPU Used в обзорной панели, но отсутствует агрегация по instance. Это дает данные о нагрузке на каждое ядро ЦПУ. 100 * (1 - (irate(node_cpu_seconds_total{mode='idle',instance=~'$instance.*'}[1m]))) Запрос PromQL аналогичен предыдущему, только данные визуализируются как график. Данные, которые мы собираем со всех серверов и приложений, одинаковые, поэтому дашборды для их отображения мы используем одни и те же, только меняем переменную Instance, значения  которой подтягиваются из Prometheus. В качестве локальной базы данных для хранения конфигурации (дашборды, пользователи и т. д.) в Grafana поднят sqlite3. Но дашборды удобно хранить в формате JSON в репозитории проекта для развертывания Grafana на новом сервере. Экспортировать их можно на странице самого дашборда, а импортировать — в меню Grafana. Ссылка на проект с Prometheus+Grafana — тут. Без оповещений о критических ситуациях можно узнать, только если весь день смотреть в монитор, а это крайне неэффективный способ обнаружения проблем и довольно скучное времяпрепровождение. Узнать о проблемах в момент их возникновения поможет механизм алертов. Правила уведомлений настраиваются в Prometheus. Правило — это PromQL выражение, которое должно стать истинным, чтобы активировать алерт. Prometheus вычисляет эти выражения в цикле с настроенной периодичностью. У нас уведомления настроены на основные критические ситуации: Приложение/сервер не работает. В течение 5 минут метрика up находится в значении 0. На сервере мало свободной памяти/диска. Отношение значений занятого объема к общему больше 95 процентов. На сервере высокое потребление ЦПУ. Время, которое процессор проводит в любом состоянии, кроме бездействия, составляет больше 80 процентов. Приложение недоступно. Число ответов 502 Bad Gateway прокси-сервера возрастает в течение 3 минут. Приложение возвращает 5хх-е коды ответов. За последнюю минуту вернулся 5хх-й ответ. Активные алерты (то есть те, для которых условие стало истинным) шлются в Alertmanager, где они перенаправляются в каналы связи.  Он отправляет уведомления в различные каналы связи, такие как электронная почта или мессенджеры, есть много интеграций, которые поддерживаются из коробки. Также можно сделать переотправку на вебхуки. Они могут помочь в интегрировании каналов связи, для которых нет готовых интеграций. Например, если вы хотите узнавать о высокой нагрузке на ваш сервер в своем паблике во «ВКонтакте». prometheus/alerts.yml:  prometheus/prometheus.yml:  Для оповещения мы используем Телеграм. Хотя у Alertmanager есть свой конфиг специально для Телеграма, для более гибкого форматирования сообщений у нас запущено приложение-вебхук. Alertmanager отправляет POST-запросы с данными об активных алертах в формате JSON по URL типа /alert/{id} (id — идентификатор телеграм-канала) в webhook-receiver, тот их форматирует и отправляет сообщения в Телеграм.   Для каждого проекта мы создаем отдельные телеграм-каналы. Кроме того, есть канал об инфраструктурных серверах и приложениях. Ответственные за проект люди — разработчики, devops-инженеры и менеджеры — подписываются на соответствующий канал, чтобы получать уведомления о проблемах и оперативно на них реагировать.  alertmanager/alertmanager.yml:  В этой статье я рассказал о том, как мы в Doubletapp собираем метрики с помощью Prometheus, визуализируем их в Grafana и отправляем уведомления через Alertmanager в Телеграм. Буду рад ответить на ваши вопросы и подискутировать в комментариях. Ссылки на репозитории с проектами — Prometheus+Grafana, экспортер для серверов, шаблон приложения с Nginx-экспортером.