@@ -2,13 +2,13 @@ use snafu::{ResultExt, Snafu};
22use stackable_operator:: {
33 builder:: meta:: ObjectMetaBuilder ,
44 commons:: product_image_selection:: ResolvedProductImage ,
5- k8s_openapi:: api:: core:: v1:: { Service , ServiceSpec } ,
6- kvp:: { Label , Labels } ,
5+ k8s_openapi:: api:: core:: v1:: { Service , ServicePort , ServiceSpec } ,
6+ kvp:: { Annotations , Labels } ,
77 role_utils:: RoleGroupRef ,
88} ;
99
1010use crate :: {
11- crd:: { APP_NAME , v1alpha1} ,
11+ crd:: { APP_NAME , METRICS_PORT , METRICS_PORT_NAME , security :: KafkaTlsSecurity , v1alpha1} ,
1212 kafka_controller:: KAFKA_CONTROLLER_NAME ,
1313 utils:: build_recommended_labels,
1414} ;
@@ -34,15 +34,16 @@ pub enum Error {
3434/// The rolegroup [`Service`] is a headless service that allows direct access to the instances of a certain rolegroup
3535///
3636/// This is mostly useful for internal communication between peers, or for clients that perform client-side load balancing.
37- pub fn build_rolegroup_service (
37+ pub fn build_rolegroup_headless_service (
3838 kafka : & v1alpha1:: KafkaCluster ,
3939 resolved_product_image : & ResolvedProductImage ,
4040 rolegroup : & RoleGroupRef < v1alpha1:: KafkaCluster > ,
41+ kafka_security : & KafkaTlsSecurity ,
4142) -> Result < Service , Error > {
4243 Ok ( Service {
4344 metadata : ObjectMetaBuilder :: new ( )
4445 . name_and_namespace ( kafka)
45- . name ( rolegroup . object_name ( ) )
46+ . name ( rolegroup_headless_service_name ( rolegroup ) )
4647 . ownerreference_from_resource ( kafka, None , Some ( true ) )
4748 . context ( ObjectMissingMetadataForOwnerRefSnafu ) ?
4849 . with_recommended_labels ( build_recommended_labels (
@@ -53,10 +54,10 @@ pub fn build_rolegroup_service(
5354 & rolegroup. role_group ,
5455 ) )
5556 . context ( MetadataBuildSnafu ) ?
56- . with_label ( Label :: try_from ( ( "prometheus.io/scrape" , "true" ) ) . context ( LabelBuildSnafu ) ?)
5757 . build ( ) ,
5858 spec : Some ( ServiceSpec {
5959 cluster_ip : Some ( "None" . to_string ( ) ) ,
60+ ports : Some ( headless_ports ( kafka_security) ) ,
6061 selector : Some (
6162 Labels :: role_group_selector (
6263 kafka,
@@ -73,3 +74,100 @@ pub fn build_rolegroup_service(
7374 status : None ,
7475 } )
7576}
77+
78+ /// The rolegroup metrics [`Service`] is a service that exposes metrics and a prometheus scraping label
79+ pub fn build_rolegroup_metrics_service (
80+ kafka : & v1alpha1:: KafkaCluster ,
81+ resolved_product_image : & ResolvedProductImage ,
82+ rolegroup : & RoleGroupRef < v1alpha1:: KafkaCluster > ,
83+ ) -> Result < Service , Error > {
84+ let metrics_service = Service {
85+ metadata : ObjectMetaBuilder :: new ( )
86+ . name_and_namespace ( kafka)
87+ // TODO: Use method on RoleGroupRef once op-rs is released
88+ . name ( rolegroup_metrics_service_name ( rolegroup) )
89+ . ownerreference_from_resource ( kafka, None , Some ( true ) )
90+ . context ( ObjectMissingMetadataForOwnerRefSnafu ) ?
91+ . with_recommended_labels ( build_recommended_labels (
92+ kafka,
93+ KAFKA_CONTROLLER_NAME ,
94+ & resolved_product_image. app_version_label_value ,
95+ & rolegroup. role ,
96+ & rolegroup. role_group ,
97+ ) )
98+ . context ( MetadataBuildSnafu ) ?
99+ . with_labels ( prometheus_labels ( ) )
100+ . with_annotations ( prometheus_annotations ( ) )
101+ . build ( ) ,
102+ spec : Some ( ServiceSpec {
103+ // Internal communication does not need to be exposed
104+ type_ : Some ( "ClusterIP" . to_string ( ) ) ,
105+ cluster_ip : Some ( "None" . to_string ( ) ) ,
106+ ports : Some ( metrics_ports ( ) ) ,
107+ selector : Some (
108+ Labels :: role_group_selector (
109+ kafka,
110+ APP_NAME ,
111+ & rolegroup. role ,
112+ & rolegroup. role_group ,
113+ )
114+ . context ( LabelBuildSnafu ) ?
115+ . into ( ) ,
116+ ) ,
117+ publish_not_ready_addresses : Some ( true ) ,
118+ ..ServiceSpec :: default ( )
119+ } ) ,
120+ status : None ,
121+ } ;
122+ Ok ( metrics_service)
123+ }
124+
125+ /// Headless service for cluster internal purposes only.
126+ // TODO: Move to operator-rs
127+ fn rolegroup_headless_service_name ( rolegroup : & RoleGroupRef < v1alpha1:: KafkaCluster > ) -> String {
128+ format ! ( "{name}-headless" , name = rolegroup. object_name( ) )
129+ }
130+
131+ /// Headless metrics service exposes Prometheus endpoint only
132+ // TODO: Move to operator-rs
133+ fn rolegroup_metrics_service_name ( rolegroup : & RoleGroupRef < v1alpha1:: KafkaCluster > ) -> String {
134+ format ! ( "{name}-metrics" , name = rolegroup. object_name( ) )
135+ }
136+
137+ fn metrics_ports ( ) -> Vec < ServicePort > {
138+ vec ! [ ServicePort {
139+ name: Some ( METRICS_PORT_NAME . to_string( ) ) ,
140+ port: METRICS_PORT . into( ) ,
141+ protocol: Some ( "TCP" . to_string( ) ) ,
142+ ..ServicePort :: default ( )
143+ } ]
144+ }
145+
146+ fn headless_ports ( kafka_security : & KafkaTlsSecurity ) -> Vec < ServicePort > {
147+ vec ! [ ServicePort {
148+ name: Some ( kafka_security. client_port_name( ) . into( ) ) ,
149+ port: kafka_security. client_port( ) . into( ) ,
150+ protocol: Some ( "TCP" . to_string( ) ) ,
151+ ..ServicePort :: default ( )
152+ } ]
153+ }
154+
155+ /// Common labels for Prometheus
156+ fn prometheus_labels ( ) -> Labels {
157+ Labels :: try_from ( [ ( "prometheus.io/scrape" , "true" ) ] ) . expect ( "should be a valid label" )
158+ }
159+
160+ /// Common annotations for Prometheus
161+ ///
162+ /// These annotations can be used in a ServiceMonitor.
163+ ///
164+ /// see also <https://github.com/prometheus-community/helm-charts/blob/prometheus-27.32.0/charts/prometheus/values.yaml#L983-L1036>
165+ fn prometheus_annotations ( ) -> Annotations {
166+ Annotations :: try_from ( [
167+ ( "prometheus.io/path" . to_owned ( ) , "/metrics" . to_owned ( ) ) ,
168+ ( "prometheus.io/port" . to_owned ( ) , METRICS_PORT . to_string ( ) ) ,
169+ ( "prometheus.io/scheme" . to_owned ( ) , "http" . to_owned ( ) ) ,
170+ ( "prometheus.io/scrape" . to_owned ( ) , "true" . to_owned ( ) ) ,
171+ ] )
172+ . expect ( "should be valid annotations" )
173+ }
0 commit comments