fix(controller): re-drain workloadRef Deployment on later revisions. Fixes #4935 - #4940
Open
michaelgriskified wants to merge 4 commits into
Conversation
…ixes argoproj#4935 With workloadRef.scaleDown: progressively, the drain of the referenced Deployment was gated on revision == 1. Anything that scaled the Deployment back up after the initial migration — most commonly a GitOps controller re-applying the Deployment manifest with its original replica count — left the Rollout and the Deployment both running at full size indefinitely, with no recovery path other than deleting the Rollout or patching the Deployment by hand. Nothing reports unhealthy in that state. Drain to 0 whenever the Rollout is Healthy, on any revision, so the controller converges on the documented end state instead of getting one chance at it. This also covers the case the removed NOTE described: a failed revision 1 whose fix lands in revision 2+. The availability branch (scaling the Deployment to the shortfall while the Rollout is not yet Healthy) stays scoped to revision 1. On later revisions the stable ReplicaSet already serves that capacity, so scaling the Deployment up there would add capacity rather than preserve it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: michaelgriskified <michael.bg@riskified.com>
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #4940 +/- ##
=======================================
Coverage 85.20% 85.20%
=======================================
Files 166 166
Lines 19437 19437
=======================================
Hits 16561 16561
Misses 2026 2026
Partials 850 850
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Contributor
Published E2E Test Results 4 files 4 suites 4h 10m 30s ⏱️ Results for commit 274cd5f. ♻️ This comment has been updated with latest results. |
Contributor
Published Unit Test Results2 624 tests 2 624 ✅ 3m 30s ⏱️ Results for commit 274cd5f. ♻️ This comment has been updated with latest results. |
|
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 #4935.
The bug
With
spec.workloadRef.scaleDown: progressively, the drain of the referenced Deployment was gated onrevision == 1. If anything scales the Deployment back up after the initial migration, nothing ever drains it again:replicas: Nin git) on the next sync, or an operator scales it by hand.spec.replicasfield ownership moves away from the rollouts controller.revision >= 2, so the gate never matches. Rollout stable RS at N and Deployment RS at N, permanently, for a service sized at N.Nothing reports unhealthy: the Rollout is Healthy, the pods are Ready, the GitOps app is Synced. The only symptom is doubled capacity. Recovery requires deleting the Rollout and re-migrating, or patching the Deployment by hand.
For Argo CD users specifically,
ignoreDifferenceson the Deployment'sreplicasis not enough — it only suppresses the diff, while the apply still writes git's value. Avoiding step 2 requires the separateRespectIgnoreDifferences=truesync option (or omittingreplicasfrom the manifest under server-side apply), and there is no in-cluster signal that you got it wrong until you notice the bill.The change
Drain to 0 whenever the Rollout is
Healthy, on any revision, instead of only on revision 1. The controller then converges on the documented end state (as the Rollout is scaled up the Deployment is scaled down) rather than getting exactly one chance at it. This also resolves the case the removedNOTEdescribed — a failed revision 1 whose fix lands in revision 2+.The availability branch (scaling the Deployment to
spec.replicas - status.readyReplicaswhile the Rollout is not yet Healthy) stays scoped torevision == 1. That branch exists to hold total capacity steady during the initial handover; on a later revision the stable ReplicaSet already serves that capacity, so running it there would add capacity — and would resurrect the Deployment mid-canary on every update. There's a new test case pinning that.scaleDeploymentalready no-ops when the Deployment is at the target replica count, so a steady-state Healthy Rollout costs one extra GET per reconcile and no writes — the same cost revision-1 Rollouts already pay today.Behavior change for existing users
This changes existing behavior, deliberately: a Deployment currently sitting at full size behind a Healthy Rollout with
scaleDown: progressivelywill be scaled to 0 on the first reconcile after upgrade. That is the documented intent of the setting, and for anyone in the state described above it is the recovery they'd otherwise have to perform by hand. Worth a release note.The pre-existing unit case named
Do not scale deploymentasserted the old gate (revision 2 + Healthy + Deployment at 5 → stays 5); it's been renamed and flipped to expect 0, which is the crux of the fix.Tests
rollout/replicaset_test.go, inTestScaleDownProgressively:Rollout healthy on a later revision - Deployment scaled to 0— the regression. Verified it fails on unmodifiedmaster(expected: 0, actual: 5) and passes with the change.Rollout progressing on a later revision - Deployment untouched— guards the scoping of the availability branch, so this fix can't turn into a mid-canary scale-up.go test ./rollout/...passes locally, as doesgo build ./....I did not add an e2e case: reproducing this needs an external actor scaling the referenced Deployment back up between revisions, and I'd rather not push e2e code I can't run end-to-end here. The existing
TestScaleDownProgressivelyCompleted*suites pass unchanged. Happy to add one if you'd like it, or to hand over the exact repro.Checklist