fix(relay): bind single-node probes to loopback

Signed-off-by: npub13n66s06epmqf2kc3v373ez8hj65cuzyvxzjf93vwpervxqn2u7jq2qd9je <8cf5a83f590ec0955b11647d1c88f796a98e088c30a492c58e0e46c3026ae7a4@buzz.block.builderlab.xyz>
This commit is contained in:
npub13n66s06epmqf2kc3v373ez8hj65cuzyvxzjf93vwpervxqn2u7jq2qd9je
2026-08-10 12:12:03 -04:00
committed by Brother Darryl
parent 024491648d
commit 6b7b533f14
2 changed files with 46 additions and 5 deletions
+13 -4
View File
@@ -1,4 +1,5 @@
use std::collections::{HashMap, HashSet};
use std::net::{IpAddr, Ipv4Addr};
use std::sync::atomic::Ordering;
use std::sync::Arc;
@@ -18,7 +19,7 @@ use buzz_db::{Db, DbConfig};
use buzz_pubsub::{rate_limiter::AdmissionRateLimiter, InProcessNip98ReplayGuard, PubSubManager};
use buzz_search::SearchService;
use buzz_relay::config::{Config, MAX_DRAIN_JITTER_MS};
use buzz_relay::config::{Config, RelayProfile, MAX_DRAIN_JITTER_MS};
use buzz_relay::metrics as relay_metrics;
use buzz_relay::router::{build_health_router, build_router};
use buzz_relay::state::{AppBackends, AppState};
@@ -1217,7 +1218,7 @@ async fn run_single_node(config: Config, tracer_init: telemetry::TracerInit) ->
));
}
relay_metrics::install(config.metrics_port, usage_metrics_idle_timeout_secs(60));
relay_metrics::install_loopback(config.metrics_port, usage_metrics_idle_timeout_secs(60));
let db_path =
std::env::var("BUZZ_LOCAL_DB").unwrap_or_else(|_| "buzz-local.sqlite".to_string());
let media_root =
@@ -1334,6 +1335,13 @@ async fn run_single_node(config: Config, tracer_init: telemetry::TracerInit) ->
/// roughly the 5s grace plus the ack wait.
const GRACEFUL_DRAIN_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(30);
fn health_listener_ip(profile: RelayProfile) -> IpAddr {
match profile {
RelayProfile::SingleNode => IpAddr::V4(Ipv4Addr::LOCALHOST),
RelayProfile::Production => IpAddr::V4(Ipv4Addr::UNSPECIFIED),
}
}
async fn serve(
router: axum::Router,
health_router: axum::Router,
@@ -1341,10 +1349,11 @@ async fn serve(
) -> anyhow::Result<()> {
let config = &state.config;
let health_listener = tokio::net::TcpListener::bind(("0.0.0.0", config.health_port))
let health_host = health_listener_ip(config.profile);
let health_listener = tokio::net::TcpListener::bind((health_host, config.health_port))
.await
.map_err(|e| anyhow::anyhow!("Failed to bind health port {}: {e}", config.health_port))?;
info!(port = config.health_port, "Health probe listener started");
info!(host = %health_host, port = config.health_port, "Health probe listener started");
tokio::spawn(async move {
axum::serve(health_listener, health_router).await.ok();
});
+33 -1
View File
@@ -14,6 +14,7 @@
//! recorded by [`track_metrics`] middleware on the app router. Buzz-specific
//! metrics are recorded inline at their call sites.
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
use std::time::{Duration, Instant};
use axum::{
@@ -56,6 +57,17 @@ const GIT_PACK_BUCKETS: [f64; 9] = [0.0, 1.0, 2.0, 4.0, 8.0, 16.0, 32.0, 64.0, 1
/// Integer-count buckets for fan-out recipient histograms.
const FANOUT_BUCKETS: [f64; 9] = [0.0, 1.0, 5.0, 10.0, 25.0, 50.0, 100.0, 500.0, 1000.0];
fn prometheus_listener(port: u16, loopback_only: bool) -> SocketAddr {
SocketAddr::new(
IpAddr::V4(if loopback_only {
Ipv4Addr::LOCALHOST
} else {
Ipv4Addr::UNSPECIFIED
}),
port,
)
}
/// Install the global metrics recorder and spawn the Prometheus HTTP exporter.
///
/// `build()` returns the recorder + exporter future and internally spawns
@@ -64,8 +76,17 @@ const FANOUT_BUCKETS: [f64; 9] = [0.0, 1.0, 5.0, 10.0, 25.0, 50.0, 100.0, 500.0,
/// Must be called from within a Tokio runtime.
/// Panics if a recorder is already installed or the port is in use.
pub fn install(port: u16, gauge_idle_timeout_secs: u64) {
install_on(prometheus_listener(port, false), gauge_idle_timeout_secs);
}
/// Install the metrics exporter on loopback for the single-node profile.
pub fn install_loopback(port: u16, gauge_idle_timeout_secs: u64) {
install_on(prometheus_listener(port, true), gauge_idle_timeout_secs);
}
fn install_on(listener: SocketAddr, gauge_idle_timeout_secs: u64) {
let (recorder, exporter) = PrometheusBuilder::new()
.with_http_listener(([0, 0, 0, 0], port))
.with_http_listener(listener)
// Remove gauge series that the relay intentionally stops emitting.
.idle_timeout(
MetricKindMask::GAUGE,
@@ -205,3 +226,14 @@ pub async fn track_metrics(req: Request, next: Next) -> Response {
response
}
#[cfg(test)]
mod tests {
use super::prometheus_listener;
#[test]
fn single_node_metrics_listener_is_loopback_only() {
assert!(prometheus_listener(9102, true).ip().is_loopback());
assert!(prometheus_listener(9102, false).ip().is_unspecified());
}
}