Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Support configuring JVM arguments #919

Merged
merged 4 commits into from
Feb 27, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ All notable changes to this project will be documented in this file.
config property `requestedSecretLifetime`. This helps reduce frequent Pod restarts ([#892]).
- Run a `containerdebug` process in the background of each Zookeeper container to collect debugging information ([#881]).
- Aggregate emitted Kubernetes events on the CustomResources ([#904]).
- Support configuring JVM arguments ([#919]).

### Changed

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

## [24.11.1] - 2025-01-10

Expand Down
52 changes: 52 additions & 0 deletions deploy/helm/zookeeper-operator/crds/crds.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,32 @@ spec:
default: {}
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.'
type: object
jvmArgumentOverrides:
default:
add: []
remove: []
removeRegex: []
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.
properties:
add:
default: []
description: JVM arguments to be added
items:
type: string
type: array
remove:
default: []
description: JVM arguments to be removed by exact match
items:
type: string
type: array
removeRegex:
default: []
description: JVM arguments matching any of this regexes will be removed
items:
type: string
type: array
type: object
podOverrides:
default: {}
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.
Expand Down Expand Up @@ -698,6 +724,32 @@ spec:
default: {}
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.'
type: object
jvmArgumentOverrides:
default:
add: []
remove: []
removeRegex: []
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.
properties:
add:
default: []
description: JVM arguments to be added
items:
type: string
type: array
remove:
default: []
description: JVM arguments to be removed by exact match
items:
type: string
type: array
removeRegex:
default: []
description: JVM arguments matching any of this regexes will be removed
items:
type: string
type: array
type: object
podOverrides:
default: {}
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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,3 +93,11 @@ servers:

The ZooKeeper operator also supports Pod overrides, allowing you to override any property that you can set on a Kubernetes Pod.
Read the xref:concepts:overrides.adoc#pod-overrides[Pod overrides documentation] to learn more about this feature.

== JVM argument overrides

Stackable operators automatically determine the set of needed JVM arguments, such as memory settings or trust- and keystores.
Using JVM argument overrides you can configure the JVM arguments xref:concepts:overrides.adoc#jvm-argument-overrides[according to the concepts page].

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`.
`ZK_SERVER_HEAP` can *not* have a unit suffix, it will always be an integer representing the number of megabytes heap available.
2 changes: 1 addition & 1 deletion docs/modules/zookeeper/partials/nav.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
** xref:zookeeper:usage_guide/log_aggregation.adoc[]
** xref:zookeeper:usage_guide/using_multiple_role_groups.adoc[]
** xref:zookeeper:usage_guide/isolating_clients_with_znodes.adoc[]
** xref:zookeeper:usage_guide/configuration_environment_overrides.adoc[]
** xref:zookeeper:usage_guide/overrides.adoc[]
** xref:zookeeper:usage_guide/operations/index.adoc[]
*** xref:zookeeper:usage_guide/operations/cluster-operations.adoc[]
*** xref:zookeeper:usage_guide/operations/pod-placement.adoc[]
Expand Down
194 changes: 194 additions & 0 deletions rust/operator-binary/src/config/jvm.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
use snafu::{OptionExt, ResultExt, Snafu};
use stackable_operator::{
memory::{BinaryMultiple, MemoryQuantity},
role_utils::{self, GenericRoleConfig, JavaCommonConfig, JvmArgumentOverrides, Role},
};

use crate::crd::{
v1alpha1::{ZookeeperCluster, ZookeeperConfig, ZookeeperConfigFragment},
LoggingFramework, JVM_SECURITY_PROPERTIES_FILE, LOG4J_CONFIG_FILE, LOGBACK_CONFIG_FILE,
METRICS_PORT, STACKABLE_CONFIG_DIR, STACKABLE_LOG_CONFIG_DIR,
};

const JAVA_HEAP_FACTOR: f32 = 0.8;

#[derive(Snafu, Debug)]
pub enum Error {
#[snafu(display("invalid memory resource configuration - missing default or value in crd?"))]
MissingMemoryResourceConfig,

#[snafu(display("invalid memory config"))]
InvalidMemoryConfig {
source: stackable_operator::memory::Error,
},

#[snafu(display("failed to merge jvm argument overrides"))]
MergeJvmArgumentOverrides { source: role_utils::Error },
}

/// All JVM arguments.
fn construct_jvm_args(
zk: &ZookeeperCluster,
role: &Role<ZookeeperConfigFragment, GenericRoleConfig, JavaCommonConfig>,
role_group: &str,
) -> Result<Vec<String>, Error> {
let logging_framework = zk.logging_framework();

let jvm_args = vec![
format!("-Djava.security.properties={STACKABLE_CONFIG_DIR}/{JVM_SECURITY_PROPERTIES_FILE}"),
format!("-javaagent:/stackable/jmx/jmx_prometheus_javaagent.jar={METRICS_PORT}:/stackable/jmx/server.yaml"),
match logging_framework {
LoggingFramework::LOG4J => format!("-Dlog4j.configuration=file:{STACKABLE_LOG_CONFIG_DIR}/{LOG4J_CONFIG_FILE}"),
LoggingFramework::LOGBACK => format!("-Dlogback.configurationFile={STACKABLE_LOG_CONFIG_DIR}/{LOGBACK_CONFIG_FILE}"),
},
];

let operator_generated = JvmArgumentOverrides::new_with_only_additions(jvm_args);
let merged = role
.get_merged_jvm_argument_overrides(role_group, &operator_generated)
.context(MergeJvmArgumentOverridesSnafu)?;
Ok(merged
.effective_jvm_config_after_merging()
// Sorry for the clone, that's how operator-rs is currently modelled :P
.clone())
}

/// Arguments that go into `SERVER_JVMFLAGS`, so *not* the heap settings (which you can get using
/// [`construct_zk_server_heap_env`]).
pub fn construct_non_heap_jvm_args(
zk: &ZookeeperCluster,
role: &Role<ZookeeperConfigFragment, GenericRoleConfig, JavaCommonConfig>,
role_group: &str,
) -> Result<String, Error> {
let mut jvm_args = construct_jvm_args(zk, role, role_group)?;
jvm_args.retain(|arg| !is_heap_jvm_argument(arg));

Ok(jvm_args.join(" "))
}

/// This will be put into `ZK_SERVER_HEAP`, which is just the heap size in megabytes (*without* the `m`
/// unit prepended).
pub fn construct_zk_server_heap_env(merged_config: &ZookeeperConfig) -> Result<String, Error> {
let heap_size_in_mb = (MemoryQuantity::try_from(
merged_config
.resources
.memory
.limit
.as_ref()
.context(MissingMemoryResourceConfigSnafu)?,
)
.context(InvalidMemoryConfigSnafu)?
* JAVA_HEAP_FACTOR)
.scale_to(BinaryMultiple::Mebi);

Ok((heap_size_in_mb.value.floor() as u32).to_string())
}

fn is_heap_jvm_argument(jvm_argument: &str) -> bool {
let lowercase = jvm_argument.to_lowercase();

lowercase.starts_with("-xms") || lowercase.starts_with("-xmx")
}

#[cfg(test)]
mod tests {
use super::*;
use crate::crd::{v1alpha1::ZookeeperConfig, ZookeeperRole};

#[test]
fn test_construct_jvm_arguments_defaults() {
let input = r#"
apiVersion: zookeeper.stackable.tech/v1alpha1
kind: ZookeeperCluster
metadata:
name: simple-zookeeper
spec:
image:
productVersion: "3.9.2"
servers:
roleGroups:
default:
replicas: 1
"#;
let (zookeeper, merged_config, role, rolegroup) = construct_boilerplate(input);
let non_heap_jvm_args = construct_non_heap_jvm_args(&zookeeper, &role, &rolegroup).unwrap();
let zk_server_heap_env = construct_zk_server_heap_env(&merged_config).unwrap();

assert_eq!(
non_heap_jvm_args,
"-Djava.security.properties=/stackable/config/security.properties \
-javaagent:/stackable/jmx/jmx_prometheus_javaagent.jar=9505:/stackable/jmx/server.yaml \
-Dlogback.configurationFile=/stackable/log_config/logback.xml"
);
assert_eq!(zk_server_heap_env, "409");
}

#[test]
fn test_construct_jvm_argument_overrides() {
let input = r#"
apiVersion: zookeeper.stackable.tech/v1alpha1
kind: ZookeeperCluster
metadata:
name: simple-zookeeper
spec:
image:
productVersion: "3.9.2"
servers:
config:
resources:
memory:
limit: 42Gi
jvmArgumentOverrides:
add:
- -Dhttps.proxyHost=proxy.my.corp
- -Dhttps.proxyPort=8080
- -Djava.net.preferIPv4Stack=true
roleGroups:
default:
replicas: 1
jvmArgumentOverrides:
# We need more memory!
removeRegex:
- -Xmx.*
- -Dhttps.proxyPort=.*
add:
- -Xmx40000m
- -Dhttps.proxyPort=1234
"#;
let (zookeeper, merged_config, role, rolegroup) = construct_boilerplate(input);
let non_heap_jvm_args = construct_non_heap_jvm_args(&zookeeper, &role, &rolegroup).unwrap();
let zk_server_heap_env = construct_zk_server_heap_env(&merged_config).unwrap();

assert_eq!(
non_heap_jvm_args,
"-Djava.security.properties=/stackable/config/security.properties \
-javaagent:/stackable/jmx/jmx_prometheus_javaagent.jar=9505:/stackable/jmx/server.yaml \
-Dlogback.configurationFile=/stackable/log_config/logback.xml \
-Dhttps.proxyHost=proxy.my.corp \
-Djava.net.preferIPv4Stack=true \
-Dhttps.proxyPort=1234"
);
assert_eq!(zk_server_heap_env, "34406");
}

fn construct_boilerplate(
zookeeper_cluster: &str,
) -> (
ZookeeperCluster,
ZookeeperConfig,
Role<ZookeeperConfigFragment, GenericRoleConfig, JavaCommonConfig>,
String,
) {
let zookeeper: ZookeeperCluster =
serde_yaml::from_str(zookeeper_cluster).expect("illegal test input");

let zookeeper_role = ZookeeperRole::Server;
let rolegroup_ref = zookeeper.server_rolegroup_ref("default");
let merged_config = zookeeper
.merged_config(&zookeeper_role, &rolegroup_ref)
.unwrap();
let role = zookeeper.spec.servers.clone().unwrap();

(zookeeper, merged_config, role, "default".to_owned())
}
}
1 change: 1 addition & 0 deletions rust/operator-binary/src/config/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub mod jvm;
Loading