From a815f6ac9c06a1a65ba9f6e101e6c12248fedd14 Mon Sep 17 00:00:00 2001 From: Willem Date: Mon, 20 Oct 2025 11:44:11 -0400 Subject: [PATCH] feat: scrape targets to be able to get snmp alerts from machines to prometheus --- .../topology/oberservability/monitoring.rs | 9 +- .../kube_prometheus/crd/crd_scrape_config.rs | 187 ++++++++++++++++++ .../monitoring/kube_prometheus/crd/mod.rs | 1 + .../helm_prometheus_alert_score.rs | 1 + harmony/src/modules/monitoring/mod.rs | 1 + .../modules/monitoring/scrape_target/mod.rs | 1 + .../monitoring/scrape_target/server.rs | 76 +++++++ 7 files changed, 274 insertions(+), 2 deletions(-) create mode 100644 harmony/src/modules/monitoring/kube_prometheus/crd/crd_scrape_config.rs create mode 100644 harmony/src/modules/monitoring/scrape_target/mod.rs create mode 100644 harmony/src/modules/monitoring/scrape_target/server.rs diff --git a/harmony/src/domain/topology/oberservability/monitoring.rs b/harmony/src/domain/topology/oberservability/monitoring.rs index 1489e83..d9fe4d0 100644 --- a/harmony/src/domain/topology/oberservability/monitoring.rs +++ b/harmony/src/domain/topology/oberservability/monitoring.rs @@ -21,6 +21,7 @@ pub struct AlertingInterpret { pub sender: S, pub receivers: Vec>>, pub rules: Vec>>, + pub scrape_target: Vec>>, } #[async_trait] @@ -38,6 +39,10 @@ impl, T: Topology> Interpret for AlertingInte debug!("installing rule: {:#?}", rule); rule.install(&self.sender).await?; } + for target in self.scrape_target.iter() { + debug!("installing scrape_target: {:#?}", target); + target.install(&self.sender).await?; + } self.sender.ensure_installed(inventory, topology).await?; Ok(Outcome::success(format!( "successfully installed alert sender {}", @@ -77,6 +82,6 @@ pub trait AlertRule: std::fmt::Debug + Send + Sync { } #[async_trait] -pub trait ScrapeTarget { - async fn install(&self, sender: &S) -> Result<(), InterpretError>; +pub trait ScrapeTarget: std::fmt::Debug + Send + Sync { + async fn install(&self, sender: &S) -> Result; } diff --git a/harmony/src/modules/monitoring/kube_prometheus/crd/crd_scrape_config.rs b/harmony/src/modules/monitoring/kube_prometheus/crd/crd_scrape_config.rs new file mode 100644 index 0000000..24a2833 --- /dev/null +++ b/harmony/src/modules/monitoring/kube_prometheus/crd/crd_scrape_config.rs @@ -0,0 +1,187 @@ +use std::net::IpAddr; + +use async_trait::async_trait; +use kube::CustomResource; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +use crate::{ + modules::monitoring::kube_prometheus::crd::{ + crd_alertmanager_config::CRDPrometheus, crd_prometheuses::LabelSelector, + }, + topology::oberservability::monitoring::ScrapeTarget, +}; + +#[derive(CustomResource, Serialize, Deserialize, Debug, Clone, JsonSchema)] +#[kube( + group = "monitoring.coreos.com", + version = "v1alpha1", + kind = "ScrapeConfig", + plural = "scrapeconfigs", + namespaced +)] +#[serde(rename_all = "camelCase")] +pub struct ScrapeConfigSpec { + /// List of static configurations. + pub static_configs: Option>, + + /// Kubernetes service discovery. + pub kubernetes_sd_configs: Option>, + + /// HTTP-based service discovery. + pub http_sd_configs: Option>, + + /// File-based service discovery. + pub file_sd_configs: Option>, + + /// DNS-based service discovery. + pub dns_sd_configs: Option>, + + /// Consul service discovery. + pub consul_sd_configs: Option>, + + /// Relabeling configuration applied to discovered targets. + pub relabel_configs: Option>, + + /// Metric relabeling configuration applied to scraped samples. + pub metric_relabel_configs: Option>, + + /// Path to scrape metrics from (defaults to `/metrics`). + pub metrics_path: Option, + + /// Interval at which Prometheus scrapes targets (e.g., "30s"). + pub scrape_interval: Option, + + /// Timeout for scraping (e.g., "10s"). + pub scrape_timeout: Option, + + /// Optional job name override. + pub job_name: Option, + + /// Optional scheme (http or https). + pub scheme: Option, + + /// Authorization paramaters for snmp walk + pub params: Option, +} + +/// Static configuration section of a ScrapeConfig. +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +#[serde(rename_all = "camelCase")] +pub struct StaticConfig { + pub targets: Vec, + + pub labels: Option, +} + +/// Relabeling configuration for target or metric relabeling. +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +#[serde(rename_all = "camelCase")] +pub struct RelabelConfig { + pub source_labels: Option>, + pub separator: Option, + pub target_label: Option, + pub regex: Option, + pub modulus: Option, + pub replacement: Option, + pub action: Option, +} + +/// Kubernetes service discovery configuration. +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +#[serde(rename_all = "camelCase")] +pub struct KubernetesSDConfig { + ///"pod", "service", "endpoints"pub role: String, + pub namespaces: Option, + pub selectors: Option>, + pub api_server: Option, + pub bearer_token_file: Option, + pub tls_config: Option, +} + +/// Namespace selector for Kubernetes service discovery. +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +#[serde(rename_all = "camelCase")] +pub struct NamespaceSelector { + pub any: Option, + pub match_names: Option>, +} + +/// HTTP-based service discovery configuration. +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +#[serde(rename_all = "camelCase")] +pub struct HttpSDConfig { + pub url: String, + pub refresh_interval: Option, + pub basic_auth: Option, + pub authorization: Option, + pub tls_config: Option, +} + +/// File-based service discovery configuration. +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +#[serde(rename_all = "camelCase")] +pub struct FileSDConfig { + pub files: Vec, + pub refresh_interval: Option, +} + +/// DNS-based service discovery configuration. +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +#[serde(rename_all = "camelCase")] +pub struct DnsSDConfig { + pub names: Vec, + pub refresh_interval: Option, + pub type_: Option, // SRV, A, AAAA + pub port: Option, +} + +/// Consul service discovery configuration. +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +#[serde(rename_all = "camelCase")] +pub struct ConsulSDConfig { + pub server: String, + pub services: Option>, + pub scheme: Option, + pub datacenter: Option, + pub tag_separator: Option, + pub refresh_interval: Option, + pub tls_config: Option, +} + +/// Basic authentication credentials. +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +#[serde(rename_all = "camelCase")] +pub struct BasicAuth { + pub username: String, + pub password: Option, + pub password_file: Option, +} + +/// Bearer token or other auth mechanisms. +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +#[serde(rename_all = "camelCase")] +pub struct Authorization { + pub credentials: Option, + pub credentials_file: Option, + pub type_: Option, +} + +/// TLS configuration for secure scraping. +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +#[serde(rename_all = "camelCase")] +pub struct TLSConfig { + pub ca_file: Option, + pub cert_file: Option, + pub key_file: Option, + pub server_name: Option, + pub insecure_skip_verify: Option, +} + +/// Authorization parameters for SNMP walk. +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +#[serde(rename_all = "camelCase")] +pub struct Params { + pub auth: Option>, + pub module: Option>, +} diff --git a/harmony/src/modules/monitoring/kube_prometheus/crd/mod.rs b/harmony/src/modules/monitoring/kube_prometheus/crd/mod.rs index 4dbea74..c8cb854 100644 --- a/harmony/src/modules/monitoring/kube_prometheus/crd/mod.rs +++ b/harmony/src/modules/monitoring/kube_prometheus/crd/mod.rs @@ -4,6 +4,7 @@ pub mod crd_default_rules; pub mod crd_grafana; pub mod crd_prometheus_rules; pub mod crd_prometheuses; +pub mod crd_scrape_config; pub mod grafana_default_dashboard; pub mod grafana_operator; pub mod prometheus_operator; diff --git a/harmony/src/modules/monitoring/kube_prometheus/helm_prometheus_alert_score.rs b/harmony/src/modules/monitoring/kube_prometheus/helm_prometheus_alert_score.rs index c9a0c04..da26b03 100644 --- a/harmony/src/modules/monitoring/kube_prometheus/helm_prometheus_alert_score.rs +++ b/harmony/src/modules/monitoring/kube_prometheus/helm_prometheus_alert_score.rs @@ -31,6 +31,7 @@ impl Score for HelmPrometheusAlert sender: KubePrometheus { config }, receivers: self.receivers.clone(), rules: self.rules.clone(), + scrape_target: vec![], }) } fn name(&self) -> String { diff --git a/harmony/src/modules/monitoring/mod.rs b/harmony/src/modules/monitoring/mod.rs index edda516..7f07d5a 100644 --- a/harmony/src/modules/monitoring/mod.rs +++ b/harmony/src/modules/monitoring/mod.rs @@ -6,3 +6,4 @@ pub mod kube_prometheus; pub mod ntfy; pub mod okd; pub mod prometheus; +pub mod scrape_target; diff --git a/harmony/src/modules/monitoring/scrape_target/mod.rs b/harmony/src/modules/monitoring/scrape_target/mod.rs new file mode 100644 index 0000000..74f47ad --- /dev/null +++ b/harmony/src/modules/monitoring/scrape_target/mod.rs @@ -0,0 +1 @@ +pub mod server; diff --git a/harmony/src/modules/monitoring/scrape_target/server.rs b/harmony/src/modules/monitoring/scrape_target/server.rs new file mode 100644 index 0000000..ba41f49 --- /dev/null +++ b/harmony/src/modules/monitoring/scrape_target/server.rs @@ -0,0 +1,76 @@ +use std::net::IpAddr; + +use async_trait::async_trait; +use kube::api::ObjectMeta; +use serde::Serialize; + +use crate::{ + interpret::{InterpretError, Outcome}, + modules::monitoring::kube_prometheus::crd::{ + crd_alertmanager_config::CRDPrometheus, + crd_scrape_config::{Params, RelabelConfig, ScrapeConfig, ScrapeConfigSpec, StaticConfig}, + }, + topology::oberservability::monitoring::ScrapeTarget, +}; + +#[derive(Debug, Clone, Serialize)] +pub struct Server { + pub name: String, + pub ip: IpAddr, + pub auth: String, + pub module: String, + pub domain: String, +} + +#[async_trait] +impl ScrapeTarget for Server { + async fn install(&self, sender: &CRDPrometheus) -> Result { + let scrape_config_spec = ScrapeConfigSpec { + static_configs: Some(vec![StaticConfig { + targets: vec![self.ip.to_string()], + labels: None, + }]), + scrape_interval: Some("2m".to_string()), + kubernetes_sd_configs: None, + http_sd_configs: None, + file_sd_configs: None, + dns_sd_configs: None, + params: Some(Params { + auth: Some(vec![self.auth.clone()]), + module: Some(vec![self.module.clone()]), + }), + consul_sd_configs: None, + relabel_configs: Some(vec![RelabelConfig { + action: None, + source_labels: Some(vec!["__address__".to_string()]), + separator: None, + target_label: Some("__param_target".to_string()), + regex: None, + replacement: Some(format!("snmp.{}:31080", self.domain.clone())), + modulus: None, + }]), + metric_relabel_configs: None, + metrics_path: Some("/snmp".to_string()), + scrape_timeout: Some("2m".to_string()), + job_name: Some(format!("snmp_exporter/cloud/{}", self.name.clone())), + scheme: None, + }; + + let scrape_config = ScrapeConfig { + metadata: ObjectMeta { + name: Some(self.name.clone()), + namespace: Some(sender.namespace.clone()), + ..Default::default() + }, + spec: scrape_config_spec, + }; + sender + .client + .apply(&scrape_config, Some(&sender.namespace.clone())) + .await?; + Ok(Outcome::success(format!( + "installed scrape target {}", + self.name.clone() + ))) + } +}