Метрики /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:
2026-07-09 23:21:26 +07:00
parent fafd573a28
commit cfcb1afcec
11 changed files with 367 additions and 13 deletions
+7
View File
@@ -62,6 +62,7 @@ impl Circuit {
let mut state = self.state.lock().unwrap();
state.consecutive_failures = 0;
state.open_until = None;
metrics::gauge!("netrunner_circuit_breaker_open").set(0.0);
}
/// Считать неудачей только сетевые ошибки/таймауты/5xx — HTTP 401/403 на
@@ -71,6 +72,7 @@ impl Circuit {
state.consecutive_failures += 1;
if state.consecutive_failures >= FAILURE_THRESHOLD {
state.open_until = Some(Instant::now() + CIRCUIT_OPEN_COOLDOWN);
metrics::gauge!("netrunner_circuit_breaker_open").set(1.0);
warn!(
failures = state.consecutive_failures,
"Circuit breaker разомкнут: бэкенд не отвечает {} раз подряд",
@@ -140,6 +142,7 @@ struct UsageResponse {
impl AuthValidator for BackendClient {
async fn validate(&self, token: &str) -> Result<UserQuota, AppError> {
if token.is_empty() {
metrics::counter!("netrunner_auth_failures_total").increment(1);
return Err(AppError::new(
netrunner_logger::ERR_AUTH_FAILED,
"Доступ запрещен",
@@ -158,6 +161,7 @@ impl AuthValidator for BackendClient {
return Err(Self::circuit_open_error());
}
let request_start = Instant::now();
let resp = self
.http
.post(format!("{}/api/v1/internal/validate", self.base_url))
@@ -173,6 +177,8 @@ impl AuthValidator for BackendClient {
e.to_string(),
)
})?;
metrics::histogram!("netrunner_auth_validate_duration_seconds")
.record(request_start.elapsed().as_secs_f64());
// 5xx — признак нездоровья самого бэкенда, считается неудачей для
// circuit breaker'а. 4xx (например 401 на невалидный токен) — это
@@ -181,6 +187,7 @@ impl AuthValidator for BackendClient {
self.circuit.record_failure();
}
if !resp.status().is_success() {
metrics::counter!("netrunner_auth_failures_total").increment(1);
return Err(AppError::new(
netrunner_logger::ERR_AUTH_FAILED,
"Доступ запрещен",