diff --git a/CHANGELOG.md b/CHANGELOG.md index 236f40c1..77cf3ae5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ All notable changes to this project will be documented in this file. - Helm: Allow Pod `priorityClassName` to be configured ([#840]). - Add support for `2.6.0` ([#849]). +- Add `prometheus.io/path|port|scheme` annotations to metrics service ([#855]). ### Changed @@ -24,6 +25,7 @@ All notable changes to this project will be documented in this file. [#840]: https://github.com/stackabletech/nifi-operator/pull/840 [#844]: https://github.com/stackabletech/nifi-operator/pull/844 [#849]: https://github.com/stackabletech/nifi-operator/pull/849 +[#855]: https://github.com/stackabletech/nifi-operator/pull/855 ## [25.7.0] - 2025-07-23 diff --git a/rust/operator-binary/src/controller.rs b/rust/operator-binary/src/controller.rs index 02e0d292..c7039148 100644 --- a/rust/operator-binary/src/controller.rs +++ b/rust/operator-binary/src/controller.rs @@ -110,10 +110,7 @@ use crate::{ build_tls_volume, check_or_generate_oidc_admin_password, check_or_generate_sensitive_key, tls::{KEYSTORE_NIFI_CONTAINER_MOUNT, KEYSTORE_VOLUME_NAME, TRUSTSTORE_VOLUME_NAME}, }, - service::{ - build_rolegroup_headless_service, build_rolegroup_metrics_service, metrics_service_port, - rolegroup_headless_service_name, rolegroup_metrics_service_name, - }, + service::{build_rolegroup_headless_service, build_rolegroup_metrics_service}, }; pub const NIFI_CONTROLLER_NAME: &str = "nificluster"; @@ -576,9 +573,7 @@ pub async fn reconcile_nifi( &rolegroup, role_group_service_recommended_labels, role_group_service_selector.into(), - vec![metrics_service_port( - &resolved_product_image.product_version, - )], + &resolved_product_image.product_version, ) .context(ServiceConfigurationSnafu)?; @@ -926,7 +921,7 @@ async fn build_node_rolegroup_statefulset( let node_address = format!( "$POD_NAME.{service_name}.{namespace}.svc.{cluster_domain}", - service_name = rolegroup_headless_service_name(&rolegroup_ref.object_name()), + service_name = rolegroup_ref.rolegroup_headless_service_name(), namespace = &nifi .metadata .namespace @@ -1360,7 +1355,7 @@ async fn build_node_rolegroup_statefulset( nifi, KEYSTORE_VOLUME_NAME, [ - rolegroup_metrics_service_name(rolegroup_ref.object_name()), + rolegroup_ref.rolegroup_metrics_service_name(), build_reporting_task_service_name(&nifi_cluster_name), ], SecretFormat::TlsPkcs12, @@ -1434,9 +1429,7 @@ async fn build_node_rolegroup_statefulset( ), ..LabelSelector::default() }, - service_name: Some(rolegroup_headless_service_name( - &rolegroup_ref.object_name(), - )), + service_name: Some(rolegroup_ref.rolegroup_headless_service_name()), template: pod_template, update_strategy: Some(StatefulSetUpdateStrategy { type_: if rolling_update_supported { diff --git a/rust/operator-binary/src/service.rs b/rust/operator-binary/src/service.rs index a403d3a2..a276f581 100644 --- a/rust/operator-binary/src/service.rs +++ b/rust/operator-binary/src/service.rs @@ -4,15 +4,12 @@ use snafu::{ResultExt, Snafu}; use stackable_operator::{ builder::meta::ObjectMetaBuilder, k8s_openapi::api::core::v1::{Service, ServicePort, ServiceSpec}, - kvp::{Label, ObjectLabels}, + kvp::{Annotations, Labels, ObjectLabels}, role_utils::RoleGroupRef, }; use crate::crd::{HTTPS_PORT, HTTPS_PORT_NAME, METRICS_PORT, METRICS_PORT_NAME, v1alpha1}; -const METRICS_SERVICE_SUFFIX: &str = "metrics"; -const HEADLESS_SERVICE_SUFFIX: &str = "headless"; - #[derive(Snafu, Debug)] pub enum Error { #[snafu(display("object is missing metadata to build owner reference"))] @@ -24,11 +21,6 @@ pub enum Error { MetadataBuild { source: stackable_operator::builder::meta::Error, }, - - #[snafu(display("failed to build Labels"))] - LabelBuild { - source: stackable_operator::kvp::LabelError, - }, } /// The rolegroup headless [`Service`] is a service that allows direct access to the instances of a certain rolegroup @@ -42,9 +34,7 @@ pub fn build_rolegroup_headless_service( Ok(Service { metadata: ObjectMetaBuilder::new() .name_and_namespace(nifi) - .name(rolegroup_headless_service_name( - &role_group_ref.object_name(), - )) + .name(role_group_ref.rolegroup_headless_service_name()) .ownerreference_from_resource(nifi, None, Some(true)) .context(ObjectMissingMetadataForOwnerRefSnafu)? .with_recommended_labels(object_labels) @@ -69,23 +59,24 @@ pub fn build_rolegroup_metrics_service( role_group_ref: &RoleGroupRef, object_labels: ObjectLabels, selector: BTreeMap, - ports: Vec, + product_version: &str, ) -> Result { Ok(Service { metadata: ObjectMetaBuilder::new() .name_and_namespace(nifi) - .name(rolegroup_metrics_service_name(role_group_ref.object_name())) + .name(role_group_ref.rolegroup_metrics_service_name()) .ownerreference_from_resource(nifi, None, Some(true)) .context(ObjectMissingMetadataForOwnerRefSnafu)? .with_recommended_labels(object_labels) .context(MetadataBuildSnafu)? - .with_label(Label::try_from(("prometheus.io/scrape", "true")).context(LabelBuildSnafu)?) + .with_labels(prometheus_labels()) + .with_annotations(prometheus_annotations(product_version)) .build(), spec: Some(ServiceSpec { // Internal communication does not need to be exposed type_: Some("ClusterIP".to_string()), cluster_ip: Some("None".to_string()), - ports: Some(ports), + ports: Some(vec![metrics_service_port(product_version)]), selector: Some(selector), publish_not_ready_addresses: Some(true), ..ServiceSpec::default() @@ -124,13 +115,28 @@ pub fn metrics_service_port(product_version: &str) -> ServicePort { } } -/// Returns the metrics rolegroup service name `---`. -pub fn rolegroup_metrics_service_name(role_group_ref_object_name: impl AsRef) -> String { - let role_group_ref_object_name = role_group_ref_object_name.as_ref(); - format!("{role_group_ref_object_name}-{METRICS_SERVICE_SUFFIX}") +/// Common labels for Prometheus +fn prometheus_labels() -> Labels { + Labels::try_from([("prometheus.io/scrape", "true")]).expect("should be a valid label") } -/// Returns the headless rolegroup service name `---`. -pub fn rolegroup_headless_service_name(role_group_ref_object_name: &str) -> String { - format!("{role_group_ref_object_name}-{HEADLESS_SERVICE_SUFFIX}") +/// Common annotations for Prometheus +/// +/// These annotations can be used in a ServiceMonitor. +/// +/// see also +fn prometheus_annotations(product_version: &str) -> Annotations { + let (path, port, scheme) = if product_version.starts_with("1.") { + ("/metrics", METRICS_PORT, "http") + } else { + ("/nifi-api/flow/metrics/prometheus", HTTPS_PORT, "https") + }; + + Annotations::try_from([ + ("prometheus.io/path".to_owned(), path.to_owned()), + ("prometheus.io/port".to_owned(), port.to_string()), + ("prometheus.io/scheme".to_owned(), scheme.to_owned()), + ("prometheus.io/scrape".to_owned(), "true".to_owned()), + ]) + .expect("should be valid annotations") }