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 @@ -24,6 +24,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 ([#894]).
- The logs of the `containerdebug` process are now collected by the Vector agent and forwarded to
the Vector aggregator ([#912]).
- Add `containerdebug` config CRD ([#914]).

### Changed

Expand Down Expand Up @@ -133,6 +134,7 @@ All notable changes to this project will be documented in this file.
[#908]: https://github.com/stackabletech/opa-operator/pull/908
[#909]: https://github.com/stackabletech/opa-operator/pull/909
[#912]: https://github.com/stackabletech/opa-operator/pull/912
[#914]: https://github.com/stackabletech/opa-operator/pull/914

## [26.7.0] - 2026-07-21

Expand Down
80 changes: 80 additions & 0 deletions extra/crds.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -875,6 +875,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 @@ -1622,6 +1642,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 @@ -3217,6 +3257,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 @@ -3964,6 +4024,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
Original file line number Diff line number Diff line change
Expand Up @@ -674,7 +674,7 @@ fn build_opa_start_command(
{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}
opa run -s -a 0.0.0.0:{bind_port} -c {CONFIG_DIR}/{config_file} -l {opa_log_level} --shutdown-grace-period {shutdown_grace_period_s} --disable-telemetry {tls_flags} {extra_cli_args} {logging_redirects} &
wait_for_termination $!
{create_vector_shutdown_file_command}
Expand All @@ -684,6 +684,10 @@ fn build_opa_start_command(
create_vector_shutdown_file_command =
create_vector_shutdown_file_command(STACKABLE_LOG_DIR),
file_log_directory = FILE_LOG_DIRECTORY.as_ref(),
containerdebug_command = merged_config
.containerdebug
.background_command(STACKABLE_LOG_DIR)
.unwrap_or_default(),
shutdown_grace_period_s = merged_config.graceful_shutdown_timeout.unwrap_or(DEFAULT_SERVER_GRACEFUL_SHUTDOWN_TIMEOUT).as_secs(),
opa_log_level = [console_log_level, file_log_level].iter().min().unwrap_or(&LogLevel::INFO).to_opa_literal(),
extra_cli_args = extra_cli_args
Expand Down Expand Up @@ -789,3 +793,77 @@ fn build_prepare_start_command(

prepare_container_args
}

#[cfg(test)]
mod tests {
use serde_json::json;
use stackable_opa_operator::crd::OpaRole;
use stackable_operator::{
commons::networking::DomainName, crd::containerdebug::ValidatedContainerdebugConfig,
shared::time::Duration,
};

use super::*;
use crate::controller::build::properties::test_support::validated_cluster_from_spec;

/// Builds the `default` server Pod template with the given `containerdebug` config and returns
/// the `containerdebug` line of the opa container's args, if any.
fn containerdebug_line(containerdebug: ValidatedContainerdebugConfig) -> Option<String> {
let cluster = validated_cluster_from_spec(json!({
"image": { "productVersion": "1.2.3" },
"servers": { "roleGroups": { "default": {} } },
}));
let (role_group_name, role_group) = cluster.role_group_configs[&OpaRole::Server]
.iter()
.next()
.expect("the default role group should exist");
let mut role_group = role_group.clone();
role_group.config.containerdebug = containerdebug;

build_server_rolegroup_pod_template(
&cluster,
role_group_name,
&role_group,
"bundle-builder-image",
"user-info-fetcher-image",
"resource-info-fetcher-image",
&KubernetesClusterInfo {
cluster_domain: DomainName::try_from("cluster.local").unwrap(),
},
)
.expect("the Pod template should build")
.spec
.expect("the Pod template has a spec")
.containers
.into_iter()
.find(|container| container.name == Container::Opa.name().to_string())
.expect("the opa container exists")
.args
.expect("the opa container has args")
.join("\n")
.lines()
.map(str::trim)
.find(|line| line.starts_with("containerdebug "))
.map(str::to_owned)
}

/// The opa 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 opa_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 opa_container_does_not_start_containerdebug_when_disabled() {
assert_eq!(
containerdebug_line(ValidatedContainerdebugConfig::Disabled),
None
);
}
}
9 changes: 8 additions & 1 deletion rust/operator-binary/src/controller/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ use stackable_operator::{
product_image_selection::ResolvedProductImage,
resources::{NoRuntimeLimits, Resources},
},
crd::containerdebug::ValidatedContainerdebugConfig,
k8s_openapi::api::{
apps::v1::{DaemonSet, Deployment},
core::v1::{ConfigMap, Service, ServiceAccount},
Expand Down Expand Up @@ -241,15 +242,21 @@ pub struct ValidatedOpaConfig {
pub resources: Resources<OpaStorageConfig, NoRuntimeLimits>,
pub logging: validate::ValidatedLogging,
pub affinity: StackableAffinity,
pub containerdebug: ValidatedContainerdebugConfig,
pub graceful_shutdown_timeout: Option<Duration>,
}

impl ValidatedOpaConfig {
pub(crate) fn from_merged(merged: OpaConfig, logging: validate::ValidatedLogging) -> Self {
pub(crate) fn from_merged(
merged: OpaConfig,
containerdebug: ValidatedContainerdebugConfig,
logging: validate::ValidatedLogging,
) -> Self {
Self {
resources: merged.resources,
logging,
affinity: merged.affinity,
containerdebug,
graceful_shutdown_timeout: merged.graceful_shutdown_timeout,
}
}
Expand Down
19 changes: 18 additions & 1 deletion rust/operator-binary/src/controller/validate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use stackable_opa_operator::crd::{Container, OpaConfig, OpaRole, OpaRoleType, v1
use stackable_operator::{
cli::OperatorEnvironmentOptions,
commons::product_image_selection,
crd::containerdebug,
product_logging::spec::Logging,
v2::{
controller_utils::{get_cluster_name, get_namespace, get_uid},
Expand Down Expand Up @@ -54,6 +55,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("the role group name {role_group:?} is invalid"))]
ParseRoleGroupName {
source: <RoleGroupName as FromStr>::Err,
Expand Down Expand Up @@ -234,6 +241,12 @@ pub fn validate(
role_group: role_group_name.clone(),
})?;

let containerdebug = merged.config.config.containerdebug.validate().context(
InvalidContainerdebugConfigSnafu {
role_group: role_group_name.clone(),
},
)?;

// Validate the logging configuration up-front (borrows the merged config before it is
// moved into the `OpaRoleGroupConfig` below).
let logging = validate_logging(
Expand All @@ -254,7 +267,11 @@ pub fn validate(
// Only used in `Deployment` mode; a DaemonSet derives its Pod count from the
// number of nodes.
replicas: merged.replicas,
config: ValidatedOpaConfig::from_merged(merged.config.config, logging),
config: ValidatedOpaConfig::from_merged(
merged.config.config,
containerdebug,
logging,
),
config_overrides: merged.config.config_overrides,
env_overrides: merged.config.env_overrides.into(),
cli_overrides: merged.config.cli_overrides,
Expand Down
7 changes: 6 additions & 1 deletion rust/operator-binary/src/crd/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use stackable_operator::{
},
config::{fragment::Fragment, merge::Merge},
constant,
crd::containerdebug::{default_containerdebug_config, v1alpha1::ContainerdebugConfig},
deep_merger::ObjectOverrides,
k8s_openapi::apimachinery::pkg::api::resource::Quantity,
kube::CustomResource,
Expand Down Expand Up @@ -315,7 +316,7 @@ impl Container {

// NOTE (@Techassi): This struct can currently NOT be versioned because it is used via Role which
// makes it incredible hard to implement the From trait for conversions.
#[derive(Clone, Debug, Default, Fragment, JsonSchema, PartialEq)]
#[derive(Clone, Debug, Fragment, JsonSchema, PartialEq)]
#[fragment_attrs(
derive(
Clone,
Expand All @@ -339,6 +340,9 @@ pub struct OpaConfig {
#[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 @@ -441,6 +445,7 @@ impl OpaConfig {
// Spreads the role's Pods across nodes.
v1alpha2::WorkloadKind::Deployment => affinity::get_affinity(cluster_name, role),
},
containerdebug: default_containerdebug_config(),
graceful_shutdown_timeout: Some(DEFAULT_SERVER_GRACEFUL_SHUTDOWN_TIMEOUT),
}
}
Expand Down
Loading