Skip to content

Stop and retain a single Kafka broker - #230

Closed
acgtun wants to merge 3 commits into
strimzi:mainfrom
acgtun:147-stop-and-retain-a-single-kafka-broker
Closed

Stop and retain a single Kafka broker#230
acgtun wants to merge 3 commits into
strimzi:mainfrom
acgtun:147-stop-and-retain-a-single-kafka-broker

Conversation

@acgtun

@acgtun acgtun commented Jun 15, 2026

Copy link
Copy Markdown

Summary

Proposal for stopping a single Kafka broker — no Pod, no Kafka process — while retaining its node ID, storage, and configuration, until it is explicitly started again. The rest of the cluster keeps reconciling normally. This targets planned host maintenance and investigation where the same broker must come back with its data intact and its pod cannot simply be rescheduled (local/host-attached storage, or the host itself is the maintenance target).

Design (rewritten after the first review round)

  • The trigger is a KafkaNodePool annotation, strimzi.io/stopped-nodes, using the same node-ID list format as the existing node-ID annotations, instead of a spec field.
  • Core mechanism: the StrimziPodSetController does not (re)create a pod for a listed node. The broker is stopped by deleting its pod (or letting a host drain evict it) and stays down; removing the ID from the annotation brings it back with the same identity, storage, and current configuration.
  • The annotation is self-contained: it also implies the roller and readiness exclusions, so a stopped node does not wedge reconciliation of the rest of the cluster.
  • Brokers only; a stop targeting a node with the controller role is ignored (KRaft quorum safety is future work).
  • Dropped from the first draft per review: the PDB recomputation, the scale-down refusal logic, the per-reconcile ISR scan and partition metrics, the at-least-one-broker enforcement, the feature gate, and the status-condition timestamps.

acgtun added 2 commits June 14, 2026 20:18
Signed-off-by: Haifeng Chen <haifengc@twitter.com>
- Enforce "at least one broker running"; the bootstrap/Admin pipeline steps
  (serviceEndpointsReady, clusterId, metadataVersion, nodeUnregistration)
  wedge if the last running broker is stopped, independent of runningNodes()
- Correct the roller rationale: restartIfNecessary no-ops on a missing pod;
  the real hazard is the async-deletion race vs inline rollingUpdate()
- Controller refusal is a new parse-time gate (KafkaPool.fromCrd), not "no change"
- Forbid scale-down that would remove a currently-stopped node (049/108/PVC)
- PDB must size from runningNodes(); gate the ISR scan and reuse 049's scan
- Precise listener-status claim (nodePort only); note status.replicas skew
- Call out combined broker+controller exclusion prominently

Signed-off-by: Haifeng Chen <haifengc@twitter.com>
@im-konge

Copy link
Copy Markdown
Member

What does this block in the description mean?

Notes
ID 147 is the next free number (≤144 merged; 145 and 146 are in open PRs https://github.com/strimzi/proposals/pull/227 and https://github.com/strimzi/proposals/pull/229).

You never know what ID of the proposal you will have - that depends when it will be approved and merged. So you don't need to add any number based on your other PRs, it will be anyway something that might change.

Also, as I mentioned in the previous proposal, please have one sentence per line.


You are mentioning scope v1, there will be other scope? Similar to previous proposal, does it need feature gate?

@acgtun

acgtun commented Jun 19, 2026

Copy link
Copy Markdown
Author

mentioning

Thanks @im-konge

@scholzj scholzj left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the proposal. I think my comments are actually pretty similar to #229. I think the idea makes sense. But to be honest, the implementation seems to be a bit overcomplicated. That makes it hard to accept and maintain.

I was thinking about the following:

  • The #229 proposes a mechanism for skipping a broker from rolling updates. I suggested to use an KafkaNodePool or Kafka annotation for it. It would make sure the pod is skipped by the roller and by the readiness checks.
  • One of the comments I raised there was about how the skipped Pod would be updated (its Pod configuration, Kafka configuration, services, ...) and whether it would be recreated when shutdown. Let's assume based on this proposal that in #229 the Pod will be kept up to date in StrimziPodSet and recreated when deleted, but just not rolled.

So why don't we add here a second annotation to stop the Pod from being respawned? When you set such annotation, StrimziPodSetController would never recreate the Pod. That means that you can stop it and it would not be started.

And you can combine it with the annotation from #229 to handle the rest:

  • Make sure the KafkaRoller ignores this Pod
  • Make sure the readiness checks skip the Pod
  • Etc.

That sounds like something that would match the use-case and be pretty straight forward to implement and maintain.

- **Data is retained, not lost.** PVCs are kept; on start the broker recovers its logs and rejoins the ISR. This is the key difference from scale-down.
- **Scale-down of a pool with a stopped member is forbidden.** `stoppedNodes` governs whether a pod runs, *not* pool membership — so the two must not conflict. If a user lowers `replicas` (or uses `remove-node-ids`) such that a stopped node's ID would leave the desired set, that ID enters `removedNodes()` and is treated as a real removal: the 049 check, 108 unregistration, and PVC deletion all fire, destroying exactly what "retain" promised. v1 refuses a scale-down that would remove a currently-stopped node (warning + `.status`); the operator must start the node first, then scale down. (The existing 049 replica-holding check already blocks the common case, but membership removal of a stopped node must be refused explicitly, not left to 049.)
- **Graceful shutdown.** Stopping deletes the pod via the normal background propagation, so the broker receives SIGTERM (forwarded by `tini`) and runs a controlled shutdown (leadership handoff, fence, flush). For partition-heavy brokers the default `terminationGracePeriodSeconds` (30s) may be too short for a clean handoff; operators should set `template.pod.terminationGracePeriodSeconds` accordingly (a longer maintenance grace may be applied on the stop path).
- **PodDisruptionBudget interaction.** A stopped broker has no pod, so it counts against the pool/cluster PDB's available replicas: with `maxUnavailable: 1` a single stop consumes the whole budget and blocks *voluntary* disruptions (host drains, manual rolls) of other brokers — directly relevant since the host-maintenance flow drains nodes. The PDB is therefore recomputed excluding stopped nodes, so the budget reflects only running brokers.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Strictly speaking, the PDB is more or less virtual because the expectation is that you use Drain Cleaner that delegates rolling updates to the Cluster Operator that rolls things as part of the manual rolling update according to the regular availability checks. So I'm not sure how much the PDB matters here.

I'm also not really sure what you suggest. I do not think we want to have some special PDB logic based on the stopped and retained nodes. At the end, the PDBs are fully configurable (although as I said, their role is normally smaller than people might be used to)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Understood — I hadn't considered that the PDB is mostly nominal once Drain Cleaner delegates the actual rolling decisions to the operator. The PDB recomputation is dropped entirely in the rewrite; PDBs stay exactly as they are today.


Stopping a broker takes its replicas offline (they are retained on disk but not served), so partitions it leads/follows lose a replica until it returns.

- **Availability check — stance and cost (open question for review).** If stopping a broker would push any partition below `min.insync.replicas`, the operator raises a loud warning event and a `.status` condition naming the affected partitions; partitions whose only in-sync replica is on the stopped node become unavailable for `acks=all` writes (surfaced via metrics). The proposal's default is **warn-and-allow** (honor the explicit operator action), deliberately laxer than [049](https://github.com/strimzi/proposals/blob/main/049-prevent-broker-scale-down-if-it-contains-partition-replicas.md)'s refuse-by-default — because the impacts differ in kind: scale-down **permanently removes** the broker and risks data loss, whereas a stop **retains data** and the unavailability is temporary and self-healing on restart. Two caveats reviewers should weigh: (1) given the maintenance motivation, **refuse-by-default with an explicit `force` override** may be the safer v1 stance — this is genuinely a judgement call, noted as the alternative. (2) The per-partition ISR check has **no existing home** in a stop path; the only comparable machinery is 049's conditional `describeTopics` scan in `KafkaClusterCreator`. Running a full ISR scan **every reconcile** is Admin-heavy and costly on large clusters, so it must be gated (only when `stoppedNodes` is non-empty) and ideally reuse 049's scan rather than add a second one. The `..._sole_isr_on_stopped_node` / `..._under_min_isr` metrics are produced from that same scan, not an independent one.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TBH, I do not follow what is this trying to suggest. Are you suggesting that the operator should check the availability of partition replicas and report issues as warnings during every reconciliation? For me personally, this might be reasonable. But I'm not sure everyone would agree (+ there are also other tools how to monitor that with metrics etc.). But in any case, that sounds more like a separate proposal to me.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed — the per-reconcile ISR scan and the partition-level metrics are gone. The rewrite just states the consequence plainly (a stopped broker's partitions lose a replica; below min-ISR, acks=all producers fail until it returns) and treats the stop as an explicit administrative action, without the operator trying to measure or prevent the impact. If cluster-wide availability reporting is worth having, it deserves its own proposal, as you say.


- **Availability check — stance and cost (open question for review).** If stopping a broker would push any partition below `min.insync.replicas`, the operator raises a loud warning event and a `.status` condition naming the affected partitions; partitions whose only in-sync replica is on the stopped node become unavailable for `acks=all` writes (surfaced via metrics). The proposal's default is **warn-and-allow** (honor the explicit operator action), deliberately laxer than [049](https://github.com/strimzi/proposals/blob/main/049-prevent-broker-scale-down-if-it-contains-partition-replicas.md)'s refuse-by-default — because the impacts differ in kind: scale-down **permanently removes** the broker and risks data loss, whereas a stop **retains data** and the unavailability is temporary and self-healing on restart. Two caveats reviewers should weigh: (1) given the maintenance motivation, **refuse-by-default with an explicit `force` override** may be the safer v1 stance — this is genuinely a judgement call, noted as the alternative. (2) The per-partition ISR check has **no existing home** in a stop path; the only comparable machinery is 049's conditional `describeTopics` scan in `KafkaClusterCreator`. Running a full ISR scan **every reconcile** is Admin-heavy and costly on large clusters, so it must be gated (only when `stoppedNodes` is non-empty) and ideally reuse 049's scan rather than add a second one. The `..._sole_isr_on_stopped_node` / `..._under_min_isr` metrics are produced from that same scan, not an independent one.
- **Data is retained, not lost.** PVCs are kept; on start the broker recovers its logs and rejoins the ISR. This is the key difference from scale-down.
- **Scale-down of a pool with a stopped member is forbidden.** `stoppedNodes` governs whether a pod runs, *not* pool membership — so the two must not conflict. If a user lowers `replicas` (or uses `remove-node-ids`) such that a stopped node's ID would leave the desired set, that ID enters `removedNodes()` and is treated as a real removal: the 049 check, 108 unregistration, and PVC deletion all fire, destroying exactly what "retain" promised. v1 refuses a scale-down that would remove a currently-stopped node (warning + `.status`); the operator must start the node first, then scale down. (The existing 049 replica-holding check already blocks the common case, but membership removal of a stopped node must be refused explicitly, not left to 049.)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This sounds like unnecessary complication to me:

  • You control things, so you can just decide to not scale down (or for example disable HPAs if you have auto-scaling)
  • Even if you scale down, it is pretty likely that the scaledown would not succeed because the data cannot be moved from the broker being scaled down?
  • If the scale down can actually safely happen, why not scale down?

Dropping this would make this change easier to implement and maintain and more likely to be approved. So I think you should carefully consider if it is really needed.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right, and your second point settles it: a stopped broker cannot move its replicas away, so the existing scale-down check blocks the removal anyway — the dedicated refusal logic was redundant. Dropped; the rewrite states that no new scale-down logic is needed and links the existing check.


### Feature gate

A `StopKafkaNode` feature gate, default **disabled** in the introducing release, graduating alpha → beta → GA. The CRD always accepts `spec.stoppedNodes` (Kubernetes admission does not know about feature gates), but when the gate is disabled the operator ignores it entirely — no pod omission and no `StopRefused` conditions (so users do not see refusals for entries that would be valid). The gate is warranted because this lets a human take a broker offline indefinitely — a new, availability-relevant behavior.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similarly to the other proposal. It is not clear to me why the feature gate is needed if this feature requires some special steps to activate anyway.

Should there be a feature gate, it would be food to suggest the graduation schedule at least theoretically ... e.g. 3 releases in alpha, 2 in beta, GA afterwards etc.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed — the feature is opt-in by annotation and an absent annotation leaves behavior exactly as today, so a gate adds ceremony without protection. The rewrite drops it and records the reasoning under rejected alternatives.


### Status and metrics

- A `Kafka` CR `.status` condition (`type: NodeStopped`) listing stopped node IDs and, per node, the time it was stopped ("stopped since `<timestamp>`" via `lastTransitionTime`). Mirror per-node detail in the owning `KafkaNodePool.status`; a node that resolves to a controller shows `StopRefused` instead. This condition is informational and is **not** folded into the `Ready` condition.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the condition is not unreasonable. But the timestamps might not make sense. Especially since in most situations when this would make sense it would not be the operator stopping the brokers.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point — in the new design the operator only observes that the pod is gone and keeps it gone, so a "stopped since" timestamp owned by the operator would be misleading. The condition in the rewrite just lists the stopped node IDs (plus any ignored entries), with no timestamps.

@Frawless Frawless left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a SME, but having the logic simple with annotation sounds to me like much a better idea than changing CRs API.

And as for previous proposal - making it less verbose will be much better to make it more readable for everyone which will also make the reviews easier and quickier.


## Motivation

The driving use case is **planned host maintenance** where the same broker must return with its data intact: take the broker offline so the underlying Kubernetes host (or its disks) can be serviced, then bring the identical broker back. Because the broker's storage and identity are retained, on restart it rejoins, runs log recovery, and catches up — no partition reassignment, no re-replication of the full dataset, no node ID churn.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't usage of DrainClainer suitable solution for that? Move the broker to different node gracefully?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For networked storage, yes — draining moves the pod and the volume follows, and the rewrite says so explicitly. As you spotted in the next line, the feature is for the cases where moving is impossible: local or host-attached storage pins the pod to one host, or the host itself is the maintenance target. Thanks for confirming the text answered it; I've kept that framing prominent in the motivation.


This earns its keep specifically when the broker's pod **cannot** simply be rescheduled elsewhere:

- **Local / host-attached storage** (`PersistentVolume`s bound `WaitForFirstConsumer` to a specific host). Draining the host cannot move the broker — its pod just goes `Pending` on the gone node — so there is no graceful way to take it down and bring it back on the same disks today.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Guess this answers my previous question


- **Brokers only.** A stop targeting a controller node (controller-only or combined broker+controller) is **refused** — the entry is ignored, a `.status` condition and warning event explain why, and the operator keeps managing that node. Stopping a controller removes it from the KRaft metadata quorum, which needs dedicated quorum-safety machinery; that is future work.
- **Known limitation — combined-role deployments.** Because a combined broker+controller node *is* a controller, every stop targeting it is refused. In a fully **combined-mode** cluster (every node holds both roles, common on small KRaft deployments — and exactly the clusters most likely to run the host-attached storage this feature targets) v1 can stop *nothing*. The feature is only useful where dedicated broker-only nodes exist; combined-node stops arrive with the controller work.
- **At least one broker must remain running (enforced).** Despite the title's "a single broker," `stoppedNodes` is a per-pool `array<integer>` and nothing structurally bounds it. The operator must refuse a stop that would leave **zero** running brokers cluster-wide (ignored entry + warning + `.status`), because the bootstrap-dependent reconcile steps below would otherwise wedge regardless of `runningNodes()`. v1's safe stance is "one broker at a time"; stopping enough brokers to break bootstrap/min-ISR is the operator's responsibility, surfaced loudly (see Safety).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we really enforce this on KafkaRoller/StrimziPodSetController? Like I would expect the current rolling logic will handle it and in case user will stop 3 pods from 3 it is his fault.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed — the enforcement is dropped. The rewrite says the operator does not limit how many brokers can be stopped, and that the consequences are the same as taking those brokers down manually and are the user's responsibility.

@ppatierno ppatierno left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am so sorry but I had to stop reading half way through after leaving some comments.
I see this proposal very poor written by AI and also not well checked by a human before being submitted.

- `StrimziPodSetController` treats `spec.pods` as absolute: it creates any listed pod that is missing (`maybeCreateOrPatchPod`) and **deletes any matching pod not listed** (`removeDeletedPods`). A terminal-state pod is deleted and recreated. So `kubectl delete pod` always bounces back, and hand-editing the `StrimziPodSet` is overwritten on the next `KafkaReconciler` run.
- The Kafka container command is hardcoded to `/opt/kafka/kafka_run.sh`, which unconditionally execs `kafka-server-start.sh`. There is no idle/sleep mode and no per-node command override.
- `KafkaRoller` assumes every input node should be running. (It does not throw on a *missing* pod — `restartIfNecessary` no-ops when `podOperations.get(...)` returns `null`; the `FatalProblem("Pod is unschedulable or is not starting")` fires only for a pod that *exists* and is stuck/Pending. The hazard for a stopped node is therefore not that path but the transient window described under "Readiness and reconciliation.")
- No annotation or CRD field expresses stop/park/offline; no `.status` field tracks per-node running state.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

imho the above is a very poor AI generated paragraph with a lot of code reference which doesn't make clear what's the current situation to anyone who isn't a maintainer (and knows the code).

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right — that paragraph assumed the reader knows the operator internals. The current-situation section is rewritten as four plain sentences (pod deletion bounces back, scale-down is permanent removal, whole-CR pause freezes everything else) with no class or method names. Code specifics now appear only in the short mechanism section, where they're the actual subject.

- `KafkaRoller` assumes every input node should be running. (It does not throw on a *missing* pod — `restartIfNecessary` no-ops when `podOperations.get(...)` returns `null`; the `FatalProblem("Pod is unschedulable or is not starting")` fires only for a pod that *exists* and is stuck/Pending. The hazard for a stopped node is therefore not that path but the transient window described under "Readiness and reconciliation.")
- No annotation or CRD field expresses stop/park/offline; no `.status` field tracks per-node running state.

The adjacent controls all do something else: `strimzi.io/pause-reconciliation` freezes the whole CR but does not stop Kafka; `strimzi.io/manual-rolling-update` restarts a pod; scale-down + `strimzi.io/skip-broker-scaledown-check` permanently removes a node; `strimzi.io/delete-pod-and-pvc` deletes then recreates.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why strimzi.io/skip-broker-scaledown-check annotation was mentioned here? It serves a different purpose.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, it served a different purpose and only added noise — removed in the rewrite.


Doing this today forces a bad choice:

- **Scale down** to drop the broker, then scale back up — but that permanently removes the node, reassigns/loses its replicas, unregisters it (108), and the broker that comes back is a *different* node ID with empty storage that must re-replicate everything.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unregisters it (108)

What's 108? Actually I know what it is but it's another example of a badly AI generated text which was not reviewed properly by the author to make it clear to a human reader.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair — a bare number is meaningless to anyone who doesn't already know the proposal index. The rewrite drops the numeric references and describes the behavior in words ("the node is unregistered from the KRaft metadata"); the one remaining cross-reference is a named link to the scale-down check proposal.

### Scope (v1)

- **Brokers only.** A stop targeting a controller node (controller-only or combined broker+controller) is **refused** — the entry is ignored, a `.status` condition and warning event explain why, and the operator keeps managing that node. Stopping a controller removes it from the KRaft metadata quorum, which needs dedicated quorum-safety machinery; that is future work.
- **Known limitation — combined-role deployments.** Because a combined broker+controller node *is* a controller, every stop targeting it is refused. In a fully **combined-mode** cluster (every node holds both roles, common on small KRaft deployments — and exactly the clusters most likely to run the host-attached storage this feature targets) v1 can stop *nothing*. The feature is only useful where dedicated broker-only nodes exist; combined-node stops arrive with the controller work.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is actually very similar to the previous point where "combined" is mentioned.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reworded — the scope section now spells it out: "a node with the controller role (whether controller-only or with both the controller and broker roles)".


### How a node is taken offline

The key design point is the distinction between two node sets. `KafkaCluster.nodes()` stays the **desired-and-retained** set (it still includes a stopped node), so everything that should keep working for a stopped node keeps working unchanged: PVC generation, certificate Secrets, per-broker `ConfigMap`s, listener/Service generation, `KafkaNodePool.status.nodeIds`, the 049 scale-down check (sees no removal), and 108 unregistration suppression (sees the node as still present). A new accessor, **`runningNodes()` = `nodes()` minus stopped nodes**, is introduced for every place that requires a node to be *live right now*.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I found reading this sentence pretty hard. Clearly not written or checked by a human.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That sentence didn't survive the rewrite — the whole section was redone in plain prose, one sentence per line. I hope the new version reads like it was written for a human this time.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was this answer written by a human? 🤔

@acgtun

acgtun commented Jul 8, 2026

Copy link
Copy Markdown
Author

So why don't we add here a second annotation to stop the Pod from being respawned? When you set such annotation, StrimziPodSetController would never recreate the Pod. That means that you can stop it and it would not be started.

@scholzj Thank you for the concrete direction — I've rewritten the proposal around it. The trigger is now a KafkaNodePool annotation (strimzi.io/stopped-nodes, same node-ID list format as next-node-ids / remove-node-ids), and the core mechanism is exactly what you describe: the StrimziPodSetController will not recreate a pod for a listed node, so you stop the broker by deleting the pod (or letting a host drain evict it) and it stays down. The pod entry stays in the StrimziPodSet and is kept up to date, so the broker restarts with current configuration. The spec.stoppedNodes field, the PDB logic, the scale-down refusal, the ISR scanning, and the feature gate are all gone; the rewrite is about a third shorter.

One adaptation I'd like your view on: rather than requiring the #229 annotation to be set alongside this one, the proposal makes stopped-nodes itself imply the roller and readiness exclusions. My reasoning is that a stopped pod can never become ready, so a user who sets stopped-nodes but forgets the second annotation would wedge the whole Kafka CR NotReady on every reconcile — the combination isn't really a choice, it's a requirement, and forgetting it is an easy footgun. It also keeps the two proposals from depending on each other. The two annotations remain complementary: skip-rolling-update is for a node that is still running, stopped-nodes for one that is intentionally absent. If you'd rather keep them strictly composed as you sketched, I'm happy to change it — the rest of the design is the same either way.

@acgtun

acgtun commented Jul 8, 2026

Copy link
Copy Markdown
Author

Not a SME, but having the logic simple with annotation sounds to me like much a better idea than changing CRs API.

@Frawless Agreed on both — the rewrite uses a KafkaNodePool annotation instead of a spec field, and the text is down from 171 lines to about 110, one sentence per line. Thanks for the push on readability; it forced out a lot of machinery that didn't need to be there.

@acgtun

acgtun commented Jul 8, 2026

Copy link
Copy Markdown
Author

I see this proposal very poor written by AI and also not well checked by a human before being submitted.

@ppatierno That's fair, and I'm sorry for the review burden it caused — the sections you flagged leaned on internal code references and bare proposal numbers that only make sense to someone who already knows the codebase, which defeats the point of a proposal. I've rewritten it from scratch around Jakub's suggested design: plain prose, one sentence per line, no bare code symbols in the motivation or current-situation sections, and every cross-reference is now a named link. It's also about a third of the original length. I do use AI as a drafting tool, but the checking is my responsibility, and I'd appreciate you giving the rewrite a fresh look when you have time.

@acgtun

acgtun commented Jul 8, 2026

Copy link
Copy Markdown
Author

@im-konge Following up properly on your points now that the rewrite is pushed: the proposal-ID note is gone from the PR description (you're right, the number is whatever it is at merge time); the text is one sentence per line throughout; the "v1" scope language is gone (broker-only is stated as the scope, controller support as future work); and the feature gate is dropped — as an opt-in annotation with unchanged default behavior it didn't warrant one.

@acgtun

acgtun commented Jul 8, 2026

Copy link
Copy Markdown
Author

Thank you @scholzj, @Frawless, @ppatierno, and @im-konge for the review. I've rewritten the proposal from scratch around Jakub's suggested design and it's now about a third of the original length. The main changes:

  • The trigger is a KafkaNodePool annotation, strimzi.io/stopped-nodes (same node-ID list format as the existing node-ID annotations), instead of a spec field.
  • The core mechanism is the one Jakub sketched: the StrimziPodSetController does not (re)create a pod for a listed node, so you stop the broker by deleting the pod (or letting a host drain evict it) and it stays down; removing the ID brings it back with the same identity, storage, and current configuration.
  • Dropped: the PDB recomputation, the scale-down refusal logic, the per-reconcile ISR scan and partition metrics, the at-least-one-broker enforcement, and the feature gate.
  • The status condition lists stopped node IDs without timestamps.
  • The text is rewritten in plain prose, one sentence per line, without bare code or proposal-number references.

One open point flagged inline for Jakub: the rewrite makes the annotation self-contained (it implies the roller and readiness exclusions) instead of requiring the #229 annotation to be set alongside it, because forgetting the second annotation would wedge the whole Kafka CR NotReady. Happy to go either way based on the discussion there.

@ppatierno

Copy link
Copy Markdown
Member

@acgtun I think you should reduce the usage of AI tbh. That's my personal view. I think most of the answers are AI generated and also you keep writing (even on other proposals) a sort of summary for each reviewer + a global summary for all of them. Is it really worth the time for the reviewers to read all this content?

@acgtun

acgtun commented Jul 8, 2026

Copy link
Copy Markdown
Author

@acgtun I think you should reduce the usage of AI tbh. That's my personal view. I think most of the answers are AI generated and also you keep writing (even on other proposals) a sort of summary for each reviewer + a global summary for all of them. Is it really worth the time for the reviewers to read all this content?

Thanks @ppatierno for the feedback. Sorry for this. I will balance this.

@acgtun

acgtun commented Jul 13, 2026

Copy link
Copy Markdown
Author

thanks all for the discussion. i will close this for now. And work on it later if still needed. thank again.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants