Skip to content
Open
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 @@ -12,6 +12,7 @@ All notable changes to this project will be documented in this file.
- Add `/ready` endpoint to the operator Deployment, which reports the CRD installation status ([#1090]).
- The logs of the `containerdebug` process are now collected by the Vector agent and forwarded to
the Vector aggregator ([#1106]).
- Add `containerdebug` config CRD ([#1108]).

### Changed

Expand Down Expand Up @@ -75,6 +76,7 @@ All notable changes to this project will be documented in this file.
[#1101]: https://github.com/stackabletech/zookeeper-operator/pull/1101
[#1102]: https://github.com/stackabletech/zookeeper-operator/pull/1102
[#1106]: https://github.com/stackabletech/zookeeper-operator/pull/1106
[#1108]: https://github.com/stackabletech/zookeeper-operator/pull/1108

## [26.7.0] - 2026-07-21

Expand Down
40 changes: 40 additions & 0 deletions extra/crds.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,26 @@ spec:
type: object
x-kubernetes-preserve-unknown-fields: true
type: object
containerdebug:
default:
enabled: null
interval: null
description: |-
Configures the `containerdebug` tool, which periodically logs information about the container
environment (such as network interfaces, DNS lookups and disk usage) in the background of the
product container.
properties:
enabled:
description: Whether `containerdebug` runs in the background of the product container. Defaults to `true`.
nullable: true
type: boolean
interval:
description: |-
Time between two runs of `containerdebug`, e.g. `30m`, `1h` or `2d`. Must not be zero.
Defaults to `30m`.
nullable: true
type: string
type: object
gracefulShutdownTimeout:
description: Time period Pods have to gracefully shut down, e.g. `30m`, `1h` or `2d`. Consult the operator documentation for details.
nullable: true
Expand Down Expand Up @@ -849,6 +869,26 @@ spec:
type: object
x-kubernetes-preserve-unknown-fields: true
type: object
containerdebug:
default:
enabled: null
interval: null
description: |-
Configures the `containerdebug` tool, which periodically logs information about the container
environment (such as network interfaces, DNS lookups and disk usage) in the background of the
product container.
properties:
enabled:
description: Whether `containerdebug` runs in the background of the product container. Defaults to `true`.
nullable: true
type: boolean
interval:
description: |-
Time between two runs of `containerdebug`, e.g. `30m`, `1h` or `2d`. Must not be zero.
Defaults to `30m`.
nullable: true
type: string
type: object
gracefulShutdownTimeout:
description: Time period Pods have to gracefully shut down, e.g. `30m`, `1h` or `2d`. Consult the operator documentation for details.
nullable: true
Expand Down
11 changes: 9 additions & 2 deletions rust/operator-binary/src/crd/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,10 @@ use stackable_operator::{
},
config::{fragment::Fragment, merge::Merge},
constant,
crd::ClusterRef,
crd::{
ClusterRef,
containerdebug::{default_containerdebug_config, v1alpha1::ContainerdebugConfig},
},
deep_merger::ObjectOverrides,
k8s_openapi::apimachinery::pkg::api::resource::Quantity,
kube::CustomResource,
Expand Down Expand Up @@ -203,7 +206,7 @@ pub mod versioned {
pub tls: Option<tls::v1alpha1::ZookeeperTls>,
}

#[derive(Clone, Debug, Default, Fragment, JsonSchema, PartialEq)]
#[derive(Clone, Debug, Fragment, JsonSchema, PartialEq)]
#[fragment_attrs(
derive(
Clone,
Expand Down Expand Up @@ -232,6 +235,9 @@ pub mod versioned {
#[fragment_attrs(serde(default))]
pub affinity: StackableAffinity,

#[fragment_attrs(serde(default))]
pub containerdebug: ContainerdebugConfig,

/// Time period Pods have to gracefully shut down, e.g. `30m`, `1h` or `2d`. Consult the operator documentation for details.
#[fragment_attrs(serde(default))]
pub graceful_shutdown_timeout: Option<Duration>,
Expand Down Expand Up @@ -433,6 +439,7 @@ impl v1alpha1::ZookeeperConfig {
},
logging: product_logging::spec::default_logging(),
affinity: get_affinity(cluster_name, role),
containerdebug: default_containerdebug_config(),
graceful_shutdown_timeout: Some(DEFAULT_SERVER_GRACEFUL_SHUTDOWN_TIMEOUT),
requested_secret_lifetime: Some(Self::DEFAULT_SECRET_LIFETIME),
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -292,13 +292,17 @@ pub fn build_server_rolegroup_statefulset(
{remove_vector_shutdown_file_command}
prepare_signal_handlers
mkdir --parents \"${file_log_directory}\" || true
containerdebug --output={STACKABLE_LOG_DIR}/containerdebug-state.json --loop &
{containerdebug_command}
bin/zkServer.sh start-foreground {STACKABLE_RW_CONFIG_DIR}/{zoo_cfg} &
wait_for_termination $!
{create_vector_shutdown_file_command}
",
zoo_cfg = ConfigFileName::ZooCfg,
file_log_directory = FILE_LOG_DIRECTORY.as_ref(),
containerdebug_command = merged_config
.containerdebug
.background_command(STACKABLE_LOG_DIR)
.unwrap_or_default(),
remove_vector_shutdown_file_command =
remove_vector_shutdown_file_command(STACKABLE_LOG_DIR),
create_vector_shutdown_file_command =
Expand Down Expand Up @@ -480,7 +484,11 @@ fn common_env_vars(merged_config: &ValidatedZookeeperConfig) -> EnvVarSet {

#[cfg(test)]
mod tests {
use stackable_operator::k8s_openapi::api::core::v1::{Container, EnvVar};
use stackable_operator::{
crd::containerdebug::ValidatedContainerdebugConfig,
k8s_openapi::api::core::v1::{Container, EnvVar},
shared::time::Duration,
};

use super::*;
use crate::zk_controller::test_support::{minimal_zk, validated_cluster};
Expand Down Expand Up @@ -661,4 +669,64 @@ mod tests {
);
assert_eq!(zoocfgdir[0].value.as_deref(), Some("/custom/conf/dir"));
}

/// Builds the `default` server StatefulSet with the given `containerdebug` config and returns
/// the `containerdebug` line of the zookeeper container's args, if any.
fn containerdebug_line(containerdebug: ValidatedContainerdebugConfig) -> Option<String> {
let validated = validated_cluster(&minimal_zk(
r#"
apiVersion: zookeeper.stackable.tech/v1alpha1
kind: ZookeeperCluster
metadata:
name: simple-zookeeper
spec:
image:
productVersion: "3.9.6"
servers:
roleGroups:
default:
replicas: 1
"#,
));
let rg_name = RoleGroupName::from_str("default").expect("valid role group name");
let mut rg = validated.role_group_configs[&ZookeeperRole::Server][&rg_name].clone();
rg.config.containerdebug = containerdebug;

build_server_rolegroup_statefulset(&validated, &ZookeeperRole::Server, &rg_name, &rg)
.expect("statefulset builds")
.spec
.and_then(|spec| spec.template.spec)
.expect("pod spec")
.containers
.into_iter()
.find(|container| container.name == ZOOKEEPER_CONTAINER_NAME.to_string())
.expect("the zookeeper container exists")
.args
.expect("the zookeeper container has args")
.join("\n")
.lines()
.map(str::trim)
.find(|line| line.starts_with("containerdebug "))
.map(str::to_owned)
}

/// The zookeeper container starts `containerdebug` from the role group's validated config. The
/// command itself is owned (and tested) by operator-rs, so only the wiring is checked here.
#[test]
fn zookeeper_container_starts_containerdebug_when_enabled() {
let containerdebug = ValidatedContainerdebugConfig::Enabled {
interval: Duration::from_minutes_unchecked(10),
};
let expected = containerdebug.background_command(STACKABLE_LOG_DIR);

assert_eq!(containerdebug_line(containerdebug), expected);
}

#[test]
fn zookeeper_container_does_not_start_containerdebug_when_disabled() {
assert_eq!(
containerdebug_line(ValidatedContainerdebugConfig::Disabled),
None
);
}
}
26 changes: 23 additions & 3 deletions rust/operator-binary/src/zk_controller/validate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ use stackable_operator::{
resources::{NoRuntimeLimits, Resources},
},
config::fragment,
crd::containerdebug::{self, ValidatedContainerdebugConfig},
deep_merger::ObjectOverrides,
k8s_openapi::apimachinery::pkg::apis::meta::v1::ObjectMeta,
kube::Resource,
Expand Down Expand Up @@ -80,6 +81,12 @@ pub enum Error {
role_group: String,
},

#[snafu(display("invalid containerdebug config for role group {role_group:?}"))]
InvalidContainerdebugConfig {
source: containerdebug::Error,
role_group: String,
},

#[snafu(display("failed to get the cluster name"))]
GetClusterName {
source: stackable_operator::v2::controller_utils::Error,
Expand Down Expand Up @@ -123,14 +130,19 @@ pub struct ValidatedZookeeperConfig {
pub resources: Resources<v1alpha1::ZookeeperStorageConfig, NoRuntimeLimits>,
pub logging: ValidatedLogging,
pub affinity: StackableAffinity,
pub containerdebug: ValidatedContainerdebugConfig,
pub graceful_shutdown_timeout: Option<Duration>,
pub requested_secret_lifetime: Option<Duration>,
}

impl ValidatedZookeeperConfig {
/// Builds the validated config from the merged [`ZookeeperConfig`], swapping in the
/// already-validated logging.
fn from_merged(merged: ZookeeperConfig, logging: ValidatedLogging) -> Self {
/// already-validated `containerdebug` and logging configuration.
fn from_merged(
merged: ZookeeperConfig,
containerdebug: ValidatedContainerdebugConfig,
logging: ValidatedLogging,
) -> Self {
Self {
init_limit: merged.init_limit,
sync_limit: merged.sync_limit,
Expand All @@ -139,6 +151,7 @@ impl ValidatedZookeeperConfig {
resources: merged.resources,
logging,
affinity: merged.affinity,
containerdebug,
graceful_shutdown_timeout: merged.graceful_shutdown_timeout,
requested_secret_lifetime: merged.requested_secret_lifetime,
}
Expand Down Expand Up @@ -493,11 +506,18 @@ fn validate_role_group_config(
})?;

let config = merged.config.config;
let containerdebug =
config
.containerdebug
.validate()
.with_context(|_| InvalidContainerdebugConfigSnafu {
role_group: role_group_name.to_owned(),
})?;
let logging = validate_logging(&config.logging, vector_aggregator_config_map_name)?;

Ok(ZookeeperRoleGroupConfig {
replicas: merged.replicas,
config: ValidatedZookeeperConfig::from_merged(config, logging),
config: ValidatedZookeeperConfig::from_merged(config, containerdebug, logging),
config_overrides: merged.config.config_overrides,
// The env override names are validated on deserialization.
env_overrides: merged.config.env_overrides.into(),
Expand Down
Loading