Skip to content

feat!: Support configuring JVM arguments #572

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

Merged
merged 11 commits into from
Feb 20, 2025
Merged
Show file tree
Hide file tree
Changes from 3 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 @@ -8,6 +8,7 @@ All notable changes to this project will be documented in this file.

- Run a `containerdebug` process in the background of each Hive container to collect debugging information ([#554]).
- Aggregate emitted Kubernetes events on the CustomResources ([#560]).
- Support configuring JVM arguments ([#572]).

### Changed

Expand All @@ -16,6 +17,7 @@ All notable changes to this project will be documented in this file.
[#554]: https://github.com/stackabletech/hive-operator/pull/554
[#560]: https://github.com/stackabletech/hive-operator/pull/560
[#561]: https://github.com/stackabletech/hive-operator/pull/561
[#572]: https://github.com/stackabletech/hive-operator/pull/572

## [24.11.1] - 2025-01-10

Expand Down
52 changes: 52 additions & 0 deletions deploy/helm/hive-operator/crds/crds.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -507,6 +507,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 @@ -775,6 +801,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 @@ -101,3 +101,10 @@ metastore:

The Hive 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 Hive metastores, is that all heap-related arguments will be passed in via the env variable `HADOOP_HEAPSIZE`, all the other ones via `HADOOP_OPTS`.
2 changes: 1 addition & 1 deletion docs/modules/hive/partials/nav.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
** xref:hive:usage-guide/monitoring.adoc[]
** xref:hive:usage-guide/resources.adoc[]
** xref:hive:usage-guide/security.adoc[]
** xref:hive:usage-guide/configuration-environment-overrides.adoc[]
** xref:hive:usage-guide/overrides.adoc[]
** xref:hive:usage-guide/operations/index.adoc[]
*** xref:hive:usage-guide/operations/cluster-operations.adoc[]
*** xref:hive:usage-guide/operations/pod-placement.adoc[]
Expand Down
225 changes: 225 additions & 0 deletions rust/operator-binary/src/config/jvm.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
use snafu::{OptionExt, ResultExt, Snafu};
use stackable_operator::{
memory::{BinaryMultiple, MemoryQuantity},
role_utils::{self, GenericRoleConfig, JavaCommonConfig, JvmArgumentOverrides, Role},
};

use crate::crd::{
v1alpha1::HiveCluster, MetaStoreConfig, MetaStoreConfigFragment, JVM_SECURITY_PROPERTIES_FILE,
METRICS_PORT, STACKABLE_CONFIG_DIR, STACKABLE_TRUST_STORE, STACKABLE_TRUST_STORE_PASSWORD,
};

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(
hive: &HiveCluster,
merged_config: &MetaStoreConfig,
role: &Role<MetaStoreConfigFragment, GenericRoleConfig, JavaCommonConfig>,
role_group: &str,
) -> Result<Vec<String>, Error> {
let heap_size = MemoryQuantity::try_from(
merged_config
.resources
.memory
.limit
.as_ref()
.context(MissingMemoryResourceConfigSnafu)?,
)
.context(InvalidMemoryConfigSnafu)?
.scale_to(BinaryMultiple::Mebi)
* JAVA_HEAP_FACTOR;
let java_heap = heap_size
.format_for_java()
.context(InvalidMemoryConfigSnafu)?;

let mut jvm_args = vec![
// Heap settings
format!("-Xmx{java_heap}"),
format!("-Xms{java_heap}"),
format!("-Djava.security.properties={STACKABLE_CONFIG_DIR}/{JVM_SECURITY_PROPERTIES_FILE}"),
format!("-javaagent:/stackable/jmx/jmx_prometheus_javaagent.jar={METRICS_PORT}:/stackable/jmx/jmx_hive_config.yaml"),
format!("-Djavax.net.ssl.trustStore={STACKABLE_TRUST_STORE}"),
format!("-Djavax.net.ssl.trustStorePassword={STACKABLE_TRUST_STORE_PASSWORD}"),
format!("-Djavax.net.ssl.trustStoreType=pkcs12"),
];

if hive.has_kerberos_enabled() {
jvm_args.push("-Djava.security.krb5.conf=/stackable/kerberos/krb5.conf".to_owned());
}

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 `HADOOP_OPTS`, so *not* the heap settings (which you can get using
/// [`construct_heap_jvm_args`]).
pub fn construct_non_heap_jvm_args(
hive: &HiveCluster,
merged_config: &MetaStoreConfig,
role: &Role<MetaStoreConfigFragment, GenericRoleConfig, JavaCommonConfig>,
role_group: &str,
) -> Result<String, Error> {
let mut jvm_args = construct_jvm_args(hive, merged_config, role, role_group)?;
jvm_args.retain(|arg| !is_heap_jvm_argument(arg));

Ok(jvm_args.join(" "))
}

/// Arguments that go into `HADOOP_HEAPSIZE`.
/// You can get the normal JVM arguments using [`construct_non_heap_jvm_args`].
pub fn construct_heap_jvm_args(
hive: &HiveCluster,
merged_config: &MetaStoreConfig,
role: &Role<MetaStoreConfigFragment, GenericRoleConfig, JavaCommonConfig>,
role_group: &str,
) -> Result<String, Error> {
let mut jvm_args = construct_jvm_args(hive, merged_config, role, role_group)?;
jvm_args.retain(|arg| is_heap_jvm_argument(arg));

Ok(jvm_args.join(" "))
}

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::HiveRole;

#[test]
fn test_construct_jvm_arguments_defaults() {
let input = r#"
apiVersion: hive.stackable.tech/v1alpha1
kind: HiveCluster
metadata:
name: simple-hive
spec:
image:
productVersion: 4.0.0
clusterConfig:
database:
connString: jdbc:derby:;databaseName=/tmp/hive;create=true
dbType: derby
credentialsSecret: mySecret
metastore:
roleGroups:
default:
replicas: 1
"#;
let (hive, hive_role, role, merged_config) = construct_boilerplate(input);
let non_heap_jvm_args =
construct_non_heap_jvm_args(&hive, &hive_role, &role, &merged_config).unwrap();
let heap_jvm_args =
construct_heap_jvm_args(&hive, &hive_role, &role, &merged_config).unwrap();

assert_eq!(
non_heap_jvm_args,
"-Djava.security.properties=/stackable/config/security.properties \
-javaagent:/stackable/jmx/jmx_prometheus_javaagent.jar=9084:/stackable/jmx/jmx_hive_config.yaml \
-Djavax.net.ssl.trustStore=/stackable/truststore.p12 \
-Djavax.net.ssl.trustStorePassword=changeit \
-Djavax.net.ssl.trustStoreType=pkcs12"
);
assert_eq!(heap_jvm_args, "-Xmx409m -Xms409m");
}

#[test]
fn test_construct_jvm_argument_overrides() {
let input = r#"
apiVersion: hive.stackable.tech/v1alpha1
kind: HiveCluster
metadata:
name: simple-hive
spec:
image:
productVersion: 4.0.0
clusterConfig:
database:
connString: jdbc:derby:;databaseName=/tmp/hive;create=true
dbType: derby
credentialsSecret: mySecret
metastore:
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 (hive, merged_config, role, role_group) = construct_boilerplate(input);
let non_heap_jvm_args =
construct_non_heap_jvm_args(&hive, &merged_config, &role, &role_group).unwrap();
let heap_jvm_args =
construct_heap_jvm_args(&hive, &merged_config, &role, &role_group).unwrap();

assert_eq!(
non_heap_jvm_args,
"-Djava.security.properties=/stackable/config/security.properties \
-javaagent:/stackable/jmx/jmx_prometheus_javaagent.jar=9084:/stackable/jmx/jmx_hive_config.yaml \
-Djavax.net.ssl.trustStore=/stackable/truststore.p12 \
-Djavax.net.ssl.trustStorePassword=changeit \
-Djavax.net.ssl.trustStoreType=pkcs12 \
-Dhttps.proxyHost=proxy.my.corp \
-Djava.net.preferIPv4Stack=true \
-Dhttps.proxyPort=1234"
);
assert_eq!(heap_jvm_args, "-Xms34406m -Xmx40000m");
}

fn construct_boilerplate(
hive_cluster: &str,
) -> (
HiveCluster,
MetaStoreConfig,
Role<MetaStoreConfigFragment, GenericRoleConfig, JavaCommonConfig>,
String,
) {
let hive: HiveCluster = serde_yaml::from_str(hive_cluster).expect("illegal test input");

let hive_role = HiveRole::MetaStore;
let rolegroup_ref = hive.metastore_rolegroup_ref("default");
let merged_config = hive.merged_config(&hive_role, &rolegroup_ref).unwrap();
let role = hive.spec.metastore.clone().unwrap();

(hive, 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
Loading