feat: add service monitors support to prom #66

Merged
johnride merged 9 commits from monitoring_servicemonitor into master 2025-07-02 15:29:23 +00:00
4 changed files with 174 additions and 3 deletions
Showing only changes of commit baf63e97b1 - Show all commits

View File

@ -26,6 +26,7 @@ pub struct KubePrometheusConfig {
pub prometheus_operator: bool,
pub alert_receiver_configs: Vec<AlertManagerChannelConfig>,
pub alert_rules: Vec<AlertManagerAdditionalPromRules>,
pub additional_service_monitors: Vec<ServiceMonitor>,
}
impl KubePrometheusConfig {
pub fn new() -> Self {
@ -49,6 +50,7 @@ impl KubePrometheusConfig {
kube_scheduler: false,
alert_receiver_configs: vec![],
alert_rules: vec![],
additional_service_monitors: vec![],
}
}
}

View File

@ -12,7 +12,7 @@ use crate::modules::{
helm::chart::HelmChartScore,
monitoring::kube_prometheus::types::{
AlertGroup, AlertManager, AlertManagerAdditionalPromRules, AlertManagerConfig,
AlertManagerRoute, AlertManagerValues,
AlertManagerRoute, AlertManagerValues, PrometheusConfig,
},
};
@ -101,10 +101,23 @@ nodeExporter:
enabled: {node_exporter}
prometheusOperator:
enabled: {prometheus_operator}
prometheus:
enabled: {prometheus}
"#,
);
let prometheus_config =
crate::modules::monitoring::kube_prometheus::types::PrometheusConfigValues {
prometheus: PrometheusConfig {
prometheus,
additional_service_monitors: config.additional_service_monitors.clone(),
},
};
let prometheus_config_yaml =
serde_yaml::to_string(&prometheus_config).expect("Failed to serialize YAML");
debug!(
"serialized prometheus config: \n {:#}",
prometheus_config_yaml
);
values.push_str(&prometheus_config_yaml);
// add required null receiver for prometheus alert manager
let mut null_receiver = Mapping::new();

View File

@ -1,6 +1,7 @@
use std::collections::BTreeMap;
use async_trait::async_trait;
use harmony_macros::ingress_path;
use serde::Serialize;
use serde_yaml::{Mapping, Sequence, Value};
@ -53,3 +54,138 @@ pub struct AlertManagerAdditionalPromRules {
pub struct AlertGroup {
pub groups: Vec<AlertManagerRuleGroup>,
}
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct PrometheusConfigValues {
pub prometheus: PrometheusConfig,
}
johnride marked this conversation as resolved Outdated

Not a String, should be a path type of some sort. I think we already handle this somewhere else correctly.

You might have to implement Serialize for it or wrap it into another type that Serializes as a String but that's ok.

Not a String, should be a path type of some sort. I think we already handle this somewhere else correctly. You might have to implement Serialize for it or wrap it into another type that Serializes as a String but that's ok.
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct PrometheusConfig {
pub prometheus: String,
pub additional_service_monitors: Vec<ServiceMonitor>,
}
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ServiceMonitorTLSConfig {
// ## Path to the CA file
// ##
pub ca_file: String,
// ## Path to client certificate file
// ##
pub cert_file: String,
// ## Skip certificate verification
// ##
pub insecure_skip_verify: bool,
// ## Path to client key file
// ##
pub key_file: String,
// ## Server name used to verify host name
// ##
pub server_name: String,
}
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ServiceMonitorEndpoint {
// ## Name of the endpoint's service port
// ## Mutually exclusive with targetPort
pub port: String,
// ## Name or number of the endpoint's target port

This should be a specific type that validates the path

This should be a specific type that validates the path
// ## Mutually exclusive with port
pub target_port: String,
// ## File containing bearer token to be used when scraping targets
taha marked this conversation as resolved Outdated

This should be an enum :

pub enum URLScheme {
 HTTP,
 HTTPS,
 // Maybe others such as :
 FILE,
 FTP,
 OTHER(String), // With this we are both usable with more frequent schemes and extensible 
}

impl Display for URLScheme {
 // TODO
}
 
This should be an enum : ```rust pub enum URLScheme { HTTP, HTTPS, // Maybe others such as : FILE, FTP, OTHER(String), // With this we are both usable with more frequent schemes and extensible } impl Display for URLScheme { // TODO }
// ##
pub bearer_token_file: String,
// ## Interval at which metrics should be scraped
// ##
pub interval: String,
// ## HTTP path to scrape for metrics
// ##
pub path: String,
// ## HTTP scheme to use for scraping
// ##
pub scheme: String,
// ## TLS configuration to use when scraping the endpoint
// ##
pub tls_config: ServiceMonitorTLSConfig,
// ## MetricRelabelConfigs to apply to samples after scraping, but before ingestion.
// ## ref: https://github.com/prometheus-operator/prometheus-operator/blob/main/Documentation/api-reference/api.md#relabelconfig
// ##
// # - action: keep
// # regex: 'kube_(daemonset|deployment|pod|namespace|node|statefulset).+'
// # sourceLabels: [__name__]
pub metric_relabelings: Vec<Mapping>,
// ## RelabelConfigs to apply to samples before scraping
// ## ref: https://github.com/prometheus-operator/prometheus-operator/blob/main/Documentation/api-reference/api.md#relabelconfig
// ##
taha marked this conversation as resolved Outdated

I guess operator is not any string? Probably should be an enum too.

I guess operator is not any string? Probably should be an enum too.
// # - sourceLabels: [__meta_kubernetes_pod_node_name]
// # separator: ;
// # regex: ^(.*)$
// # targetLabel: nodename
// # replacement: $1
// # action: replace
pub relabelings: Vec<Mapping>,
}
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ServiceMonitor {
pub name: String,
// # Additional labels to set used for the ServiceMonitorSelector. Together with standard labels from the chart
pub additional_labels: Mapping,
// # Service label for use in assembling a job name of the form <label value>-<port>
// # If no label is specified, the service name is used.
pub job_label: String,
// # labels to transfer from the kubernetes service to the target

I think we already do have a Label type somewhere. I think it would be more appropriate here than String. That's true for all the label related fields in this file.

This Label type might not be fully compatible in its current form/place but it is definitely a semantic that we will see very often in various use cases and implementations. I think it is worth for us to maintain a Label type which we can eventually provide very interesting functionnality for such as search, tracking, matching, versionning, etc.

I think we already do have a Label type somewhere. I think it would be more appropriate here than String. That's true for all the label related fields in this file. This Label type might not be fully compatible in its current form/place but it is definitely a semantic that we will see very often in various use cases and implementations. I think it is worth for us to maintain a Label type which we can eventually provide very interesting functionnality for such as search, tracking, matching, versionning, etc.
pub target_labels: Vec<String>,
// # labels to transfer from the kubernetes pods to the target
pub pod_target_labels: Vec<String>,
// # Label selector for services to which this ServiceMonitor applies
// # Example which selects all services to be monitored
// # with label "monitoredby" with values any of "example-service-1" or "example-service-2"
// matchExpressions:
// - key: "monitoredby"
// operator: In
// values:
// - example-service-1
// - example-service-2
pub selector: Mapping,
// # label selector for services
pub match_labels: Mapping,
// # Namespaces from which services are selected
// # Match any namespace
// any: bool,
// # Explicit list of namespace names to select
// matchNames: Vec,
pub namespace_selector: Mapping,
// # Endpoints of the selected service to be monitored
pub endpoints: Vec<ServiceMonitorEndpoint>,
// # Fallback scrape protocol used by Prometheus for scraping metrics
// # ref: https://github.com/prometheus-operator/prometheus-operator/blob/main/Documentation/api-reference/api.md#monitoring.coreos.com/v1.ScrapeProtocol
pub fallback_scrape_protocol: String,
}

View File

@ -133,6 +133,26 @@ pub fn ingress_path(input: TokenStream) -> TokenStream {
}
}
/// Verify that a string is a valid http scheme
/// Panics if not http or https
#[proc_macro]
pub fn http_scheme(input: TokenStream) -> TokenStream {
taha marked this conversation as resolved Outdated

What's the use case for this? I don't see this used in this pr.

What's the use case for this? I don't see this used in this pr.
Outdated
Review

For the httpScheme field, in endpoints. I hadn't yet pushed the commit that uses it

For the `httpScheme` field, in endpoints. I hadn't yet pushed the commit that uses it

Yeah ok, I see what you did there.

But this is not a good use case for Macros. Macros are a last resort when the type semantics are already correct and we can't rely on the regular rust type system anymore to provide a good UX/DX. In this case a macro outputting a String type prevents us from adding useful functionnality around the "Scheme" idea.

I can very well see us eventually detecting the scheme here and performing automatic endpoint discovery and health checks or something like that, which we cannot do if we keep this as a String. See my commnent above, this should be an enum.

Yeah ok, I see what you did there. But this is not a good use case for Macros. Macros are a last resort when the type semantics are already correct and we can't rely on the regular rust type system anymore to provide a good UX/DX. In this case a macro outputting a String type prevents us from adding useful functionnality around the "Scheme" idea. I can very well see us eventually detecting the scheme here and performing automatic endpoint discovery and health checks or something like that, which we cannot do if we keep this as a `String`. See my commnent above, this should be an enum.
let input = parse_macro_input!(input as LitStr);
let scheme_str = input.value();
if scheme_str.to_lowercase() == "http" {
let expanded = quote! {(#scheme_str.to_lowercase().to_string()) };
return TokenStream::from(expanded);
}
if scheme_str.to_lowercase() == "https" {
let expanded = quote! {(#scheme_str.to_lowercase().to_string()) };
return TokenStream::from(expanded);
}
panic!("Invalid HTTP scheme")
}
#[proc_macro]
pub fn cidrv4(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as LitStr);