Метрики /metrics для центрального Prometheus + генерация scrape-таргетов
--metrics-port (0.0.0.0, в отличие от --health-port — тот 127.0.0.1-only) отдаёт netrunner_connections_active/total, netrunner_auth_validate_duration_seconds, netrunner_auth_failures_total, netrunner_circuit_breaker_open. Тот же hand-rolled HTTP-паттерн, что и health.rs — без HTTP-фреймворка ради одного эндпоинта. scripts/generate-prometheus-targets.mjs генерит список scrape-таргетов из nodes.json — job update-observability-targets в deploy.yml гоняет его при каждом деплое нод и заливает на VPS с данными (netrunner-data/observability/targets/nodes.json), чтобы центральный Prometheus не терял из виду ноды без ручной правки конфига. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,60 @@
|
||||
//! `/metrics` для центрального Prometheus (см.
|
||||
//! `netrunner-data/docker-compose.observability.yml`) — в отличие от
|
||||
//! `/health` (127.0.0.1-only, см. `health.rs`), этот порт публикуется наружу
|
||||
//! и ОБЯЗАТЕЛЬНО должен быть зафайрволен на IP observability-VPS: любой,
|
||||
//! кто до него дотянется, увидит число активных соединений/трафик этой
|
||||
//! ноды (не секрет пользователей, но разведка для DPI/блокировщика).
|
||||
//!
|
||||
//! Тот же hand-rolled HTTP-паттерн, что и в `health.rs` (не тянуть HTTP-
|
||||
//! фреймворк ради одного эндпоинта на бинарнике, для которого важен размер
|
||||
//! и минимальные зависимости) — просто рендерит текущий снапшот метрик из
|
||||
//! уже установленного `PrometheusHandle` на каждый коннект.
|
||||
|
||||
use metrics_exporter_prometheus::{PrometheusBuilder, PrometheusHandle};
|
||||
use netrunner_logger::{error, info, warn};
|
||||
use tokio::io::AsyncWriteExt;
|
||||
use tokio::net::TcpListener;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
|
||||
/// Регистрирует глобальный recorder `metrics`-крейта — вызывать ровно один
|
||||
/// раз при старте, до первого `metrics::counter!`/`gauge!`/`histogram!`.
|
||||
pub fn install_recorder() -> PrometheusHandle {
|
||||
PrometheusBuilder::new()
|
||||
.install_recorder()
|
||||
.expect("Failed to install Prometheus recorder")
|
||||
}
|
||||
|
||||
pub async fn run(host: String, port: u16, handle: PrometheusHandle, token: CancellationToken) {
|
||||
let addr = format!("{host}:{port}");
|
||||
let listener = match TcpListener::bind(&addr).await {
|
||||
Ok(l) => l,
|
||||
Err(e) => {
|
||||
error!(error = %e, addr = %addr, "Не удалось поднять metrics-листенер");
|
||||
return;
|
||||
}
|
||||
};
|
||||
info!("📊 Metrics-эндпоинт слушает на {}", addr);
|
||||
|
||||
loop {
|
||||
tokio::select! {
|
||||
_ = token.cancelled() => {
|
||||
info!("Metrics-listener остановлен по сигналу отмены.");
|
||||
break;
|
||||
}
|
||||
res = listener.accept() => {
|
||||
let Ok((mut stream, _)) = res else { continue };
|
||||
let body = handle.render();
|
||||
tokio::spawn(async move {
|
||||
let response = format!(
|
||||
"HTTP/1.1 200 OK\r\nContent-Type: text/plain; version=0.0.4\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{}",
|
||||
body.len(),
|
||||
body
|
||||
);
|
||||
if let Err(e) = stream.write_all(response.as_bytes()).await {
|
||||
warn!(error = %e, "Metrics-listener: ошибка записи ответа");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user