fix(cruisecontrol): sequence CC rolls around scaling operations (fixes #301) - #304
Open
amuraru wants to merge 3 commits into
Open
fix(cruisecontrol): sequence CC rolls around scaling operations (fixes #301)#304amuraru wants to merge 3 commits into
amuraru wants to merge 3 commits into
Conversation
13 tasks
…e#301) A broker add/remove regenerates capacity.json, which is hashed into the CruiseControl pod template and rolls the CC Deployment. Because the CC roll (KafkaClusterReconciler) and the add_broker/remove_broker CruiseControlOperation (CruiseControlOperationReconciler) are driven by two uncoordinated controllers, a roll can hit an in-flight op: the fresh CC pod loses the in-memory task and resets its metric-sampling window, stalling scaling. Worse, for removal it is a chicken-and-egg: marking a broker GracefulDownscaleRequired requires a successful cc.BrokersWithState() (CC REST-ready), but in the pre-downscale window capacity.json is regenerated with a fallback entry for the departing broker, rolling CC and keeping it un-ready - so the downscale is never marked and no remove_broker operation is ever created. Sequence the roll instead of removing it (keeps exact per-broker capacity; no dependency on capacity estimation or a "-1" default): - Reuse the deployed capacity.json for the whole removal window, including before the downscale is marked: isBrokerRemovalPending (broker in status but dropped from spec) now also loads the existing ConfigMap so GenerateCapacityConfig short-circuits. CC is not rolled during removal -> stays ready -> the downscale is marked and the remove_broker op is created. - Gate the operation controller so it defers an add_broker/remove_broker while the CC Deployment is mid-rollout (requeueIfCCDeploymentNotRolledOut / isDeploymentRolledOut), so an add runs against a settled CC that already loaded the new broker's capacity, and no op is submitted against a rolling CC. No deadlock: reuse is scoped to removal; an add still rolls CC (new broker in spec, no removal pending) and the op simply waits for the roll to settle. Add unit tests: TestIsBrokerRemovalPending, TestIsDeploymentRolledOut. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…be#301) Upscale a KRaft cluster from 3 broker-only nodes to 4 (add broker 6) and then downscale back to 3, asserting Cruise Control drives each direction with exactly one add_broker / remove_broker operation, the broker-only pod count tracks the spec, and the 3 controller-only pods are never touched. Exercises BOTH directions on purpose: upscale is the path a "stop hashing capacity.json" fix would have silently broken (the capacity roll loads the new broker's exact capacity), and downscale is the path that stalled on master. Uses 4-to-3 and back so at least 3 brokers remain for RF=3 / min.insync.replicas=3 during the reassignment, reusing the existing 3-broker KRaft install. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
amuraru
force-pushed
the
cc-restart-race
branch
from
August 12, 2026 09:43
354f708 to
bd1060a
Compare
Author
|
Reworked from the earlier |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #301.
The race
A broker add/remove regenerates
capacity.json, which is hashed into the CruiseControl pod-template annotation and rolls the CC Deployment. The roll (KafkaClusterReconciler) and theadd_broker/remove_brokerCruiseControlOperation(CruiseControlOperationReconciler) are driven by two uncoordinated controllers, so a roll can land on an in-flight op — the fresh CC pod loses its in-memory task and resets its metric-sampling window, stalling scaling.For removal it's a chicken-and-egg: marking a broker
GracefulDownscaleRequiredrequires a successfulcc.BrokersWithState()(CC REST-ready), but in the pre-downscale windowcapacity.jsonis regenerated with a fallback entry for the departing broker, which rolls CC and keeps it un-ready — so the downscale is never marked and noremove_brokerop is ever created. This is what stalled the KRaft removal e2e onmaster.Fix — sequence the roll (keep exact per-broker capacity)
capacity.jsonfor the whole removal window, including before the downscale is marked.GenerateCapacityConfigalready reuses the deployed config once a broker is in a downscale state; broaden the guard withisBrokerRemovalPending(a broker present in status but dropped from spec) so CC is not rolled during removal → CC stays ready → the downscale is marked → theremove_brokerop is created. (This is the load-bearing change.)requeueIfCCDeploymentNotRolledOut/isDeploymentRolledOut): defer anadd_broker/remove_brokerwhile the CC Deployment is mid-rollout, so an add runs against a settled CC that has already loaded the new broker's exact capacity, and no op is submitted against a rolling CC. Stop-execution is never gated.No deadlock (asymmetric): removal reuses
capacity.json(no roll); an add still rolls CC (new broker in spec, no removal pending) and the op simply waits for that roll to settle. No estimation /-1dependency, so it's correct for homogeneous and heterogeneous clusters.Tests
TestIsBrokerRemovalPending,TestIsDeploymentRolledOut.testKRaftBrokerScaling: upscales a KRaft cluster 3→4 brokers (add broker 6) then downscales 4→3, asserting exactly oneadd_brokerthen oneremove_brokeroperation, broker-pod count tracks the spec, and the 3 controller-only pods are never touched. It deliberately covers both directions — upscale is the path the flip would have broken; downscale is the path that stalled on master.Verified locally:
go build ./...,go vet -tags e2e ./...,golangci-lint(0 issues), unit tests, and thecontrollers/testsenvtest suite pass.🤖 Generated with Claude Code