Skip to content

Commit 0f1c9a9

Browse files
Listener: Use headless and metrics service (#613)
* updating according to decision * remove unused input * update test to also test for headless service * update function name and smoke test * Updating function name everywhere * Updating changelog * Moving services to own module, splitting up functions * Fixing rust docs * Update according to review Co-authored-by: Malte Sander <[email protected]> * Fix function import --------- Co-authored-by: Malte Sander <[email protected]>
1 parent fa9eaad commit 0f1c9a9

File tree

8 files changed

+183
-66
lines changed

8 files changed

+183
-66
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ All notable changes to this project will be documented in this file.
1111
- Use `--file-log-rotation-period` (or `FILE_LOG_ROTATION_PERIOD`) to configure the frequency of rotation.
1212
- Use `--console-log-format` (or `CONSOLE_LOG_FORMAT`) to set the format to `plain` (default) or `json`.
1313
- BREAKING: Add Listener support for Hive ([#605]).
14+
- Add internal headless service in addition to the metrics service ([#613]).
1415

1516
### Changed
1617

@@ -43,6 +44,7 @@ All notable changes to this project will be documented in this file.
4344
[#603]: https://github.com/stackabletech/hive-operator/pull/603
4445
[#604]: https://github.com/stackabletech/hive-operator/pull/604
4546
[#605]: https://github.com/stackabletech/hive-operator/pull/605
47+
[#613]: https://github.com/stackabletech/hive-operator/pull/613
4648

4749
## [25.3.0] - 2025-03-21
4850

rust/operator-binary/src/controller.rs

Lines changed: 30 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,8 @@ use stackable_operator::{
4343
api::{
4444
apps::v1::{StatefulSet, StatefulSetSpec},
4545
core::v1::{
46-
ConfigMap, ConfigMapVolumeSource, EmptyDirVolumeSource, Probe, Service,
47-
ServicePort, ServiceSpec, TCPSocketAction, Volume,
46+
ConfigMap, ConfigMapVolumeSource, EmptyDirVolumeSource, Probe, TCPSocketAction,
47+
Volume,
4848
},
4949
},
5050
apimachinery::pkg::{
@@ -56,7 +56,7 @@ use stackable_operator::{
5656
core::{DeserializeGuard, error_boundary},
5757
runtime::controller::Action,
5858
},
59-
kvp::{Label, Labels, ObjectLabels},
59+
kvp::{Labels, ObjectLabels},
6060
logging::controller::ReconcilerError,
6161
memory::{BinaryMultiple, MemoryQuantity},
6262
product_config_utils::{transform_all_roles_to_config, validate_all_roles_and_groups_config},
@@ -94,14 +94,18 @@ use crate::{
9494
STACKABLE_LOG_DIR_NAME,
9595
v1alpha1::{self, HiveMetastoreRoleConfig},
9696
},
97-
discovery::{self, build_headless_role_group_metrics_service_name},
97+
discovery::{self},
9898
kerberos::{
9999
self, add_kerberos_pod_config, kerberos_config_properties,
100100
kerberos_container_start_commands,
101101
},
102102
listener::{LISTENER_VOLUME_DIR, LISTENER_VOLUME_NAME, build_role_listener},
103103
operations::{graceful_shutdown::add_graceful_shutdown_config, pdb::add_pdbs},
104104
product_logging::extend_role_group_config_map,
105+
service::{
106+
build_rolegroup_headless_service, build_rolegroup_metrics_service,
107+
rolegroup_headless_service_name,
108+
},
105109
};
106110

107111
pub const HIVE_CONTROLLER_NAME: &str = "hivecluster";
@@ -345,6 +349,9 @@ pub enum Error {
345349
BuildListenerVolume {
346350
source: ListenerOperatorVolumeSourceBuilderError,
347351
},
352+
353+
#[snafu(display("faild to configure service"))]
354+
ServiceConfiguration { source: crate::service::Error },
348355
}
349356
type Result<T, E = Error> = std::result::Result<T, E>;
350357

@@ -456,7 +463,14 @@ pub async fn reconcile_hive(
456463
.merged_config(&HiveRole::MetaStore, &rolegroup)
457464
.context(FailedToResolveResourceConfigSnafu)?;
458465

459-
let rg_service = build_rolegroup_service(hive, &resolved_product_image, &rolegroup)?;
466+
let rg_metrics_service =
467+
build_rolegroup_metrics_service(hive, &resolved_product_image, &rolegroup)
468+
.context(ServiceConfigurationSnafu)?;
469+
470+
let rg_headless_service =
471+
build_rolegroup_headless_service(hive, &resolved_product_image, &rolegroup)
472+
.context(ServiceConfigurationSnafu)?;
473+
460474
let rg_configmap = build_metastore_rolegroup_config_map(
461475
hive,
462476
&hive_namespace,
@@ -479,7 +493,14 @@ pub async fn reconcile_hive(
479493
)?;
480494

481495
cluster_resources
482-
.add(client, rg_service)
496+
.add(client, rg_metrics_service)
497+
.await
498+
.context(ApplyRoleGroupServiceSnafu {
499+
rolegroup: rolegroup.clone(),
500+
})?;
501+
502+
cluster_resources
503+
.add(client, rg_headless_service)
483504
.await
484505
.context(ApplyRoleGroupServiceSnafu {
485506
rolegroup: rolegroup.clone(),
@@ -713,52 +734,10 @@ fn build_metastore_rolegroup_config_map(
713734
})
714735
}
715736

716-
/// The rolegroup [`Service`] is a headless service that allows direct access to the instances of a certain rolegroup
717-
///
718-
/// This is mostly useful for internal communication between peers, or for clients that perform client-side load balancing.
719-
fn build_rolegroup_service(
720-
hive: &v1alpha1::HiveCluster,
721-
resolved_product_image: &ResolvedProductImage,
722-
rolegroup: &RoleGroupRef<v1alpha1::HiveCluster>,
723-
) -> Result<Service> {
724-
Ok(Service {
725-
metadata: ObjectMetaBuilder::new()
726-
.name_and_namespace(hive)
727-
.name(build_headless_role_group_metrics_service_name(
728-
rolegroup.object_name(),
729-
))
730-
.ownerreference_from_resource(hive, None, Some(true))
731-
.context(ObjectMissingMetadataForOwnerRefSnafu)?
732-
.with_recommended_labels(build_recommended_labels(
733-
hive,
734-
&resolved_product_image.app_version_label,
735-
&rolegroup.role,
736-
&rolegroup.role_group,
737-
))
738-
.context(MetadataBuildSnafu)?
739-
.with_label(Label::try_from(("prometheus.io/scrape", "true")).context(LabelBuildSnafu)?)
740-
.build(),
741-
spec: Some(ServiceSpec {
742-
// Internal communication does not need to be exposed
743-
type_: Some("ClusterIP".to_string()),
744-
cluster_ip: Some("None".to_string()),
745-
ports: Some(service_ports()),
746-
selector: Some(
747-
Labels::role_group_selector(hive, APP_NAME, &rolegroup.role, &rolegroup.role_group)
748-
.context(LabelBuildSnafu)?
749-
.into(),
750-
),
751-
publish_not_ready_addresses: Some(true),
752-
..ServiceSpec::default()
753-
}),
754-
status: None,
755-
})
756-
}
757-
758737
/// The rolegroup [`StatefulSet`] runs the rolegroup, as configured by the administrator.
759738
///
760739
/// The [`Pod`](`stackable_operator::k8s_openapi::api::core::v1::Pod`)s are accessible through the
761-
/// corresponding [`Service`] (from [`build_rolegroup_service`]).
740+
/// corresponding [`Service`](`stackable_operator::k8s_openapi::api::core::v1::Service`) (via [`build_rolegroup_headless_service`] and metrics from [`build_rolegroup_metrics_service`]).
762741
#[allow(clippy::too_many_arguments)]
763742
fn build_metastore_rolegroup_statefulset(
764743
hive: &v1alpha1::HiveCluster,
@@ -1100,9 +1079,8 @@ fn build_metastore_rolegroup_statefulset(
11001079
),
11011080
..LabelSelector::default()
11021081
},
1103-
service_name: Some(build_headless_role_group_metrics_service_name(
1104-
rolegroup_ref.object_name(),
1105-
)),
1082+
// TODO: Use method on RoleGroupRef once op-rs is released
1083+
service_name: Some(rolegroup_headless_service_name(rolegroup_ref)),
11061084
template: pod_template,
11071085
volume_claim_templates: Some(vec![pvc]),
11081086
..StatefulSetSpec::default()
@@ -1123,15 +1101,6 @@ pub fn error_policy(
11231101
}
11241102
}
11251103

1126-
pub fn service_ports() -> Vec<ServicePort> {
1127-
vec![ServicePort {
1128-
name: Some(METRICS_PORT_NAME.to_string()),
1129-
port: METRICS_PORT.into(),
1130-
protocol: Some("TCP".to_string()),
1131-
..ServicePort::default()
1132-
}]
1133-
}
1134-
11351104
/// Creates recommended `ObjectLabels` to be used in deployed resources
11361105
pub fn build_recommended_labels<'a, T>(
11371106
owner: &'a T,

rust/operator-binary/src/discovery.rs

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,3 @@ fn build_discovery_configmap(
111111
obj_ref: ObjectRef::from_obj(hive),
112112
})
113113
}
114-
115-
pub fn build_headless_role_group_metrics_service_name(name: String) -> String {
116-
format!("{name}-metrics")
117-
}

rust/operator-binary/src/listener.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ pub fn build_role_listener(
104104
Ok(listener)
105105
}
106106

107-
fn listener_ports() -> Vec<ListenerPort> {
107+
pub fn listener_ports() -> Vec<ListenerPort> {
108108
vec![ListenerPort {
109109
name: HIVE_PORT_NAME.to_owned(),
110110
port: HIVE_PORT.into(),

rust/operator-binary/src/main.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ mod kerberos;
77
mod listener;
88
mod operations;
99
mod product_logging;
10+
mod service;
1011

1112
use std::sync::Arc;
1213

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
use snafu::{ResultExt, Snafu};
2+
use stackable_operator::{
3+
builder::meta::ObjectMetaBuilder,
4+
commons::product_image_selection::ResolvedProductImage,
5+
k8s_openapi::api::core::v1::{Service, ServicePort, ServiceSpec},
6+
kvp::{Label, Labels},
7+
role_utils::RoleGroupRef,
8+
};
9+
10+
use crate::{
11+
controller::build_recommended_labels,
12+
crd::{APP_NAME, HIVE_PORT, HIVE_PORT_NAME, METRICS_PORT, METRICS_PORT_NAME, v1alpha1},
13+
};
14+
15+
#[derive(Debug, Snafu)]
16+
pub enum Error {
17+
#[snafu(display("object is missing metadata to build owner reference"))]
18+
ObjectMissingMetadataForOwnerRef {
19+
source: stackable_operator::builder::meta::Error,
20+
},
21+
#[snafu(display("failed to build Metadata"))]
22+
MetadataBuild {
23+
source: stackable_operator::builder::meta::Error,
24+
},
25+
#[snafu(display("failed to build Labels"))]
26+
LabelBuild {
27+
source: stackable_operator::kvp::LabelError,
28+
},
29+
}
30+
31+
/// The rolegroup [`Service`] is a headless service that allows direct access to the instances of a certain rolegroup
32+
///
33+
/// This is mostly useful for internal communication between peers, or for clients that perform client-side load balancing.
34+
pub fn build_rolegroup_headless_service(
35+
hive: &v1alpha1::HiveCluster,
36+
resolved_product_image: &ResolvedProductImage,
37+
rolegroup: &RoleGroupRef<v1alpha1::HiveCluster>,
38+
) -> Result<Service, Error> {
39+
let headless_service = Service {
40+
metadata: ObjectMetaBuilder::new()
41+
.name_and_namespace(hive)
42+
// TODO: Use method on RoleGroupRef once op-rs is released
43+
.name(rolegroup_headless_service_name(rolegroup))
44+
.ownerreference_from_resource(hive, None, Some(true))
45+
.context(ObjectMissingMetadataForOwnerRefSnafu)?
46+
.with_recommended_labels(build_recommended_labels(
47+
hive,
48+
&resolved_product_image.app_version_label,
49+
&rolegroup.role,
50+
&rolegroup.role_group,
51+
))
52+
.context(MetadataBuildSnafu)?
53+
.build(),
54+
spec: Some(ServiceSpec {
55+
// Internal communication does not need to be exposed
56+
type_: Some("ClusterIP".to_string()),
57+
cluster_ip: Some("None".to_string()),
58+
// Expecting same ports as on listener service, just as a headless, internal service
59+
ports: Some(service_ports()),
60+
selector: Some(
61+
Labels::role_group_selector(hive, APP_NAME, &rolegroup.role, &rolegroup.role_group)
62+
.context(LabelBuildSnafu)?
63+
.into(),
64+
),
65+
publish_not_ready_addresses: Some(true),
66+
..ServiceSpec::default()
67+
}),
68+
status: None,
69+
};
70+
Ok(headless_service)
71+
}
72+
73+
/// The rolegroup metrics [`Service`] is a service that exposes metrics and a prometheus scraping label
74+
pub fn build_rolegroup_metrics_service(
75+
hive: &v1alpha1::HiveCluster,
76+
resolved_product_image: &ResolvedProductImage,
77+
rolegroup: &RoleGroupRef<v1alpha1::HiveCluster>,
78+
) -> Result<Service, Error> {
79+
let metrics_service = Service {
80+
metadata: ObjectMetaBuilder::new()
81+
.name_and_namespace(hive)
82+
// TODO: Use method on RoleGroupRef once op-rs is released
83+
.name(rolegroup_metrics_service_name(rolegroup))
84+
.ownerreference_from_resource(hive, None, Some(true))
85+
.context(ObjectMissingMetadataForOwnerRefSnafu)?
86+
.with_recommended_labels(build_recommended_labels(
87+
hive,
88+
&resolved_product_image.app_version_label,
89+
&rolegroup.role,
90+
&rolegroup.role_group,
91+
))
92+
.context(MetadataBuildSnafu)?
93+
.with_label(Label::try_from(("prometheus.io/scrape", "true")).context(LabelBuildSnafu)?)
94+
.build(),
95+
spec: Some(ServiceSpec {
96+
// Internal communication does not need to be exposed
97+
type_: Some("ClusterIP".to_string()),
98+
cluster_ip: Some("None".to_string()),
99+
ports: Some(metrics_ports()),
100+
selector: Some(
101+
Labels::role_group_selector(hive, APP_NAME, &rolegroup.role, &rolegroup.role_group)
102+
.context(LabelBuildSnafu)?
103+
.into(),
104+
),
105+
publish_not_ready_addresses: Some(true),
106+
..ServiceSpec::default()
107+
}),
108+
status: None,
109+
};
110+
Ok(metrics_service)
111+
}
112+
113+
/// Headless service for cluster internal purposes only.
114+
// TODO: Move to operator-rs
115+
pub fn rolegroup_headless_service_name(rolegroup: &RoleGroupRef<v1alpha1::HiveCluster>) -> String {
116+
format!("{name}-headless", name = rolegroup.object_name())
117+
}
118+
119+
/// Headless metrics service exposes Prometheus endpoint only
120+
// TODO: Move to operator-rs
121+
pub fn rolegroup_metrics_service_name(rolegroup: &RoleGroupRef<v1alpha1::HiveCluster>) -> String {
122+
format!("{name}-metrics", name = rolegroup.object_name())
123+
}
124+
125+
fn metrics_ports() -> Vec<ServicePort> {
126+
vec![ServicePort {
127+
name: Some(METRICS_PORT_NAME.to_string()),
128+
port: METRICS_PORT.into(),
129+
protocol: Some("TCP".to_string()),
130+
..ServicePort::default()
131+
}]
132+
}
133+
134+
fn service_ports() -> Vec<ServicePort> {
135+
vec![ServicePort {
136+
name: Some(HIVE_PORT_NAME.to_string()),
137+
port: HIVE_PORT.into(),
138+
protocol: Some("TCP".to_string()),
139+
..ServicePort::default()
140+
}]
141+
}

tests/templates/kuttl/external-access/20-assert.yaml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,3 +37,10 @@ metadata:
3737
name: test-hive-metastore-default-metrics
3838
spec:
3939
type: ClusterIP # exposed metrics
40+
---
41+
apiVersion: v1
42+
kind: Service
43+
metadata:
44+
name: test-hive-metastore-default-headless
45+
spec:
46+
type: ClusterIP # exposed metrics

tests/templates/kuttl/smoke/80-assert.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ apiVersion: kuttl.dev/v1beta1
33
kind: TestAssert
44
commands:
55
- script: kubectl exec -n "$NAMESPACE" test-metastore-0 -- python /tmp/test_metastore.py -m hive-metastore.$NAMESPACE.svc.cluster.local
6+
- script: kubectl exec -n "$NAMESPACE" test-metastore-0 -- python /tmp/test_metastore.py -m hive-metastore-default-headless.$NAMESPACE.svc.cluster.local

0 commit comments

Comments
 (0)