stackabletech / stackabletech/kafka-operator
bug: .spec.clusterOperation.stopped doesn't consider dependence, leads to Brokers hanging on shutdown while trying to reach controllers
Nobody has claimed this yet.
- Dominant language
- Rust
- Stars
- 28
- Forks
- 8
- Avg merge
- 2d 12h
- Merged PRs (30d)
- 9
Description
This might apply to other products, but so far applies to kafka-operator.
When spec.clusterOperation.stopped: true is used on KafkaClusters using KRaft, the operator shuts down controllers and brokers concurrently. This leads to brokers not being able to talk to controllers and hanging until Kubernetes force kills them.
There are two options depending on whether we want to fix this for all operators, or scope it to kafka-operator:
- Option A (operator-rs): Add role shutdown ordering to the framework so operators declare dependencies and ClusterResources handles multi-phase stop/start. Reusable but larger scope.
- Option B (Kafka operator): Custom logic in the reconciler that checks broker StatefulSet status before scaling down controllers. Self-contained but not reusable.
[!TIP]
If Option A is selected, please move this issue to the stackabletech/issues repository, and add a list of operator repos for tracking the rollout.
Generated explanation of the two options
Fix: Kafka hangs on shutdown when dependencies are terminated simultaneously
Problem
When a KafkaCluster is stopped (via clusterOperation.stopped: true or namespace deletion),
all roles (brokers, controllers/ZooKeeper) are terminated simultaneously. Brokers have
controlled.shutdown.enable=true, which requires communication with controllers (KRaft) or
ZooKeeper during shutdown. If those are already gone, brokers block until the
terminationGracePeriodSeconds expires (default 30 minutes).
Current behavior
ClusterOperationis cluster-wide only (crd/mod.rs:138) - no per-role control.ClusterResourceApplyStrategy::from(&kafka.spec.cluster_operation)(kafka_controller.rs:282)
applies uniformly to all StatefulSets in one reconciliation pass.- Both broker and controller StatefulSets use
podManagementPolicy: Parallel
(statefulset.rs:550, :877), so all pods terminate at once. - The reconciler iterates roles in a single loop (
kafka_controller.rs:352-488) with no
ordering or phasing.
Option A: Framework-level support in operator-rs
Add a concept of role shutdown ordering to the ClusterResourceApplyStrategy / ClusterResources
framework in operator-rs, so any operator (Kafka, Druid, Trino, etc.) can declare dependency
ordering between roles.
Approach
-
Extend
ClusterOperationorClusterResourceswith role dependency metadata.
The operator would declare something like:cluster_resources.set_shutdown_order(vec![ KafkaRole::Broker, // stop first KafkaRole::Controller, // stop after brokers are gone ]); -
Multi-phase reconciliation in
ClusterResources::add().
When the strategy is "stopped",cluster_resources.add()for a StatefulSet would:- Phase 1: Set replicas to 0 only for the first role in the shutdown order.
- Phase 2: On the next reconciliation (triggered by the StatefulSet watch), check that all
phase-1 StatefulSets have 0 ready replicas, then set replicas to 0 for the next role. - Repeat until all roles are stopped.
-
Condition reporting per phase.
Add conditions likeStoppingSinceorShutdownPhase: brokersso the status reflects
progress.
Pros
- Reusable across all Stackable operators (any product with role dependencies).
- Clean separation of concerns - individual operators just declare the ordering.
- Handles both
stopped: trueand namespace deletion (if the operator gets a chance to reconcile).
Cons
- Larger scope - requires changes to the shared operator-rs crate.
- Multi-phase reconciliation adds complexity to the framework.
- Does NOT help with namespace deletion if all resources are deleted simultaneously
(Kubernetes may not give the operator time to reconcile between phases).
Option B: Custom sequencing in the Kafka operator reconciler
Handle shutdown ordering directly in kafka_controller.rs by splitting the reconciliation
into phases when stopped: true.
Approach
-
Detect the stopped state early in the reconciler (
kafka_controller.rs, before the
role loop at line 352). -
Check current StatefulSet state before applying.
Whencluster_operation.stopped == true:// Pseudo-code let broker_sts_list = list_statefulsets(label: component=broker); let all_brokers_stopped = broker_sts_list.iter().all(|sts| sts.status.replicas == 0); if !all_brokers_stopped { // Only scale down brokers, leave controllers untouched // Apply broker StatefulSets with replicas: 0 // Apply controller StatefulSets with their current replica count // Requeue after a short delay return Ok(Action::requeue(Duration::from_secs(5))); } // All brokers are gone, now scale down controllers // Apply all StatefulSets with replicas: 0 -
Modify the role loop to accept a per-role override for the apply strategy, or
skip applying certain StatefulSets depending on the shutdown phase. -
Apply the same logic in reverse for startup (optional): when transitioning from
stopped: truetostopped: false, start controllers first, wait for them to be ready,
then start brokers.
Pros
- Self-contained in the Kafka operator - no upstream changes needed.
- Can ship independently on the Kafka operator's release cycle.
- Simpler to reason about since the logic is Kafka-specific.
Cons
- Not reusable - other operators with similar problems (e.g., Druid, HBase) would need their
own implementations. - Adds complexity to the Kafka reconciler, which is already non-trivial.
- Still does NOT help with namespace deletion (same limitation as Option A).
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with the shutdown handling in kafka_controller.rs, especially the role loop around lines 352-488, then inspect ClusterOperation in crd/mod.rs and StatefulSet behavior in statefulset.rs. Compare whether sequencing belongs in operator-rs or the Kafka reconciler. Done means brokers can stop while controllers remain available, followed by controller shutdown, without hanging until Kubernetes force-kills them.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- kubernetes, rust
- Domain
- distributed-systems, infrastructure
- Issue type
- Bug
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100