Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
117 changes: 116 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@ tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter", "json"] }
tracing-appender = "0.2"

# Metrics
metrics = "0.24"
metrics-exporter-prometheus = "0.18"

# Unix/Process
nix = { version = "0.29", features = ["signal", "process", "user", "fs", "term"] }

Expand Down
13 changes: 13 additions & 0 deletions crates/openshell-core/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,12 @@ pub struct Config {
#[serde(default)]
pub health_bind_address: Option<SocketAddr>,

/// Address to bind the Prometheus metrics endpoint to.
///
/// When `None`, the dedicated metrics listener is disabled.
#[serde(default)]
pub metrics_bind_address: Option<SocketAddr>,

/// Log level (trace, debug, info, warn, error).
#[serde(default = "default_log_level")]
pub log_level: String,
Expand Down Expand Up @@ -183,6 +189,7 @@ impl Config {
Self {
bind_address: default_bind_address(),
health_bind_address: None,
metrics_bind_address: None,
log_level: default_log_level(),
tls,
database_url: String::new(),
Expand Down Expand Up @@ -216,6 +223,12 @@ impl Config {
self
}

#[must_use]
pub const fn with_metrics_bind_address(mut self, addr: SocketAddr) -> Self {
self.metrics_bind_address = Some(addr);
self
}

/// Create a new configuration with the given log level.
#[must_use]
pub fn with_log_level(mut self, level: impl Into<String>) -> Self {
Expand Down
4 changes: 4 additions & 0 deletions crates/openshell-server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@ anyhow = { workspace = true }
tracing = { workspace = true }
tracing-subscriber = { workspace = true }

# Metrics
metrics = { workspace = true }
metrics-exporter-prometheus = { workspace = true }

# Utilities
futures = { workspace = true }
bytes = { workspace = true }
Expand Down
23 changes: 23 additions & 0 deletions crates/openshell-server/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ struct Args {
#[arg(long, default_value_t = 0, env = "OPENSHELL_HEALTH_PORT")]
health_port: u16,

/// Port for the Prometheus metrics endpoint (/metrics).
/// Set to 0 to disable the dedicated metrics listener.
#[arg(long, default_value_t = 0, env = "OPENSHELL_METRICS_PORT")]
metrics_port: u16,

/// Log level (trace, debug, info, warn, error).
#[arg(long, default_value = "info", env = "OPENSHELL_LOG_LEVEL")]
log_level: String,
Expand Down Expand Up @@ -202,6 +207,7 @@ async fn run_from_args(args: Args) -> Result<()> {
);

let bind = SocketAddr::from(([0, 0, 0, 0], args.port));

let tls = if args.disable_tls {
None
} else {
Expand Down Expand Up @@ -241,6 +247,23 @@ async fn run_from_args(args: Args) -> Result<()> {
config = config.with_health_bind_address(health_bind);
}

if args.metrics_port != 0 {
if args.port == args.metrics_port {
return Err(miette::miette!(
"--port and --metrics-port must be different (both set to {})",
args.port
));
}
if args.health_port != 0 && args.health_port == args.metrics_port {
return Err(miette::miette!(
"--health-port and --metrics-port must be different (both set to {})",
args.health_port
));
}
Comment thread
sjenning marked this conversation as resolved.
let metrics_bind = SocketAddr::from(([0, 0, 0, 0], args.metrics_port));
config = config.with_metrics_bind_address(metrics_bind);
}

config = config
.with_database_url(args.db_url)
.with_compute_drivers(args.drivers)
Expand Down
14 changes: 13 additions & 1 deletion crates/openshell-server/src/http.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@

//! HTTP health endpoints using Axum.

use axum::{Json, Router, http::StatusCode, response::IntoResponse, routing::get};
use axum::{Json, Router, extract::State, http::StatusCode, response::IntoResponse, routing::get};
use metrics_exporter_prometheus::PrometheusHandle;
use serde::Serialize;
use std::sync::Arc;

Expand Down Expand Up @@ -45,6 +46,17 @@ pub fn health_router() -> Router {
.route("/readyz", get(readyz))
}

/// Create the metrics router for the dedicated metrics port.
pub fn metrics_router(handle: PrometheusHandle) -> Router {
Router::new()
.route("/metrics", get(render_metrics))
.with_state(handle)
}

async fn render_metrics(State(handle): State<PrometheusHandle>) -> impl IntoResponse {
handle.render()
}

/// Create the HTTP router.
pub fn http_router(state: Arc<crate::ServerState>) -> Router {
crate::ssh_tunnel::router(state.clone())
Expand Down
28 changes: 27 additions & 1 deletion crates/openshell-server/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ mod tls;
pub mod tracing_bus;
mod ws_tunnel;

use metrics_exporter_prometheus::PrometheusBuilder;
use openshell_core::{ComputeDriverKind, Config, Error, Result};
use std::collections::HashMap;
use std::io::ErrorKind;
Expand All @@ -45,7 +46,7 @@ use tracing::{debug, error, info};

use compute::{ComputeRuntime, VmComputeConfig};
pub use grpc::OpenShellService;
pub use http::{health_router, http_router};
pub use http::{health_router, http_router, metrics_router};
pub use multiplex::{MultiplexService, MultiplexedService};
use openshell_driver_kubernetes::KubernetesComputeConfig;
use persistence::Store;
Expand Down Expand Up @@ -205,6 +206,31 @@ pub async fn run_server(
info!("Health server disabled");
}

// Bind the Prometheus metrics endpoint on a dedicated port when configured.
if let Some(metrics_bind_address) = config.metrics_bind_address {
let prometheus_handle = PrometheusBuilder::new()
.install_recorder()
.map_err(|e| Error::config(format!("failed to install metrics recorder: {e}")))?;
let metrics_listener = TcpListener::bind(metrics_bind_address).await.map_err(|e| {
Error::transport(format!(
"failed to bind metrics port {metrics_bind_address}: {e}",
))
})?;
info!(address = %metrics_bind_address, "Metrics server listening");
tokio::spawn(async move {
if let Err(e) = axum::serve(
metrics_listener,
metrics_router(prometheus_handle).into_make_service(),
)
.await
{
error!("Metrics server error: {e}");
}
});
} else {
info!("Metrics server disabled");
}

// Build TLS acceptor when TLS is configured; otherwise serve plaintext.
let tls_acceptor = if let Some(tls) = &config.tls {
Some(TlsAcceptor::from_files(
Expand Down
Loading
Loading