From 2eefa4a24ed155f1b317f1d54e8676320fa0a8d0 Mon Sep 17 00:00:00 2001 From: Razvan-Daniel Mihai <84674+razvan@users.noreply.github.com> Date: Mon, 10 Aug 2026 14:30:15 +0200 Subject: [PATCH 01/10] feat: add exec-based NiFi 2.x management-server probe builders Co-Authored-By: Claude Sonnet 5 --- .../src/controller/build/resource/mod.rs | 1 + .../src/controller/build/resource/probes.rs | 119 ++++++++++++++++++ 2 files changed, 120 insertions(+) create mode 100644 rust/operator-binary/src/controller/build/resource/probes.rs diff --git a/rust/operator-binary/src/controller/build/resource/mod.rs b/rust/operator-binary/src/controller/build/resource/mod.rs index 9598a324..a475d2e9 100644 --- a/rust/operator-binary/src/controller/build/resource/mod.rs +++ b/rust/operator-binary/src/controller/build/resource/mod.rs @@ -5,6 +5,7 @@ pub mod config_map; pub mod listener; pub mod pdb; +pub mod probes; pub mod rbac; pub mod service; pub mod statefulset; diff --git a/rust/operator-binary/src/controller/build/resource/probes.rs b/rust/operator-binary/src/controller/build/resource/probes.rs new file mode 100644 index 00000000..abc55939 --- /dev/null +++ b/rust/operator-binary/src/controller/build/resource/probes.rs @@ -0,0 +1,119 @@ +//! Builds the exec-based startup and readiness probes that check NiFi 2.x's +//! local, unauthenticated management-server endpoints (`/health` and +//! `/health/cluster`), rather than only checking that the HTTPS port is open. +//! +//! The management server binds `127.0.0.1` only, so these must be `exec` +//! probes using `curl` from inside the container - a `httpGet` probe cannot +//! reach a loopback-only address. + +use stackable_operator::k8s_openapi::api::core::v1::{ExecAction, Probe}; + +/// Port NiFi's management server binds to by default +/// (`org.apache.nifi.management.server.address`, see +/// `ManagementServerProvider.MANAGEMENT_SERVER_DEFAULT_ADDRESS` upstream). +/// The operator pins the JVM system property to this same value (see +/// `build::jvm`), so this constant is the single source of truth for it. +pub const MANAGEMENT_SERVER_PORT: u16 = 52020; + +fn management_health_exec(path: &str) -> ExecAction { + ExecAction { + command: Some(vec![ + "/bin/bash".to_string(), + "-euo".to_string(), + "pipefail".to_string(), + "-c".to_string(), + format!( + "curl --fail --silent --show-error --output /dev/null http://127.0.0.1:{MANAGEMENT_SERVER_PORT}{path}" + ), + ]), + } +} + +/// Gates startup on the management server reporting the app server has +/// booted. `/health` only starts responding once NiFi's own web server (and +/// flow load) has completed, so this replaces the old blunt TCP-socket check +/// on the HTTPS port. +pub fn management_startup_probe() -> Probe { + Probe { + initial_delay_seconds: Some(10), + period_seconds: Some(10), + failure_threshold: Some(20 * 6), + exec: Some(management_health_exec("/health")), + ..Probe::default() + } +} + +/// Gates readiness on cluster membership: `/health/cluster` returns 200 only +/// while the node's `ConnectionState` is `CONNECTED`/`CONNECTING`, and 503 +/// otherwise (e.g. during a rolling restart before the node has rejoined). +pub fn management_readiness_probe() -> Probe { + Probe { + initial_delay_seconds: Some(10), + period_seconds: Some(10), + failure_threshold: Some(3), + exec: Some(management_health_exec("/health/cluster")), + ..Probe::default() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn startup_probe_execs_curl_against_health_endpoint() { + let probe = management_startup_probe(); + + let command = probe + .exec + .expect("startup probe must be an exec probe") + .command + .expect("exec action must have a command"); + let script = command.last().expect("bash -c script argument"); + + assert!( + script.contains("http://127.0.0.1:52020/health") && !script.contains("/health/cluster"), + "expected curl against /health, got: {script}" + ); + assert_eq!(probe.failure_threshold, Some(120)); + assert!( + probe.tcp_socket.is_none(), + "must not fall back to tcp_socket" + ); + } + + #[test] + fn readiness_probe_execs_curl_against_cluster_health_endpoint() { + let probe = management_readiness_probe(); + + let command = probe + .exec + .expect("readiness probe must be an exec probe") + .command + .expect("exec action must have a command"); + let script = command.last().expect("bash -c script argument"); + + assert!( + script.contains("http://127.0.0.1:52020/health/cluster"), + "expected curl against /health/cluster, got: {script}" + ); + assert_eq!(probe.failure_threshold, Some(3)); + } + + #[test] + fn probes_use_bash_pipefail_wrapper_not_bare_curl_argv() { + for probe in [management_startup_probe(), management_readiness_probe()] { + let command = probe.exec.unwrap().command.unwrap(); + assert_eq!( + command[..4], + [ + "/bin/bash".to_string(), + "-euo".to_string(), + "pipefail".to_string(), + "-c".to_string(), + ], + "exec command must follow the repo's bash -euo pipefail -c convention" + ); + } + } +} From 51bdfa7912ec0a4b32cd8efabfd3afa3cdf5d292 Mon Sep 17 00:00:00 2001 From: Razvan-Daniel Mihai <84674+razvan@users.noreply.github.com> Date: Mon, 10 Aug 2026 14:34:13 +0200 Subject: [PATCH 02/10] feat: gate NiFi startup/readiness on management-server health endpoints --- .../controller/build/resource/statefulset.rs | 27 ++++++++++--------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/rust/operator-binary/src/controller/build/resource/statefulset.rs b/rust/operator-binary/src/controller/build/resource/statefulset.rs index 39346f9e..3278552f 100644 --- a/rust/operator-binary/src/controller/build/resource/statefulset.rs +++ b/rust/operator-binary/src/controller/build/resource/statefulset.rs @@ -54,9 +54,12 @@ use crate::{ graceful_shutdown::add_graceful_shutdown_config, object_meta, properties::ConfigFileName, - resource::listener::{ - LISTENER_VOLUME_DIR, LISTENER_VOLUME_NAME, build_group_listener_pvc, - group_listener_name, + resource::{ + listener::{ + LISTENER_VOLUME_DIR, LISTENER_VOLUME_NAME, build_group_listener_pvc, + group_listener_name, + }, + probes::{management_readiness_probe, management_startup_probe}, }, }, }, @@ -444,16 +447,14 @@ pub(crate) fn build_node_rolegroup_statefulset( }), ..Probe::default() }) - .startup_probe(Probe { - initial_delay_seconds: Some(10), - period_seconds: Some(10), - failure_threshold: Some(20 * 6), - tcp_socket: Some(TCPSocketAction { - port: IntOrString::String(HTTPS_PORT_NAME.to_string()), - ..TCPSocketAction::default() - }), - ..Probe::default() - }) + // Gates startup on the NiFi 2.x management server reporting the app + // server has booted (rather than just the HTTPS port accepting + // connections). + .startup_probe(management_startup_probe()) + // Gates readiness on the node actually being connected to the + // cluster, so a node that is up but not yet rejoined after a + // rolling restart is correctly reported as not Ready. + .readiness_probe(management_readiness_probe()) .resources(merged_config.resources.clone().into()); let mut pod_builder = PodBuilder::new(); From 236210b1d4bd74df5d3662011362de829c1475fe Mon Sep 17 00:00:00 2001 From: Razvan-Daniel Mihai <84674+razvan@users.noreply.github.com> Date: Mon, 10 Aug 2026 14:38:05 +0200 Subject: [PATCH 03/10] feat: pin NiFi management-server address explicitly via JVM property Co-Authored-By: Claude Sonnet 5 --- .../src/controller/build/jvm.rs | 31 ++++++++++++++++++- .../build/properties/bootstrap_conf.rs | 10 +++--- 2 files changed, 36 insertions(+), 5 deletions(-) diff --git a/rust/operator-binary/src/controller/build/jvm.rs b/rust/operator-binary/src/controller/build/jvm.rs index b6275a07..cbd21966 100644 --- a/rust/operator-binary/src/controller/build/jvm.rs +++ b/rust/operator-binary/src/controller/build/jvm.rs @@ -9,7 +9,10 @@ use stackable_operator::{ use crate::{ controller::{ ValidatedNifiConfig, - build::{NIFI_CONFIG_DIRECTORY, properties::ConfigFileName}, + build::{ + NIFI_CONFIG_DIRECTORY, properties::ConfigFileName, + resource::probes::MANAGEMENT_SERVER_PORT, + }, }, security::{ authentication::{STACKABLE_SERVER_TLS_DIR, STACKABLE_TLS_STORE_PASSWORD}, @@ -86,6 +89,10 @@ pub fn build_merged_jvm_config( "-Djava.security.properties={NIFI_CONFIG_DIRECTORY}/{}", ConfigFileName::SecurityProperties ), + // Pin the NiFi 2.x management server (used by the startup/readiness + // probes in resource::probes) to a known address instead of relying + // on its undocumented upstream default. + format!("-Dorg.apache.nifi.management.server.address=127.0.0.1:{MANAGEMENT_SERVER_PORT}"), ]; // Add JVM truststore properties when OPA TLS is enabled @@ -170,6 +177,28 @@ mod tests { ); } + /// The management-server bind address is pinned explicitly rather than + /// relying on NiFi's undocumented upstream default, so the probes in + /// `resource::probes` always target the right port even if that default + /// ever changes upstream. + #[test] + fn management_server_address_is_pinned_explicitly() { + let cluster = minimal_validated_cluster(); + let args = build_merged_jvm_config( + &default_rg(&cluster).config, + &JvmArgumentOverrides::default(), + None, + ) + .expect("jvm config should build"); + + assert!( + args.contains( + &"-Dorg.apache.nifi.management.server.address=127.0.0.1:52020".to_string() + ), + "expected an explicit management-server address JVM property, got: {args:?}" + ); + } + /// Without OPA TLS, no truststore properties are emitted. #[test] fn without_opa_tls_no_truststore_properties() { diff --git a/rust/operator-binary/src/controller/build/properties/bootstrap_conf.rs b/rust/operator-binary/src/controller/build/properties/bootstrap_conf.rs index 77602fdb..0898e5ea 100644 --- a/rust/operator-binary/src/controller/build/properties/bootstrap_conf.rs +++ b/rust/operator-binary/src/controller/build/properties/bootstrap_conf.rs @@ -122,6 +122,7 @@ mod tests { java.arg.10=-Djavax.security.auth.useSubjectCredsOnly=true java.arg.11=-Dzookeeper.admin.enableServer=false java.arg.12=-Djava.security.properties=/stackable/nifi/conf/security.properties + java.arg.13=-Dorg.apache.nifi.management.server.address=127.0.0.1:52020 java.arg.2=-Xms3276m java.arg.3=-XX:+UseG1GC java.arg.4=-Djava.awt.headless=true @@ -187,10 +188,11 @@ mod tests { java=java java.arg.1=-Xms34406m java.arg.10=-Djava.security.properties=/stackable/nifi/conf/security.properties - java.arg.11=-Dhttps.proxyHost=proxy.my.corp - java.arg.12=-Djava.net.preferIPv4Stack=true - java.arg.13=-Xmx40000m - java.arg.14=-Dhttps.proxyPort=1234 + java.arg.11=-Dorg.apache.nifi.management.server.address=127.0.0.1:52020 + java.arg.12=-Dhttps.proxyHost=proxy.my.corp + java.arg.13=-Djava.net.preferIPv4Stack=true + java.arg.14=-Xmx40000m + java.arg.15=-Dhttps.proxyPort=1234 java.arg.2=-Djava.awt.headless=true java.arg.3=-Dorg.apache.jasper.compiler.disablejsr199=true java.arg.4=-Djava.net.preferIPv4Stack=true From e67829a4ca1b8390a7151a8f81afbe8fc63020b6 Mon Sep 17 00:00:00 2001 From: Razvan-Daniel Mihai <84674+razvan@users.noreply.github.com> Date: Mon, 10 Aug 2026 14:41:39 +0200 Subject: [PATCH 04/10] test: assert NiFi 2.x management-server probes and readiness gating in cluster_operation kuttl test --- .../kuttl/cluster_operation/20-assert.yaml | 28 +++++++++++++++++++ .../kuttl/cluster_operation/50-assert.yaml | 17 +++++++++++ 2 files changed, 45 insertions(+) diff --git a/tests/templates/kuttl/cluster_operation/20-assert.yaml b/tests/templates/kuttl/cluster_operation/20-assert.yaml index 155b0d03..ed248438 100644 --- a/tests/templates/kuttl/cluster_operation/20-assert.yaml +++ b/tests/templates/kuttl/cluster_operation/20-assert.yaml @@ -12,3 +12,31 @@ metadata: status: readyReplicas: 2 replicas: 2 +--- +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: test-nifi-node-default +spec: + template: + spec: + containers: + - name: nifi + startupProbe: + failureThreshold: 120 + exec: + command: + - /bin/bash + - -euo + - pipefail + - -c + - curl --fail --silent --show-error --output /dev/null http://127.0.0.1:52020/health + readinessProbe: + failureThreshold: 3 + exec: + command: + - /bin/bash + - -euo + - pipefail + - -c + - curl --fail --silent --show-error --output /dev/null http://127.0.0.1:52020/health/cluster diff --git a/tests/templates/kuttl/cluster_operation/50-assert.yaml b/tests/templates/kuttl/cluster_operation/50-assert.yaml index c1304692..14a01a1a 100644 --- a/tests/templates/kuttl/cluster_operation/50-assert.yaml +++ b/tests/templates/kuttl/cluster_operation/50-assert.yaml @@ -3,6 +3,23 @@ apiVersion: kuttl.dev/v1beta1 kind: TestAssert timeout: 600 commands: + - script: | + # Confirm at least one pod is reported NotReady while the rollout is in + # progress - i.e. readiness is actually gating on cluster membership, + # not just passing immediately. Polls briefly right after the restart + # is triggered, before asserting the final converged state below. + for i in $(seq 1 30); do + NOT_READY=$(kubectl -n $NAMESPACE get pods -l app.kubernetes.io/name=nifi \ + -o jsonpath='{range .items[*]}{.status.conditions[?(@.type=="Ready")].status}{"\n"}{end}' \ + | grep -c False || true) + if [ "$NOT_READY" -gt 0 ]; then + echo "observed $NOT_READY not-ready pod(s) mid-restart, as expected" + exit 0 + fi + sleep 2 + done + echo "never observed a not-ready pod during the restart window - readiness probe may not be gating correctly" + exit 1 - script: kubectl -n $NAMESPACE wait --for=condition=available nificlusters.nifi.stackable.tech/test-nifi --timeout 601s --- apiVersion: apps/v1 From c5ba5fcc543835e28b52e147714c0156a967aea8 Mon Sep 17 00:00:00 2001 From: Razvan-Daniel Mihai <84674+razvan@users.noreply.github.com> Date: Mon, 10 Aug 2026 16:14:40 +0200 Subject: [PATCH 05/10] fix: set explicit timeout_seconds on management-server exec probes Co-Authored-By: Claude Sonnet 5 --- rust/operator-binary/src/controller/build/resource/probes.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/rust/operator-binary/src/controller/build/resource/probes.rs b/rust/operator-binary/src/controller/build/resource/probes.rs index abc55939..15493240 100644 --- a/rust/operator-binary/src/controller/build/resource/probes.rs +++ b/rust/operator-binary/src/controller/build/resource/probes.rs @@ -37,6 +37,7 @@ pub fn management_startup_probe() -> Probe { Probe { initial_delay_seconds: Some(10), period_seconds: Some(10), + timeout_seconds: Some(3), failure_threshold: Some(20 * 6), exec: Some(management_health_exec("/health")), ..Probe::default() @@ -50,6 +51,7 @@ pub fn management_readiness_probe() -> Probe { Probe { initial_delay_seconds: Some(10), period_seconds: Some(10), + timeout_seconds: Some(3), failure_threshold: Some(3), exec: Some(management_health_exec("/health/cluster")), ..Probe::default() @@ -76,6 +78,7 @@ mod tests { "expected curl against /health, got: {script}" ); assert_eq!(probe.failure_threshold, Some(120)); + assert_eq!(probe.timeout_seconds, Some(3)); assert!( probe.tcp_socket.is_none(), "must not fall back to tcp_socket" @@ -98,6 +101,7 @@ mod tests { "expected curl against /health/cluster, got: {script}" ); assert_eq!(probe.failure_threshold, Some(3)); + assert_eq!(probe.timeout_seconds, Some(3)); } #[test] From 2a8df57128c50e1c792d5e45bdd79d07496b06cf Mon Sep 17 00:00:00 2001 From: Razvan-Daniel Mihai <84674+razvan@users.noreply.github.com> Date: Mon, 10 Aug 2026 16:14:43 +0200 Subject: [PATCH 06/10] fix: revert retry-unsafe NotReady polling assertion in cluster_operation kuttl test --- .../kuttl/cluster_operation/50-assert.yaml | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/tests/templates/kuttl/cluster_operation/50-assert.yaml b/tests/templates/kuttl/cluster_operation/50-assert.yaml index 14a01a1a..c1304692 100644 --- a/tests/templates/kuttl/cluster_operation/50-assert.yaml +++ b/tests/templates/kuttl/cluster_operation/50-assert.yaml @@ -3,23 +3,6 @@ apiVersion: kuttl.dev/v1beta1 kind: TestAssert timeout: 600 commands: - - script: | - # Confirm at least one pod is reported NotReady while the rollout is in - # progress - i.e. readiness is actually gating on cluster membership, - # not just passing immediately. Polls briefly right after the restart - # is triggered, before asserting the final converged state below. - for i in $(seq 1 30); do - NOT_READY=$(kubectl -n $NAMESPACE get pods -l app.kubernetes.io/name=nifi \ - -o jsonpath='{range .items[*]}{.status.conditions[?(@.type=="Ready")].status}{"\n"}{end}' \ - | grep -c False || true) - if [ "$NOT_READY" -gt 0 ]; then - echo "observed $NOT_READY not-ready pod(s) mid-restart, as expected" - exit 0 - fi - sleep 2 - done - echo "never observed a not-ready pod during the restart window - readiness probe may not be gating correctly" - exit 1 - script: kubectl -n $NAMESPACE wait --for=condition=available nificlusters.nifi.stackable.tech/test-nifi --timeout 601s --- apiVersion: apps/v1 From cf041f0b3266c64ac34d0db70a0fc2c778976582 Mon Sep 17 00:00:00 2001 From: Razvan-Daniel Mihai <84674+razvan@users.noreply.github.com> Date: Mon, 10 Aug 2026 16:24:13 +0200 Subject: [PATCH 07/10] fix: increase probe timeout_seconds from 3 to 5 for real headroom Co-Authored-By: Claude Sonnet 5 --- .../src/controller/build/resource/probes.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/rust/operator-binary/src/controller/build/resource/probes.rs b/rust/operator-binary/src/controller/build/resource/probes.rs index 15493240..75ab1fc3 100644 --- a/rust/operator-binary/src/controller/build/resource/probes.rs +++ b/rust/operator-binary/src/controller/build/resource/probes.rs @@ -37,7 +37,7 @@ pub fn management_startup_probe() -> Probe { Probe { initial_delay_seconds: Some(10), period_seconds: Some(10), - timeout_seconds: Some(3), + timeout_seconds: Some(5), failure_threshold: Some(20 * 6), exec: Some(management_health_exec("/health")), ..Probe::default() @@ -51,7 +51,7 @@ pub fn management_readiness_probe() -> Probe { Probe { initial_delay_seconds: Some(10), period_seconds: Some(10), - timeout_seconds: Some(3), + timeout_seconds: Some(5), failure_threshold: Some(3), exec: Some(management_health_exec("/health/cluster")), ..Probe::default() @@ -78,7 +78,7 @@ mod tests { "expected curl against /health, got: {script}" ); assert_eq!(probe.failure_threshold, Some(120)); - assert_eq!(probe.timeout_seconds, Some(3)); + assert_eq!(probe.timeout_seconds, Some(5)); assert!( probe.tcp_socket.is_none(), "must not fall back to tcp_socket" @@ -101,7 +101,7 @@ mod tests { "expected curl against /health/cluster, got: {script}" ); assert_eq!(probe.failure_threshold, Some(3)); - assert_eq!(probe.timeout_seconds, Some(3)); + assert_eq!(probe.timeout_seconds, Some(5)); } #[test] From 63267ee4ea171a24f325ac45759fc23410d21f29 Mon Sep 17 00:00:00 2001 From: Razvan-Daniel Mihai <84674+razvan@users.noreply.github.com> Date: Mon, 10 Aug 2026 16:32:16 +0200 Subject: [PATCH 08/10] docs: changelog entry for NiFi 2.x management-server health probes --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 75b88a5c..c279084c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,9 @@ All notable changes to this project will be documented in this file. - BREAKING: The `nodes` role is now required by the CRD; a NifiCluster without it was previously accepted by the API server but failed reconciliation ([#966]). - All product containers now run with `securityContext.runAsNonRoot` set to `true` to improve security ([#975]). +- NiFi 2.x startup and readiness probes now use the local management server's `/health` and + `/health/cluster` endpoints instead of a bare TCP check, so a node is only reported Ready once + it has actually joined the cluster (e.g. during a rolling restart) ([#TBD]). ### Fixed @@ -25,6 +28,7 @@ All notable changes to this project will be documented in this file. [#966]: https://github.com/stackabletech/nifi-operator/pull/966 [#970]: https://github.com/stackabletech/nifi-operator/pull/970 [#975]: https://github.com/stackabletech/nifi-operator/pull/975 +[#TBD]: https://github.com/stackabletech/nifi-operator/pull/TBD ## [26.7.0] - 2026-07-21 From 88a8f27d36c273b02ca4196fd5eb9c42ce51a834 Mon Sep 17 00:00:00 2001 From: Razvan-Daniel Mihai <84674+razvan@users.noreply.github.com> Date: Mon, 10 Aug 2026 16:40:16 +0200 Subject: [PATCH 09/10] refactor: relocate MANAGEMENT_SERVER_PORT, document override caveat, drop dead readiness delay Co-Authored-By: Claude Sonnet 5 --- rust/operator-binary/src/controller/build.rs | 10 ++++++++++ rust/operator-binary/src/controller/build/jvm.rs | 5 +---- .../src/controller/build/resource/probes.rs | 13 ++++++------- 3 files changed, 17 insertions(+), 11 deletions(-) diff --git a/rust/operator-binary/src/controller/build.rs b/rust/operator-binary/src/controller/build.rs index 6376f9df..5e3b7d73 100644 --- a/rust/operator-binary/src/controller/build.rs +++ b/rust/operator-binary/src/controller/build.rs @@ -46,6 +46,16 @@ pub const PROTOCOL_PORT: Port = Port(9088); pub const BALANCE_PORT_NAME: &str = "balance"; pub const BALANCE_PORT: Port = Port(6243); +/// Port NiFi's management server binds to by default +/// (`org.apache.nifi.management.server.address`, see +/// `ManagementServerProvider.MANAGEMENT_SERVER_DEFAULT_ADDRESS` upstream). +/// The operator pins the JVM system property to this same value (see +/// `build::jvm`), so this constant is the single source of truth for it. +/// Note: a user-supplied `jvmArgumentOverrides` that removes or repoints the +/// corresponding `-D` property would desync this from the actual +/// management-server address, silently breaking both probes. +pub const MANAGEMENT_SERVER_PORT: u16 = 52020; + // Filesystem paths shared by multiple builders. Single-consumer paths live in their builder. pub const NIFI_CONFIG_DIRECTORY: &str = "/stackable/nifi/conf"; pub const NIFI_PYTHON_WORKING_DIRECTORY: &str = "/nifi-python-working-directory"; diff --git a/rust/operator-binary/src/controller/build/jvm.rs b/rust/operator-binary/src/controller/build/jvm.rs index cbd21966..3ee66731 100644 --- a/rust/operator-binary/src/controller/build/jvm.rs +++ b/rust/operator-binary/src/controller/build/jvm.rs @@ -9,10 +9,7 @@ use stackable_operator::{ use crate::{ controller::{ ValidatedNifiConfig, - build::{ - NIFI_CONFIG_DIRECTORY, properties::ConfigFileName, - resource::probes::MANAGEMENT_SERVER_PORT, - }, + build::{MANAGEMENT_SERVER_PORT, NIFI_CONFIG_DIRECTORY, properties::ConfigFileName}, }, security::{ authentication::{STACKABLE_SERVER_TLS_DIR, STACKABLE_TLS_STORE_PASSWORD}, diff --git a/rust/operator-binary/src/controller/build/resource/probes.rs b/rust/operator-binary/src/controller/build/resource/probes.rs index 75ab1fc3..942903dd 100644 --- a/rust/operator-binary/src/controller/build/resource/probes.rs +++ b/rust/operator-binary/src/controller/build/resource/probes.rs @@ -8,12 +8,7 @@ use stackable_operator::k8s_openapi::api::core::v1::{ExecAction, Probe}; -/// Port NiFi's management server binds to by default -/// (`org.apache.nifi.management.server.address`, see -/// `ManagementServerProvider.MANAGEMENT_SERVER_DEFAULT_ADDRESS` upstream). -/// The operator pins the JVM system property to this same value (see -/// `build::jvm`), so this constant is the single source of truth for it. -pub const MANAGEMENT_SERVER_PORT: u16 = 52020; +use crate::controller::build::MANAGEMENT_SERVER_PORT; fn management_health_exec(path: &str) -> ExecAction { ExecAction { @@ -49,7 +44,6 @@ pub fn management_startup_probe() -> Probe { /// otherwise (e.g. during a rolling restart before the node has rejoined). pub fn management_readiness_probe() -> Probe { Probe { - initial_delay_seconds: Some(10), period_seconds: Some(10), timeout_seconds: Some(5), failure_threshold: Some(3), @@ -102,6 +96,11 @@ mod tests { ); assert_eq!(probe.failure_threshold, Some(3)); assert_eq!(probe.timeout_seconds, Some(5)); + assert_eq!( + probe.initial_delay_seconds, None, + "readiness probe delay is redundant: k8s already suppresses readiness checks \ + until the startup probe succeeds" + ); } #[test] From 6e048007ab1d502c42bd22b36a5ae1732577967d Mon Sep 17 00:00:00 2001 From: Razvan-Daniel Mihai <84674+razvan@users.noreply.github.com> Date: Mon, 10 Aug 2026 17:27:49 +0200 Subject: [PATCH 10/10] Update the changelog and cleanups --- CHANGELOG.md | 5 ++--- .../src/controller/build/resource/probes.rs | 11 +---------- .../src/controller/build/resource/statefulset.rs | 6 ------ 3 files changed, 3 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c279084c..5900c503 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,8 +15,7 @@ All notable changes to this project will be documented in this file. previously accepted by the API server but failed reconciliation ([#966]). - All product containers now run with `securityContext.runAsNonRoot` set to `true` to improve security ([#975]). - NiFi 2.x startup and readiness probes now use the local management server's `/health` and - `/health/cluster` endpoints instead of a bare TCP check, so a node is only reported Ready once - it has actually joined the cluster (e.g. during a rolling restart) ([#TBD]). + `/health/cluster` endpoints instead of a bare TCP check ([#976]). ### Fixed @@ -28,7 +27,7 @@ All notable changes to this project will be documented in this file. [#966]: https://github.com/stackabletech/nifi-operator/pull/966 [#970]: https://github.com/stackabletech/nifi-operator/pull/970 [#975]: https://github.com/stackabletech/nifi-operator/pull/975 -[#TBD]: https://github.com/stackabletech/nifi-operator/pull/TBD +[#976]: https://github.com/stackabletech/nifi-operator/pull/976 ## [26.7.0] - 2026-07-21 diff --git a/rust/operator-binary/src/controller/build/resource/probes.rs b/rust/operator-binary/src/controller/build/resource/probes.rs index 942903dd..03a45ab2 100644 --- a/rust/operator-binary/src/controller/build/resource/probes.rs +++ b/rust/operator-binary/src/controller/build/resource/probes.rs @@ -1,6 +1,6 @@ //! Builds the exec-based startup and readiness probes that check NiFi 2.x's //! local, unauthenticated management-server endpoints (`/health` and -//! `/health/cluster`), rather than only checking that the HTTPS port is open. +//! `/health/cluster`). //! //! The management server binds `127.0.0.1` only, so these must be `exec` //! probes using `curl` from inside the container - a `httpGet` probe cannot @@ -23,11 +23,6 @@ fn management_health_exec(path: &str) -> ExecAction { ]), } } - -/// Gates startup on the management server reporting the app server has -/// booted. `/health` only starts responding once NiFi's own web server (and -/// flow load) has completed, so this replaces the old blunt TCP-socket check -/// on the HTTPS port. pub fn management_startup_probe() -> Probe { Probe { initial_delay_seconds: Some(10), @@ -38,10 +33,6 @@ pub fn management_startup_probe() -> Probe { ..Probe::default() } } - -/// Gates readiness on cluster membership: `/health/cluster` returns 200 only -/// while the node's `ConnectionState` is `CONNECTED`/`CONNECTING`, and 503 -/// otherwise (e.g. during a rolling restart before the node has rejoined). pub fn management_readiness_probe() -> Probe { Probe { period_seconds: Some(10), diff --git a/rust/operator-binary/src/controller/build/resource/statefulset.rs b/rust/operator-binary/src/controller/build/resource/statefulset.rs index 3278552f..54fa31dd 100644 --- a/rust/operator-binary/src/controller/build/resource/statefulset.rs +++ b/rust/operator-binary/src/controller/build/resource/statefulset.rs @@ -447,13 +447,7 @@ pub(crate) fn build_node_rolegroup_statefulset( }), ..Probe::default() }) - // Gates startup on the NiFi 2.x management server reporting the app - // server has booted (rather than just the HTTPS port accepting - // connections). .startup_probe(management_startup_probe()) - // Gates readiness on the node actually being connected to the - // cluster, so a node that is up but not yet rejoined after a - // rolling restart is correctly reported as not Ready. .readiness_probe(management_readiness_probe()) .resources(merged_config.resources.clone().into());