Skip to content

Commit e01bf3d

Browse files
authored
feat: Support configuring JVM arguments (#919)
* feat: Support configuring JVM arguments * changelog * Well I'm stupid... * cargo fmt
1 parent b84bcf4 commit e01bf3d

File tree

9 files changed

+292
-86
lines changed

9 files changed

+292
-86
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ All notable changes to this project will be documented in this file.
1010
config property `requestedSecretLifetime`. This helps reduce frequent Pod restarts ([#892]).
1111
- Run a `containerdebug` process in the background of each Zookeeper container to collect debugging information ([#881]).
1212
- Aggregate emitted Kubernetes events on the CustomResources ([#904]).
13+
- Support configuring JVM arguments ([#919]).
1314

1415
### Changed
1516

@@ -19,6 +20,7 @@ All notable changes to this project will be documented in this file.
1920
[#892]: https://github.com/stackabletech/zookeeper-operator/pull/892
2021
[#904]: https://github.com/stackabletech/zookeeper-operator/pull/904
2122
[#905]: https://github.com/stackabletech/zookeeper-operator/pull/905
23+
[#919]: https://github.com/stackabletech/zookeeper-operator/pull/919
2224

2325
## [24.11.1] - 2025-01-10
2426

deploy/helm/zookeeper-operator/crds/crds.yaml

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -411,6 +411,32 @@ spec:
411411
default: {}
412412
description: '`envOverrides` configure environment variables to be set in the Pods. It is a map from strings to strings - environment variables and the value to set. Read the [environment variable overrides documentation](https://docs.stackable.tech/home/nightly/concepts/overrides#env-overrides) for more information and consult the operator specific usage guide to find out about the product specific environment variables that are available.'
413413
type: object
414+
jvmArgumentOverrides:
415+
default:
416+
add: []
417+
remove: []
418+
removeRegex: []
419+
description: Allows overriding JVM arguments. Please read on the [JVM argument overrides documentation](https://docs.stackable.tech/home/nightly/concepts/overrides#jvm-argument-overrides) for details on the usage.
420+
properties:
421+
add:
422+
default: []
423+
description: JVM arguments to be added
424+
items:
425+
type: string
426+
type: array
427+
remove:
428+
default: []
429+
description: JVM arguments to be removed by exact match
430+
items:
431+
type: string
432+
type: array
433+
removeRegex:
434+
default: []
435+
description: JVM arguments matching any of this regexes will be removed
436+
items:
437+
type: string
438+
type: array
439+
type: object
414440
podOverrides:
415441
default: {}
416442
description: In the `podOverrides` property you can define a [PodTemplateSpec](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.27/#podtemplatespec-v1-core) to override any property that can be set on a Kubernetes Pod. Read the [Pod overrides documentation](https://docs.stackable.tech/home/nightly/concepts/overrides#pod-overrides) for more information.
@@ -698,6 +724,32 @@ spec:
698724
default: {}
699725
description: '`envOverrides` configure environment variables to be set in the Pods. It is a map from strings to strings - environment variables and the value to set. Read the [environment variable overrides documentation](https://docs.stackable.tech/home/nightly/concepts/overrides#env-overrides) for more information and consult the operator specific usage guide to find out about the product specific environment variables that are available.'
700726
type: object
727+
jvmArgumentOverrides:
728+
default:
729+
add: []
730+
remove: []
731+
removeRegex: []
732+
description: Allows overriding JVM arguments. Please read on the [JVM argument overrides documentation](https://docs.stackable.tech/home/nightly/concepts/overrides#jvm-argument-overrides) for details on the usage.
733+
properties:
734+
add:
735+
default: []
736+
description: JVM arguments to be added
737+
items:
738+
type: string
739+
type: array
740+
remove:
741+
default: []
742+
description: JVM arguments to be removed by exact match
743+
items:
744+
type: string
745+
type: array
746+
removeRegex:
747+
default: []
748+
description: JVM arguments matching any of this regexes will be removed
749+
items:
750+
type: string
751+
type: array
752+
type: object
701753
podOverrides:
702754
default: {}
703755
description: In the `podOverrides` property you can define a [PodTemplateSpec](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.27/#podtemplatespec-v1-core) to override any property that can be set on a Kubernetes Pod. Read the [Pod overrides documentation](https://docs.stackable.tech/home/nightly/concepts/overrides#pod-overrides) for more information.
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,3 +93,11 @@ servers:
9393

9494
The ZooKeeper operator also supports Pod overrides, allowing you to override any property that you can set on a Kubernetes Pod.
9595
Read the xref:concepts:overrides.adoc#pod-overrides[Pod overrides documentation] to learn more about this feature.
96+
97+
== JVM argument overrides
98+
99+
Stackable operators automatically determine the set of needed JVM arguments, such as memory settings or trust- and keystores.
100+
Using JVM argument overrides you can configure the JVM arguments xref:concepts:overrides.adoc#jvm-argument-overrides[according to the concepts page].
101+
102+
One thing that is different for Zookeeper, is that all heap-related arguments will be passed in via the env variable `ZK_SERVER_HEAP`, all the other ones via `SERVER_JVMFLAGS`.
103+
`ZK_SERVER_HEAP` can *not* have a unit suffix, it will always be an integer representing the number of megabytes heap available.

docs/modules/zookeeper/partials/nav.adoc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
** xref:zookeeper:usage_guide/log_aggregation.adoc[]
1313
** xref:zookeeper:usage_guide/using_multiple_role_groups.adoc[]
1414
** xref:zookeeper:usage_guide/isolating_clients_with_znodes.adoc[]
15-
** xref:zookeeper:usage_guide/configuration_environment_overrides.adoc[]
15+
** xref:zookeeper:usage_guide/overrides.adoc[]
1616
** xref:zookeeper:usage_guide/operations/index.adoc[]
1717
*** xref:zookeeper:usage_guide/operations/cluster-operations.adoc[]
1818
*** xref:zookeeper:usage_guide/operations/pod-placement.adoc[]
Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
use snafu::{OptionExt, ResultExt, Snafu};
2+
use stackable_operator::{
3+
memory::{BinaryMultiple, MemoryQuantity},
4+
role_utils::{self, GenericRoleConfig, JavaCommonConfig, JvmArgumentOverrides, Role},
5+
};
6+
7+
use crate::crd::{
8+
v1alpha1::{ZookeeperCluster, ZookeeperConfig, ZookeeperConfigFragment},
9+
LoggingFramework, JVM_SECURITY_PROPERTIES_FILE, LOG4J_CONFIG_FILE, LOGBACK_CONFIG_FILE,
10+
METRICS_PORT, STACKABLE_CONFIG_DIR, STACKABLE_LOG_CONFIG_DIR,
11+
};
12+
13+
const JAVA_HEAP_FACTOR: f32 = 0.8;
14+
15+
#[derive(Snafu, Debug)]
16+
pub enum Error {
17+
#[snafu(display("invalid memory resource configuration - missing default or value in crd?"))]
18+
MissingMemoryResourceConfig,
19+
20+
#[snafu(display("invalid memory config"))]
21+
InvalidMemoryConfig {
22+
source: stackable_operator::memory::Error,
23+
},
24+
25+
#[snafu(display("failed to merge jvm argument overrides"))]
26+
MergeJvmArgumentOverrides { source: role_utils::Error },
27+
}
28+
29+
/// All JVM arguments.
30+
fn construct_jvm_args(
31+
zk: &ZookeeperCluster,
32+
role: &Role<ZookeeperConfigFragment, GenericRoleConfig, JavaCommonConfig>,
33+
role_group: &str,
34+
) -> Result<Vec<String>, Error> {
35+
let logging_framework = zk.logging_framework();
36+
37+
let jvm_args = vec![
38+
format!("-Djava.security.properties={STACKABLE_CONFIG_DIR}/{JVM_SECURITY_PROPERTIES_FILE}"),
39+
format!("-javaagent:/stackable/jmx/jmx_prometheus_javaagent.jar={METRICS_PORT}:/stackable/jmx/server.yaml"),
40+
match logging_framework {
41+
LoggingFramework::LOG4J => format!("-Dlog4j.configuration=file:{STACKABLE_LOG_CONFIG_DIR}/{LOG4J_CONFIG_FILE}"),
42+
LoggingFramework::LOGBACK => format!("-Dlogback.configurationFile={STACKABLE_LOG_CONFIG_DIR}/{LOGBACK_CONFIG_FILE}"),
43+
},
44+
];
45+
46+
let operator_generated = JvmArgumentOverrides::new_with_only_additions(jvm_args);
47+
let merged = role
48+
.get_merged_jvm_argument_overrides(role_group, &operator_generated)
49+
.context(MergeJvmArgumentOverridesSnafu)?;
50+
Ok(merged
51+
.effective_jvm_config_after_merging()
52+
// Sorry for the clone, that's how operator-rs is currently modelled :P
53+
.clone())
54+
}
55+
56+
/// Arguments that go into `SERVER_JVMFLAGS`, so *not* the heap settings (which you can get using
57+
/// [`construct_zk_server_heap_env`]).
58+
pub fn construct_non_heap_jvm_args(
59+
zk: &ZookeeperCluster,
60+
role: &Role<ZookeeperConfigFragment, GenericRoleConfig, JavaCommonConfig>,
61+
role_group: &str,
62+
) -> Result<String, Error> {
63+
let mut jvm_args = construct_jvm_args(zk, role, role_group)?;
64+
jvm_args.retain(|arg| !is_heap_jvm_argument(arg));
65+
66+
Ok(jvm_args.join(" "))
67+
}
68+
69+
/// This will be put into `ZK_SERVER_HEAP`, which is just the heap size in megabytes (*without* the `m`
70+
/// unit prepended).
71+
pub fn construct_zk_server_heap_env(merged_config: &ZookeeperConfig) -> Result<String, Error> {
72+
let heap_size_in_mb = (MemoryQuantity::try_from(
73+
merged_config
74+
.resources
75+
.memory
76+
.limit
77+
.as_ref()
78+
.context(MissingMemoryResourceConfigSnafu)?,
79+
)
80+
.context(InvalidMemoryConfigSnafu)?
81+
* JAVA_HEAP_FACTOR)
82+
.scale_to(BinaryMultiple::Mebi);
83+
84+
Ok((heap_size_in_mb.value.floor() as u32).to_string())
85+
}
86+
87+
fn is_heap_jvm_argument(jvm_argument: &str) -> bool {
88+
let lowercase = jvm_argument.to_lowercase();
89+
90+
lowercase.starts_with("-xms") || lowercase.starts_with("-xmx")
91+
}
92+
93+
#[cfg(test)]
94+
mod tests {
95+
use super::*;
96+
use crate::crd::{v1alpha1::ZookeeperConfig, ZookeeperRole};
97+
98+
#[test]
99+
fn test_construct_jvm_arguments_defaults() {
100+
let input = r#"
101+
apiVersion: zookeeper.stackable.tech/v1alpha1
102+
kind: ZookeeperCluster
103+
metadata:
104+
name: simple-zookeeper
105+
spec:
106+
image:
107+
productVersion: "3.9.2"
108+
servers:
109+
roleGroups:
110+
default:
111+
replicas: 1
112+
"#;
113+
let (zookeeper, merged_config, role, rolegroup) = construct_boilerplate(input);
114+
let non_heap_jvm_args = construct_non_heap_jvm_args(&zookeeper, &role, &rolegroup).unwrap();
115+
let zk_server_heap_env = construct_zk_server_heap_env(&merged_config).unwrap();
116+
117+
assert_eq!(
118+
non_heap_jvm_args,
119+
"-Djava.security.properties=/stackable/config/security.properties \
120+
-javaagent:/stackable/jmx/jmx_prometheus_javaagent.jar=9505:/stackable/jmx/server.yaml \
121+
-Dlogback.configurationFile=/stackable/log_config/logback.xml"
122+
);
123+
assert_eq!(zk_server_heap_env, "409");
124+
}
125+
126+
#[test]
127+
fn test_construct_jvm_argument_overrides() {
128+
let input = r#"
129+
apiVersion: zookeeper.stackable.tech/v1alpha1
130+
kind: ZookeeperCluster
131+
metadata:
132+
name: simple-zookeeper
133+
spec:
134+
image:
135+
productVersion: "3.9.2"
136+
servers:
137+
config:
138+
resources:
139+
memory:
140+
limit: 42Gi
141+
jvmArgumentOverrides:
142+
add:
143+
- -Dhttps.proxyHost=proxy.my.corp
144+
- -Dhttps.proxyPort=8080
145+
- -Djava.net.preferIPv4Stack=true
146+
roleGroups:
147+
default:
148+
replicas: 1
149+
jvmArgumentOverrides:
150+
# We need more memory!
151+
removeRegex:
152+
- -Xmx.*
153+
- -Dhttps.proxyPort=.*
154+
add:
155+
- -Xmx40000m
156+
- -Dhttps.proxyPort=1234
157+
"#;
158+
let (zookeeper, merged_config, role, rolegroup) = construct_boilerplate(input);
159+
let non_heap_jvm_args = construct_non_heap_jvm_args(&zookeeper, &role, &rolegroup).unwrap();
160+
let zk_server_heap_env = construct_zk_server_heap_env(&merged_config).unwrap();
161+
162+
assert_eq!(
163+
non_heap_jvm_args,
164+
"-Djava.security.properties=/stackable/config/security.properties \
165+
-javaagent:/stackable/jmx/jmx_prometheus_javaagent.jar=9505:/stackable/jmx/server.yaml \
166+
-Dlogback.configurationFile=/stackable/log_config/logback.xml \
167+
-Dhttps.proxyHost=proxy.my.corp \
168+
-Djava.net.preferIPv4Stack=true \
169+
-Dhttps.proxyPort=1234"
170+
);
171+
assert_eq!(zk_server_heap_env, "34406");
172+
}
173+
174+
fn construct_boilerplate(
175+
zookeeper_cluster: &str,
176+
) -> (
177+
ZookeeperCluster,
178+
ZookeeperConfig,
179+
Role<ZookeeperConfigFragment, GenericRoleConfig, JavaCommonConfig>,
180+
String,
181+
) {
182+
let zookeeper: ZookeeperCluster =
183+
serde_yaml::from_str(zookeeper_cluster).expect("illegal test input");
184+
185+
let zookeeper_role = ZookeeperRole::Server;
186+
let rolegroup_ref = zookeeper.server_rolegroup_ref("default");
187+
let merged_config = zookeeper
188+
.merged_config(&zookeeper_role, &rolegroup_ref)
189+
.unwrap();
190+
let role = zookeeper.spec.servers.clone().unwrap();
191+
192+
(zookeeper, merged_config, role, "default".to_owned())
193+
}
194+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pub mod jvm;

0 commit comments

Comments
 (0)