Привет, Хабр! Меня зовут Сева, я работаю backend-разработчиком в Doubletapp, а также занимаюсь некоторыми devops-задачами. В этой статье я расскажу о мониторинге наших backend-приложений: сборе метрик, их визуализации и отправке уведомлений. Покажу примеры конфигов с подробными комментами и дам ссылки на гитхаб.

В Doubletapp мы занимаемся аутсорс-разработкой, к нам часто приходят новые проекты, а часть старых уходит в режим поддержки. Из-за этого у нас много сервисов, где активная разработка уже не ведется и которые развернуты на серверах клиентов. Мы не хотим узнавать о поломках от пользователей спустя несколько дней, поэтому нам нужна обзорная система, которая будет собирать метрики со всех проектов и отвечать на вопросы:

  • жив ли сервер/сервис; 

  • как меняется RPS;

  • какое время ответа;

  • хватает ли памяти и CPU;

  • сколько места осталось на диске.

Для этого мы выбрали популярное решение: собираем метрики с помощью Prometheus, отображаем их на дашбордах в Grafana и отправляем алерты в Телеграм через Prometheus Alert Manager.

Prometheus

Приложения обычно развернуты в облачных сервисах, у каждого клиента своя инфраструктура, поэтому мы не можем воспользоваться service discovery, который позволяет Prometheus автоматически обнаруживать новые объекты: Docker-контейнеры на хосте или поды Kubernetes-кластера.

Вместо этого мы используем статическую конфигурацию. Редактировать конфиг приходится вручную, зато можно настроить каждый источник: указать интервал сбора метрик, эндпоинт, данные для авторизации и прочее.

Конфигурация Prometheus

prometheus/prometheus.yml:

# секция global указывает параметры для всех конфигураций сбора метрик
global:
  # как часто запрашивать метрики по умолчанию
  scrape_interval: 10s
  # как часто пересчитывать правила для алертов (подробнее — в alerts.yml)
  evaluation_interval: 10s


# список конфигураций сбора метрик
scrape_configs:
  # название процесса, который собирает метрики
  - job_name: example-service
    static_configs:
      # список доменов/ip, с которых будут собираться метрики
      - targets:   
         - example.url.com
        # дополнительные данные, которые добавятся к записи
        labels:
          # название проекта
          instance_group: example_project
          # окружение приложения
          instance_env: test
          # тип источника метрик
          instance_type: service
          # команда, ответственная за проект
          team: backend
. . .

Чтобы добавить новый сервис, нужно изменить конфиг и перезапустить 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

nginx/default.conf:

# формат лога
log_format timed_combined
   '$remote_addr - $remote_user [$time_local] '
   '"$request" $status $body_bytes_sent '
   '"$http_referer" "$http_user_agent" '
   'rt=$request_time uct="$upstream_connect_time" uht="$upstream_header_time" urt="$upstream_response_time"';


server {
   # порт, который будет слушать Nginx
   listen 80;

   # переотправка запросов к API
   location / {
   	# файл логов
   	access_log /var/log/nginx/app.log timed_combined;
   	proxy_pass http://app:80/;
       . . .
   }

   # переотправка запросов на /metrics к экспортеру
   location /metrics {
 	# логирование запросов отключено, чтобы не портить метрики
   	access_log off;
 	 
   	proxy_pass http://nginx-exporter:4040/metrics;
       . . .
   }
}

Конфигурация Nginx-exporter

nginx-exporter/config.hcl:

listen {
  # порт, который будет слушать экспортер
  port = 4040
  # эндпоинт для получения метрик
  metrics_endpoint = "/metrics"
}

namespace "nginx" {
  # формат лога Nginx для парсинга
  format = "$remote_addr - $remote_user [$time_local] \"$request\" $status $body_bytes_sent \"$http_referer\" \"$http_user_agent\" rt=$request_time uct=\"$upstream_connect_time\" uht=\"$upstream_header_time\" urt=\"$upstream_response_time\""
  # границы бакетов для гистограмм
  histogram_buckets = [.005, .01, .025, .05, .1, .25, .5, 1, 2.5, 5, 10]
  # источник логов
  source {
    files = ["/var/log/nginx/app.log"]
  }
}

По ссылке — пример приложения с Nginx и экспортером. 

Метрики серверов

Также важно собирать метрики с серверов, на которых приложения запущены, —  следить за нагрузкой на ЦПУ, расходованием оперативной памяти и диска. Для сбора этих метрик есть официальный экспортер от Prometheus — prometheus/node_exporter. Он запускается как демон и периодически собирает метрики из системных файлов и интерфейсов.

Ссылка на проект с экспортером.

Дашборды

Дашборд в Grafana — это инструмент для визуализации метрик в реальном времени. Дашборд состоит из панелей, каждая из которых отображает определенный набор данных, например в виде графика или таблицы. Можно использовать готовые дашборды или собрать их с нуля в веб-интерфейсе. Для создания панели нужно написать запрос данных на PromQL и настроить их представление. 

В Grafana мы сделали 3 дашборда: главный, на котором собраны все приложения и серверы, дашборд конкретного приложения и дашборд конкретного сервера.

Главный дашборд

На главном находятся таблицы серверов и приложений с основными метриками: состояние (up или down), использование процессора, памяти и диска для сервера и RPS, статус, коды и время ответа для приложения. Он позволяет быстро оценить состояние всех наших проектов. Таблица состоит из отдельных панелей для серверов и приложений, они разделены на продовые и тестовые. Можно перейти на дашборд конкретного сервера/приложения, если кликнуть на его название или одну из метрик.

Таблица серверов

PromQL запросы, использующиеся в таблице серверов

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"})

Таблица приложений

PromQL запросы, использующиеся в таблице приложений

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, количество и коды ответов, время ответа.

PromQL запросы, использующиеся для построения панелей

Панель состояния

up{instance=~'$instance:.+'}

Панель RPS

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(...)

Панель запросов за 24 часа

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(...)

Панель запросов по статусам ответа за 24 часа

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])))

Панель графика 4хх запросов

floor(sum by (status) (increase(nginx_http_response_count_total{instance=~'$instance:.+',status=~'4..'}[1m])))

Панель графика 5хх запросов

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)(...)

Дашборд сервера

На странице сервера — графики нагрузки на ЦПУ, расходования памяти и диска сервера.

PromQL запросы, использующиеся для построения панелей

Обзорная панель

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='/'}

Панель настроена аналогично панели памяти.

Панель CPU

100 * (1 - avg by(instance)(irate(node_cpu_seconds_total{mode='idle',instance=~'$instance.*'}[1m])))

Запрос PromQL аналогичен запросу CPU Used в обзорной панели, только данные визуализируются как график.

Панель CPU Cores

100 * (1 - (irate(node_cpu_seconds_total{mode='idle',instance=~'$instance.*'}[1m])))

Запрос PromQL похож на запрос CPU Used в обзорной панели, но отсутствует агрегация по instance. Это дает данные о нагрузке на каждое ядро ЦПУ.

Панель CPU Cores Usage

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

prometheus/alerts.yml:

groups:
  - name: default
    rules:  	
        # название алерта
      - alert: NodeLowMemory
        # выражение PromQL для вычисления
    	  expr: node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes <= 0.05
        # алерт считается активным, если выражение expr верно в течение этого времени
    	  for: 5m
        # дополнительные данные для алерта
    	  labels:
          severity: critical
    	    annotations:
            # текстовое описание
            summary: "Node {{ $labels.instance }} is low on RAM for more than 5 minutes"

Настройка алертов и их отправки в основном конфиге Prometheus

prometheus/prometheus.yml:

. . .

# список файлов с описанием алертов
rule_files:
 - alerts.yml

# опции, связанные с Alertmanger. Протокол, префикс URL, адрес.
alerting:
  alertmanagers:
   - scheme: http
     path_prefix: /alertmanager
     static_configs:
       - targets: [ "alertmanager:9093" ]

Для оповещения мы используем Телеграм. Хотя у Alertmanager есть свой конфиг специально для Телеграма, для более гибкого форматирования сообщений у нас запущено приложение-вебхук. Alertmanager отправляет POST-запросы с данными об активных алертах в формате JSON по URL типа /alert/{id} (id — идентификатор телеграм-канала) в webhook-receiver, тот их форматирует и отправляет сообщения в Телеграм.  

Для каждого проекта мы создаем отдельные телеграм-каналы. Кроме того, есть канал об инфраструктурных серверах и приложениях. Ответственные за проект люди — разработчики, devops-инженеры и менеджеры — подписываются на соответствующий канал, чтобы получать уведомления о проблемах и оперативно на них реагировать. 

Конфигурация Alertmanager

alertmanager/alertmanager.yml:

# параметры, которые будут унаследованы всеми определенными в routes нодами маршрутизации
route:
  group_wait: 0s
  group_interval: 1s
  repeat_interval: 4h
  group_by: [...]
  
# ноды маршрутизации алертов  
routes:
     # параметры, по которым выбирается уведомление
   - match:
       instance_group: example_group 
     # название обработчика, куда будет отправлено уведомление
     receiver: example-receiver 
# список обработчиков уведомлений
receivers:
    # название обработчика
  - name: example-receiver
    webhook_configs:
        # уведомлять ли о разрешенных уведомлениях
      - send_resolved: True
        # куда будет отправлен POST-запрос с уведомлением
        url: http://webhook-receiver:3000/alert/<tg-channel-id>

В этой статье я рассказал о том, как мы в Doubletapp собираем метрики с помощью Prometheus, визуализируем их в Grafana и отправляем уведомления через Alertmanager в Телеграм.

Буду рад ответить на ваши вопросы и подискутировать в комментариях.

Ссылки на репозитории с проектами — Prometheus+Grafana, экспортер для серверов, шаблон приложения с Nginx-экспортером.

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