- PVSM.RU - https://www.pvsm.ru -
В статье показано как настроить мониторинг веб-приложения на Rust. Приложение выставляет наружу Prometheus метрики, которые визуализируются с помощью Grafana. Мониторинг осуществляется для проекта mongodb-redis demo [1], детально рассмотренного здесь [2]. В итоге получена следующая архитектура:
Система мониторинга включает:
Для запуска всех этих инструментов вы можете воспользоваться следующим:
version: '3.8'
services:
prometheus:
image: prom/prometheus:latest
container_name: prometheus
restart: always
ports:
- '9090:9090'
volumes:
- ./monitoring/prometheus:/etc/prometheus
command:
- '--config.file=/etc/prometheus/prometheus.yml'
- '--web.external-url=http://localhost:9090'
grafana:
image: grafana/grafana:latest
container_name: grafana
restart: always
ports:
- '3000:3000'
volumes:
- ./monitoring/grafana/data:/var/lib/grafana
- ./monitoring/grafana/provisioning:/etc/grafana/provisioning
environment:
GF_SECURITY_ADMIN_USER: admin
GF_SECURITY_ADMIN_PASSWORD: admin
alertmanager:
image: prom/alertmanager:latest
container_name: alertmanager
ports:
- '9093:9093'
volumes:
- ./monitoring/alertmanager:/etc/alertmanager
command:
- '--config.file=/etc/alertmanager/alertmanager.yml'
- '--web.external-url=http://localhost:9093'
cadvisor:
image: gcr.io/cadvisor/cadvisor:latest
container_name: cadvisor
restart: always
ports:
- '8080:8080'
volumes:
- /:/rootfs:ro
- /var/run:/var/run:rw
- /sys:/sys:ro
- /var/lib/docker/:/var/lib/docker:ro
Отдача метрик имплементируется с помощью крейта [9] prometheus
.
Существует четыре типа [10] Prometheus метрик: счётчик (counter), датчик/измеритель/шкала (gauge), гистограмма (histogram), сводка (summary). Использование первых трёх из них будет описано в статье (в настоящее время крейт не поддерживает [11] сводки).
Метрики могут быть созданы и зарегистрированы следующим образом:
Создание и регистрация метрик [12]
lazy_static! {
pub static ref HTTP_REQUESTS_TOTAL: IntCounterVec = register_int_counter_vec!(
opts!("http_requests_total", "HTTP requests total"),
&["method", "path"]
)
.expect("Can't create a metric");
pub static ref HTTP_CONNECTED_SSE_CLIENTS: IntGauge =
register_int_gauge!(opts!("http_connected_sse_clients", "Connected SSE clients"))
.expect("Can't create a metric");
pub static ref HTTP_RESPONSE_TIME_SECONDS: HistogramVec = register_histogram_vec!(
"http_response_time_seconds",
"HTTP response times",
&["method", "path"],
HTTP_RESPONSE_TIME_CUSTOM_BUCKETS.to_vec()
)
.expect("Can't create a metric");
}
В примере кода выше метрики добавляются в реестр по умолчанию. Также возможно зарегистрировать их в кастомном реестре (пример [13]).
Если требуется посчитать все входящие HTTP запросы, то возможно использовать тип IntCounter [14]. Но более полезно видеть не только общее число запросов, но также некоторые дополнительные измерения, такие как path и HTTP метод. Это можно сделать с помощью IntCounterVec [15]; метрика HTTP_REQUESTS_TOTAL
этого типа используется в кастомном Actix middleware таким образом:
Использование [16] метрики HTTP_REQUESTS_TOTAL
let request_path = req.path();
let is_registered_resource = req.resource_map().has_resource(request_path);
// this check prevents possible DoS attacks that can be done by flooding the application
// using requests to different unregistered paths. That can cause high memory consumption
// of the application and Prometheus server and also overflow Prometheus's TSDB
if is_registered_resource {
let request_method = req.method().to_string();
metrics::HTTP_REQUESTS_TOTAL
.with_label_values(&[&request_method, request_path])
.inc();
}
После того, как вы сделаете несколько запросов к API, появится что-то похожее на:
Выходные данные метрики HTTP_REQUESTS_TOTAL
# HELP http_requests_total HTTP requests total
# TYPE http_requests_total counter
http_requests_total{method="GET",path="/"} 1
http_requests_total{method="GET",path="/events"} 1
http_requests_total{method="GET",path="/metrics"} 22
http_requests_total{method="GET",path="/planets"} 20634
Каждая выборка метрики содержит лейблы (атрибуты метрики) method
и path
, что позволяет Prometheus серверу различать выборки.
Как показано в фрагменте выше, запросы к GET /metrics
(эндпоинт, с которого Prometheus сервер собирает метрики приложения) также учитываются.
Датчик отличается от счётчика тем, что его значение может уменьшаться. Пример датчика показывает сколько клиентов в настоящий момент подключены с помощью SSE. Используется следующим образом:
Использование [17] метрики HTTP_CONNECTED_SSE_CLIENTS
crate::metrics::HTTP_CONNECTED_SSE_CLIENTS.inc();
crate::metrics::HTTP_CONNECTED_SSE_CLIENTS.set(broadcaster_mutex.clients.len() as i64)
При переходе на http://localhost:9000
в браузере будет установлено SSE соединение, что инкрементирует значение метрики. После этого выходные данные станут такими:
Выходные данные метрики HTTP_CONNECTED_SSE_CLIENTS
# HELP http_connected_sse_clients Connected SSE clients
# TYPE http_connected_sse_clients gauge
http_connected_sse_clients 1
Для имплементации датчика числа SSE клиентов потребовался рефакторинг кода приложения и реализация [17] broadcaster'а. Он сохраняет всех подключённых (с помощью функции [18] sse
) клиентов в вектор и периодически их пингует (с помощью функции [17] remove_stale_clients
), чтобы убедиться, что соединение по-прежнему активно, в противном случае удаляет отсоединённых клиентов из вектора. Broadcaster позволяет открывать всего лишь одно Redis Pub/Sub соединение [19]; сообщения из него отправляются (broadcasted) всем клиентам.
В этом гайде гистограмма [20] используется для сбора данных о времени ответа. Как и в случае со счётчиком запросов, трекинг осуществляется в Actix middleware; это реализует следущий код:
...
histogram_timer = Some(
metrics::HTTP_RESPONSE_TIME_SECONDS
.with_label_values(&[&request_method, request_path])
.start_timer(),
);
...
if let Some(histogram_timer) = histogram_timer {
histogram_timer.observe_duration();
};
Полагаю, этот способ не очень точный (вопрос в том, насколько измеренное время ответа меньше реального), но тем не менее данные наблюдений будут полезны в качестве примера гистограммы и для её дальнейшей визуализации в Grafana.
Гистограмма принимает результаты наблюдений и подсчитывает их количество в конфигурируемых bucket'ах (есть bucket'ы по умолчанию, но скорее всего вам потребуется определить кастомные bucket'ы, подходящие к вашему use case); чтобы их сконфигурировать, было бы неплохо знать примерное рспределение значений определённой метрики. В этом приложении время ответа невелико, поэтому используется следующая конфигурация:
Bucket'ы для метрики времени ответа [12]
const HTTP_RESPONSE_TIME_CUSTOM_BUCKETS: &[f64; 14] = &[
0.0005, 0.0008, 0.00085, 0.0009, 0.00095, 0.001, 0.00105, 0.0011, 0.00115, 0.0012, 0.0015,
0.002, 0.003, 1.0,
];
Выходные данные будут выглядеть примерно так (показана только часть данных):
Выходные данные метрики HTTP_RESPONSE_TIME_SECONDS
# HELP http_response_time_seconds HTTP response times
# TYPE http_response_time_seconds histogram
http_response_time_seconds_bucket{method="GET",path="/planets",le="0.0005"} 0
http_response_time_seconds_bucket{method="GET",path="/planets",le="0.0008"} 6
http_response_time_seconds_bucket{method="GET",path="/planets",le="0.00085"} 1307
http_response_time_seconds_bucket{method="GET",path="/planets",le="0.0009"} 10848
http_response_time_seconds_bucket{method="GET",path="/planets",le="0.00095"} 22334
http_response_time_seconds_bucket{method="GET",path="/planets",le="0.001"} 31698
http_response_time_seconds_bucket{method="GET",path="/planets",le="0.00105"} 38973
http_response_time_seconds_bucket{method="GET",path="/planets",le="0.0011"} 44619
http_response_time_seconds_bucket{method="GET",path="/planets",le="0.00115"} 48707
http_response_time_seconds_bucket{method="GET",path="/planets",le="0.0012"} 51495
http_response_time_seconds_bucket{method="GET",path="/planets",le="0.0015"} 57066
http_response_time_seconds_bucket{method="GET",path="/planets",le="0.002"} 59542
http_response_time_seconds_bucket{method="GET",path="/planets",le="0.003"} 60532
http_response_time_seconds_bucket{method="GET",path="/planets",le="1"} 60901
http_response_time_seconds_bucket{method="GET",path="/planets",le="+Inf"} 60901
http_response_time_seconds_sum{method="GET",path="/planets"} 66.43133770000004
http_response_time_seconds_count{method="GET",path="/planets"} 60901
Данные показывают число наблюдений, попавших в определённые bucket'ы. Также предоставляются данные по общему числу и сумме наблюдений.
process
фича позволяет экспортировать метрики процесса [21], такие как использование процессора или памяти. Для этого нужно указать фичу в Cargo.toml
. После этого вы получите что-то вроде:
Выходные данные метрик процесса
# HELP process_cpu_seconds_total Total user and system CPU time spent in seconds.
# TYPE process_cpu_seconds_total counter
process_cpu_seconds_total 134.49
# HELP process_max_fds Maximum number of open file descriptors.
# TYPE process_max_fds gauge
process_max_fds 1048576
# HELP process_open_fds Number of open file descriptors.
# TYPE process_open_fds gauge
process_open_fds 37
# HELP process_resident_memory_bytes Resident memory size in bytes.
# TYPE process_resident_memory_bytes gauge
process_resident_memory_bytes 15601664
# HELP process_start_time_seconds Start time of the process since unix epoch in seconds.
# TYPE process_start_time_seconds gauge
process_start_time_seconds 1636309802.38
# HELP process_threads Number of OS threads in the process.
# TYPE process_threads gauge
process_threads 6
# HELP process_virtual_memory_bytes Virtual memory size in bytes.
# TYPE process_virtual_memory_bytes gauge
process_virtual_memory_bytes 439435264
Обратите внимание, что крейт prometheus
поддерживает экспорт метрик процесса в приложениях, запущенных на Linux (например, в таком [22] Docker контейнере).
Actix сконфигурирован так, чтобы обрабатывать запрос к GET /metrics
с помощью следующего хэндлера:
Хэндлер для метрик [18]
pub async fn metrics() -> Result<HttpResponse, CustomError> {
let encoder = TextEncoder::new();
let mut buffer = vec![];
encoder
.encode(&prometheus::gather(), &mut buffer)
.expect("Failed to encode metrics");
let response = String::from_utf8(buffer.clone()).expect("Failed to convert bytes to string");
buffer.clear();
Ok(HttpResponse::Ok()
.insert_header(header::ContentType(mime::TEXT_PLAIN))
.body(response))
}
Теперь, после успешного конфигуриования приложения, вы можете получить все ранее описанные метрики выполнив запрос GET http://localhost:9000/metrics
. Этот эндпоинт используется Prometheus сервером для получения метрик приложения.
Метрики отдаются в простом текстовом формате [23].
Prometheus собирает метрики используя следующий конфиг:
Конфиг [24] Prometheus для сбора метрик
scrape_configs:
- job_name: mongodb_redis_web_app
scrape_interval: 5s
static_configs:
- targets: ['host.docker.internal:9000']
- job_name: cadvisor
scrape_interval: 5s
static_configs:
- targets: ['cadvisor:8080']
В конфиге определены две job'ы. Первая собирает ранее описанные метрики приложения, вторая — метрики использования ресурсов и производительности запущенных контейнеров (это будет подробно рассмотрено в разделе, описывающем использование cAdvisor). scrape_interval
указывает как часто забирать данные с target. Параметр metrics_path
не указан, поэтому Prometheus ожидает, что метрики будут доступны на target'ах по пути /metrics
.
Для использования встроенного Prometheus expression browser перейдите на http://localhost:9090/graph
и попробуйте запросить любую из ранее описанных метрик, например, http_requests_total
. Используйте вкладку "Graph" для визуализации данных.
PromQL [25] позволяет делать более сложные запросы; рассмотрим пару примеров.
вернуть все временные ряды для метрики http_requests_total
и заданной job'ы:
http_requests_total{job="mongodb_redis_web_app"}
Лейблы job
и instance
автоматически добавляются [26] к собираемым Prometheus сервером временным рядам.
вернуть с помощью функции rate [27] число запросов в секунду на основании измерений в последние 5 минут:
rate(http_requests_total[5m])
Вы можете найти больше примеров здесь [28].
В этом проекте Grafana сконфигурирована с помощью следующих параметров:
источники данных (откуда Grafana будет запрашивать данные)
Конфиг источников данных [29] для Grafana
apiVersion: 1
datasources:
- name: Prometheus
type: prometheus
access: proxy
url: prometheus:9090
isDefault: true
провайдер дашбордов (откуда Grafana загрузит дашборды)
Конфиг дашбордов [30] для Grafana
apiVersion: 1
providers:
- name: 'default'
folder: 'default'
type: file
allowUiUpdates: true
updateIntervalSeconds: 30
options:
path: /etc/grafana/provisioning/dashboards
foldersFromFilesStructure: true
После запуска проекта с помощью файла Docker Compose [8] вы можете перейти на http://localhost:3000/
, войти с помощью admin/admin
и найти дашборд webapp_metrics
. Некоторое время спустя он будет выглядеть примерно так:
Дашборд показывает состояние приложения под простым нагрузочным тестом. (Если вы запустите какой-нибудь нагрузочный тест, то для большей наглядности графиков (особенно гистограммы) вам нужно будет отключить ограничение [31] MAX_REQUESTS_PER_MINUTE
, например, путём резкого увеличения этого числа.)
Для визуализации данных в дашборде [32] используются PromQL запросы, включающие ранее показанные метрики, например:
rate(http_response_time_seconds_sum[5m]) / rate(http_response_time_seconds_count[5m])
Показать среднее время ответа за последние 5 минут
sum(increase(http_response_time_seconds_bucket{path="/planets"}[30s])) by (le)
Используется для визуализации распределения времени ответа в форме тепловой карты [33]. Тепловая карта похожа на гистограмму, но во времени; каждый интервал времени представляет собой отдельную гистограмму:
rate(process_cpu_seconds_total{job="mongodb_redis_web_app"}[1m])
, sum(rate(container_cpu_usage_seconds_total{name='mongodb-redis'}[1m])) by (name)
Показывает использование процессора за последние 5 минут. Запрашиваемые данные приходят из двух источников и показывают использование ресурса процессом и контейнером соответственно. Два графика почти одинаковы. (sum
используется поскольку container_cpu_usage_seconds_total
предоставляет информацию по использованию каждого ядра.)
Примечание: График "Memory usage" показывает память, используемую:
process_resident_memory_bytes{job="mongodb_redis_web_app"} / 1024 / 1024
)container_memory_usage_bytes{name="mongodb-redis"} / 1024 / 1024
)По неизвестной мне причине график показывает, что процесс потребляет намного больше памяти, чем весь контейнер. Я создал issue [34] по данному вопросу. Напишите, если что-то не так в этом сравнении или знаете, как это объясняется.
В дополнение к системным метрикам процесса (было показано ранее) также могут быть экспортированы системные метрики Docker контейнера приложения. Это можно сделать с помощью cAdvisor.
Веб-интерфейс cAdvisor доступен по http://localhost:8080/
. Все запущенные Docker контейнеры показаны на http://localhost:8080/docker/
:
Вы можете получить информацию по использованию ресурсов любым контейнером:
Метрики собираются Prometheus сервером с http://localhost:8080/metrics
.
Метрики, экспортируемые cAdvisor'ом, перечислены здесь [35].
Системные метрики серверов могут быть экспортированы с помощью Node exporter [36] или Windows exporter [37].
В этом проекте следующая часть конфига Prometheus отвечает за уведомления:
Конфиг [24] Prometheus для уведомлений
rule_files:
- 'rules.yml'
alerting:
alertmanagers:
- static_configs:
- targets: ['alertmanager:9093']
В разделе alerting
определён инстанс AlertManager, с которым взаимодействет Prometheus сервер.
Правила оповещений [38] позволяют определить условия на основе PromQL выражений:
Пример [39] правила оповещения в rules.yml
groups:
- name: default
rules:
- alert: SseClients
expr: http_connected_sse_clients > 0
for: 1m
labels:
severity: high
annotations:
summary: Too many SSE clients
Указанное правило, что число SSE клиентов больше 0, — это не то, что вы обычно настраиваете в приложении. Оно используется в качестве примера только потому, что позволяет легко его нарушить с помощью всего лишь одного запроса.
Если правило нарушено, Prometheus сервер отправит оповещение на инстанс AlertManager, который предоставляет множество фич, таких как дедупликация оповещений, группировка, отключение и роутинг уведомлений конечного пользователя. Здесь мы рассмотрим только роутинг: уведомление будлет перенаправлено на email.
AlertManager сконфигурирован так:
Конфиг AlertManager [40]
route:
receiver: gmail
receivers:
- name: gmail
email_configs:
- to: recipient@gmail.com
from: email_id@gmail.com
smarthost: smtp.gmail.com:587
auth_username: email_id@gmail.com
auth_identity: email_id@gmail.com
auth_password: password
В этом проекте AlertManager сконфигурирован с помощью Gmail аккаунта. Чтобы сгенерировать app password, вы можете использовать этот гайд [41].
Чтобы правило оповещения SseClients
сработало, вам нужно перейти на http://localhost:9000
в браузере. Это увеличит значение метрики http_connected_sse_clients
на 1. Вы можете видеть изменения статуса оповещения SseClients
на http://localhost:9090/alerts
. После срабатывания оповещение перейдёт в статус Pending
. По прошествии интервала for
, определённого в rules.yml
(в нашем случае это 1 минута), оповещение перейдёт в статус Firing
.
Это приведёт к тому, что Prometheus сервер отправит оповещение в AlertManager, который определит что с ним делать. В нашем случае будет отправлен email:
Для third-party тулов, таких как MongoDB, Redis и многих других, есть возможность настроить мониторинг с помощью Prometheus exporters [42].
docker compose up --build
В этой статье было показано как настроить отдачу метрик веб-приложения на Rust, их сбор Prometheus'ом и визуализацию данных с помощью Grafana. Также было показано как начать работу с cAdvisor для сбора метрик контейнеров и с нотификациями с помощью AlertManager. Не стесняйтесь написать мне, если нашли какие-либо ошибки в статье или исходном коде. Спасибо за внимание!
Автор: Roman Kudryashov
Источник [47]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/vizualizatsiya-danny-h/371243
Ссылки в тексте:
[1] mongodb-redis demo: https://github.com/rkudryashov/exploring-rust-ecosystem/tree/master/mongodb-redis
[2] здесь: https://romankudryashov.com/blog/2021/06/mongodb-redis-rust/
[3] Prometheus: https://prometheus.io/
[4] time series database: https://en.wikipedia.org/wiki/Time_series_database
[5] Grafana: https://grafana.com/
[6] AlertManager: https://prometheus.io/docs/alerting/latest/alertmanager/
[7] cAdvisor: https://github.com/google/cadvisor
[8] Docker Compose файл: https://github.com/rkudryashov/exploring-rust-ecosystem/blob/master/mongodb-redis/docker-compose.override.yml
[9] крейта: https://github.com/tikv/rust-prometheus
[10] четыре типа: https://prometheus.io/docs/concepts/metric_types/
[11] не поддерживает: https://github.com/tikv/rust-prometheus/issues/5
[12] Создание и регистрация метрик: https://github.com/rkudryashov/exploring-rust-ecosystem/blob/master/mongodb-redis/src/metrics.rs
[13] пример: https://github.com/tikv/rust-prometheus/blob/master/examples/example_custom_registry.rs
[14] IntCounter: https://docs.rs/prometheus/latest/prometheus/type.IntCounter.html
[15] IntCounterVec: https://docs.rs/prometheus/latest/prometheus/type.IntCounterVec.html
[16] Использование: https://github.com/rkudryashov/exploring-rust-ecosystem/blob/master/mongodb-redis/src/main.rs
[17] Использование: https://github.com/rkudryashov/exploring-rust-ecosystem/blob/master/mongodb-redis/src/broadcaster.rs
[18] функции: https://github.com/rkudryashov/exploring-rust-ecosystem/blob/master/mongodb-redis/src/handlers.rs
[19] соединение: https://github.com/rkudryashov/exploring-rust-ecosystem/blob/master/mongodb-redis/src/redis.rs
[20] гистограмма: https://prometheus.io/docs/practices/histograms/
[21] метрики процесса: https://prometheus.io/docs/instrumenting/writing_clientlibs/#process-metrics
[22] таком: https://github.com/rkudryashov/exploring-rust-ecosystem/blob/master/mongodb-redis/Dockerfile
[23] формате: https://github.com/prometheus/docs/blob/main/content/docs/instrumenting/exposition_formats.md
[24] Конфиг: https://github.com/rkudryashov/exploring-rust-ecosystem/blob/master/mongodb-redis/monitoring/prometheus/prometheus.yml
[25] PromQL: https://prometheus.io/docs/prometheus/latest/querying/basics/
[26] автоматически добавляются: https://prometheus.io/docs/concepts/jobs_instances/
[27] rate: https://prometheus.io/docs/prometheus/latest/querying/functions/#rate
[28] здесь: https://prometheus.io/docs/prometheus/latest/querying/examples/
[29] Конфиг источников данных: https://github.com/rkudryashov/exploring-rust-ecosystem/blob/master/mongodb-redis/monitoring/grafana/provisioning/datasources/datasources.yml
[30] Конфиг дашбордов: https://github.com/rkudryashov/exploring-rust-ecosystem/blob/master/mongodb-redis/monitoring/grafana/provisioning/dashboards/providers.yml
[31] ограничение: https://github.com/rkudryashov/exploring-rust-ecosystem/blob/master/mongodb-redis/src/services.rs
[32] дашборде: https://github.com/rkudryashov/exploring-rust-ecosystem/blob/master/mongodb-redis/monitoring/grafana/provisioning/dashboards/webapp_metrics.json
[33] тепловой карты: https://grafana.com/docs/grafana/latest/basics/intro-histograms/
[34] issue: https://github.com/tikv/rust-prometheus/issues/424
[35] здесь: https://github.com/google/cadvisor/blob/master/docs/storage/prometheus.md
[36] Node exporter: https://github.com/prometheus/node_exporter
[37] Windows exporter: https://github.com/prometheus-community/windows_exporter
[38] Правила оповещений: https://prometheus.io/docs/prometheus/latest/configuration/alerting_rules/
[39] Пример: https://github.com/rkudryashov/exploring-rust-ecosystem/blob/master/mongodb-redis/monitoring/prometheus/rules.yml
[40] Конфиг AlertManager: https://github.com/rkudryashov/exploring-rust-ecosystem/blob/master/mongodb-redis/monitoring/alertmanager/alertmanager.yml
[41] этот гайд: https://support.google.com/accounts/answer/185833?hl=en
[42] exporters: https://prometheus.io/docs/instrumenting/exporters/
[43] Introduction to Prometheus: https://prometheus.io/docs/introduction/overview/
[44] Prometheus’s naming convention: https://prometheus.io/docs/practices/naming/
[45] How to visualize Prometheus histograms in Grafana: https://grafana.com/blog/2020/06/23/how-to-visualize-prometheus-histograms-in-grafana/
[46] Setup of an alerting system for Spring Boot application: https://tomgregory.com/monitoring-a-spring-boot-application-part-3-rules-and-alerting/
[47] Источник: https://habr.com/ru/post/645231/?utm_source=habrahabr&utm_medium=rss&utm_campaign=645231
Нажмите здесь для печати.