Skip to content

De-couple cordoning and draining#1119

Open
thiyyakat wants to merge 5 commits into
gardener:masterfrom
thiyyakat:enh/separate-cordon-drain
Open

De-couple cordoning and draining#1119
thiyyakat wants to merge 5 commits into
gardener:masterfrom
thiyyakat:enh/separate-cordon-drain

Conversation

@thiyyakat

@thiyyakat thiyyakat commented Jul 15, 2026

Copy link
Copy Markdown
Member

What this PR does / why we need it:

This PR removes the cordoning activity from RunDrain allowing us to control the two activities separately. This also enables the cordoning during machine preservation's drain to be done without altering spec.Unschedulable, thereby ensuring a user's manual cordoning/uncordoning is always honoured.

The changes in the PR are:

  • Remove function RunCordonOrUncordon
  • Add a new function cordonNode() and call it in the callers of RunDrain().
  • For preservation of failed machines, use custom taint to cordon.
  • Add helper function updateMachineStatusForDrain to update machine status during drain across all drain-xxx functions.

Which issue(s) this PR fixes:
Fixes #1114

Special notes for your reviewer:

IT was run with provider-AWS

The changes were tested by running mcm and mcm-provider-aws locally, targeting a shoot cluster.

Scenarios tested:

  • Drain during rolling update
  • Drain during inplace update
  • Drain during scale-down

Release note:

De-couple cordoning and drain in the various operations that involve draining such as updates and machine preservation. 

@thiyyakat
thiyyakat requested a review from a team as a code owner July 15, 2026 11:44
@gardener-prow gardener-prow Bot added do-not-merge/needs-kind Indicates a PR lacks a `kind/foo` label and requires one. cla: yes Indicates the PR's author has signed the cla-assistant.io CLA. size/L Denotes a PR that changes 100-499 lines, ignoring generated files. labels Jul 15, 2026
@thiyyakat
thiyyakat force-pushed the enh/separate-cordon-drain branch from 0a04d6a to 5299319 Compare July 15, 2026 11:52
- Add a new function `cordonNode()` and call it in the callers of `RunDrain()``.
- For preservation of failed machines, use custom taint to cordon.
- Remove function `RunCordonOrUncordon`
- Add helper function `updateMachineStatusForDrain` to update machine status during drain across all drain-xxx functions.
@thiyyakat
thiyyakat force-pushed the enh/separate-cordon-drain branch from 5299319 to 49ad362 Compare July 15, 2026 12:17
@thiyyakat thiyyakat changed the title De-couple Cordoning and Draining De-couple cordoning and draining Jul 15, 2026

@takoverflow takoverflow 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 changes, would it be possible as part of this PR to combine the three separate drain methods? Since it's getting harder to keep track of which one does what specifically.

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.

Since you've gotten rid of this skipDrain check elsewhere by returning early, why not do it here as well, this would allow for removal of another if-else at L1612. WDYT?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Addressed in 6f42253. The function returns early now when the node name is empty.

return nil
}
if node.Spec.Unschedulable {
klog.V(3).Infof("Scheduling state for node %q is already in desired state", node.Name)

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.

Suggested change
klog.V(3).Infof("Scheduling state for node %q is already in desired state", node.Name)
klog.V(3).Infof("Node %q is already marked as Unschedulable", node.Name)

The log stating desired state without context doesn't really inform what the desired state actually is.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I've tried not to deviate from original behaviour. Will make this change since two of you feel this should be done 👍

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Shall I make it "Node %q is already cordoned"? To maintain consistency with the remaining logs and comments?

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.

Sure, that's better

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Addressed in 96309f2.


// Step 4: remove preservation-related taint regardless of machine phase.
// If machine is in Running, workload can get scheduled onto it.
err = nodeops.RemoveTaintOffNode(ctx, c.targetCoreClient, updatedNode.Name, updatedNode, &v1.Taint{Key: machineutils.NodePreservedTaintKey, Effect: v1.TaintEffectNoSchedule})

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.

Suggested change
err = nodeops.RemoveTaintOffNode(ctx, c.targetCoreClient, updatedNode.Name, updatedNode, &v1.Taint{Key: machineutils.NodePreservedTaintKey, Effect: v1.TaintEffectNoSchedule})
err = nodeops.RemoveTaintOffNode(ctx, c.targetCoreClient, updatedNode.Name, updatedNode, &v1.Taint{
Key: machineutils.NodePreservedTaintKey,
Effect: v1.TaintEffectNoSchedule,
})

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.

What happens if c.targetCoreClient is nil?

// If machine is in Running, workload can get scheduled onto it.
err = nodeops.RemoveTaintOffNode(ctx, c.targetCoreClient, updatedNode.Name, updatedNode, &v1.Taint{Key: machineutils.NodePreservedTaintKey, Effect: v1.TaintEffectNoSchedule})
if err != nil {
return true, err

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 is this returning true?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Should not be returning true. Will make the change.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Changed return value to false. Addressed in b036aac.

}
}

if nodeName == "" {

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 can be moved above the conditions check, since that is useless if the nodeName is ""

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Addressed in b036aac.

if err == nil {
klog.V(3).Infof("(drainNode) For node %q, machine %q, nodeReadyCondition: %s, readOnlyFileSystemCondition: %s", nodeName, machine.Name, nodeReadyCondition, readOnlyFileSystemCondition)
} else if apierrors.IsNotFound(err) {
klog.Warningf("(drainNode) Node %q for machine %q doesn't exist, so drain will finish instantly", nodeName, machine.Name)

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.

Suggested change
klog.Warningf("(drainNode) Node %q for machine %q doesn't exist, so drain will finish instantly", nodeName, machine.Name)
klog.Warningf("(drainNode) Node %q for machine %q doesn't exist. Skipping drain.", nodeName, machine.Name)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Addressed in 96309f2. I've made the changes across all drain functions.

}

// since we do not wish to accidentally uncordon a user-cordoned node after preservation stops,
// we add a taint with effect NoSchedule, before draining the node, instead of setting Spec.Unschedulable = true

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.

Suggested change
// we add a taint with effect NoSchedule, before draining the node, instead of setting Spec.Unschedulable = true
// we add a taint with effect 'NoSchedule' before draining the node, instead of cordoning it.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Changed the comment. Addressed in b036aac.

@aaronfern

Copy link
Copy Markdown
Member

/assign

Comment thread pkg/util/provider/machinecontroller/machine_util.go
Comment thread pkg/util/provider/machinecontroller/machine_util.go Outdated
Comment thread pkg/util/provider/machinecontroller/machine_util.go Outdated
Comment thread pkg/util/provider/machinecontroller/machine_util.go
Comment thread pkg/util/provider/machinecontroller/machine_util.go
} else { // regular drain already waits for vol detach and attach for another node.
description = fmt.Sprintf("Drain successful. %s", machineutils.InitiateVMDeletion)
}
err = fmt.Errorf("%s", description)

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 do not see this err value being used anywhere, and it seems odd (or rather wrong) that a "Drain successful" message is added to an error

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This is the original behaviour, if I am not wrong:

This is aligned with the other functions called in triggerDeletionFlow :

err = fmt.Errorf("Machine deletion in process. %s", description)

err = fmt.Errorf("machine deletion in process: %s", description)

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.

In the original behaviour this was needed because err was returned. Since the final return uses fmt.Errorf("%s", description) now, err ends up not being used

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

You're right. The assignment is useless. Will make the change 👍

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Removed the assignment in commit: cbdbb69

Comment on lines +1748 to +1751
if err != nil {
// Deletion could be triggered when machine is just being created, no node present then
return nil
}

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.

Use apierrors.IsNotFound(err) to filter out cases where node is not found. For all other errors, return the error

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This is the behaviour of the earlier cordoning function:

func (o *Options) RunCordonOrUncordon(ctx context.Context, desired bool) error {
node, err := o.client.CoreV1().Nodes().Get(ctx, o.nodeName, metav1.GetOptions{})
if err != nil {
// Deletion could be triggered when machine is just being created, no node present then
return nil
}

I can change this if you like.

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.

Yes, please, I would like this change to be added.
Any other error should be caught and returned

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Have changed cordonNode() (now moved to node.go) to return all errors except for NotFound errors. Addressed in : cbdbb69

Comment thread pkg/util/provider/machinecontroller/machine_util.go Outdated
@aaronfern

Copy link
Copy Markdown
Member

would it be possible as part of this PR to combine the three separate drain methods? Since it's getting harder to keep track of which one does what specifically.

This is needed, but I think it should not be done in this PR. The scope and complexity will increase considerably. Not to mention additional time to properly test it.
I can open an issue for it, so that it can be picked up separately, and I can pick this up
wdyt? @takoverflow

- Replace "Cordoning" in descriptions with "Drain" on failure of `cordonNode()`.
- Correct return value from `stopPreservationIfActive()`.
- Move warning and early return due to empty node above condition check.
- Change comment as per reviewer's suggestion.
- Change error message on preservation-related taint failure to say "tainting" instead of "cordoning".
@takoverflow

Copy link
Copy Markdown
Member

This is needed, but I think it should not be done in this PR.

Alright, fair enough. That can be tackled later.

- Improve readability of `RemoveTaintOffNode` function call in `stopPreservationIfActive`.
- Correct warning on `NotFound` errors in drain functions.
- Change log in `cordonNode` to improve clarity.
- Add node details to warning when cordoning fails for the force-deletion case.
- Return early from `drainNode` when nodename is empty. Remove `skipDrain`.
@gardener-prow gardener-prow Bot added size/XL Denotes a PR that changes 500-999 lines, ignoring generated files. and removed size/L Denotes a PR that changes 100-499 lines, ignoring generated files. labels Jul 17, 2026
@gardener-prow

gardener-prow Bot commented Jul 17, 2026

Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by:
Once this PR has been reviewed and has the lgtm label, please ask for approval from aaronfern. For more information see the Code Review Process.

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

- Remove unnecessary assignment to `err`.
- Move `cordonNode()` to `node.go`
- Return `err` from `cordonNode()` for errors other than NotFound errors.
}

// cordonNode sets `node.Spec.Unschedulable` to true, if not already set to true
func (c *controller) cordonNode(ctx context.Context, nodeName string) error {

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.

The function below this one, uncordonNodeIfCordoned sets the value for spec.Unschedulable = false.
This could remove a manually set value, as manageMachinePreservation() calls this function when a machine goes from Failed back into Running.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Thanks. Will make the change. I think I'll put this PR on hold until #1118 gets merged. After that will take all this into consideration.

Comment on lines +373 to +376
if err != nil {
return err
}
return nil

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.

Suggested change
if err != nil {
return err
}
return nil
return err

Nit

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Ha ha. Thanks! Good catch!

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Ha ha. Thanks! Good catch!

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Ha ha. Thanks! Good catch!

@thiyyakat

Copy link
Copy Markdown
Member Author

/hold

Putting this PR on hold until #1118 gets merged. This PR will require some preservation related cordoning-uncordoning changes, and requires the changes in #1118 to be included first.

@gardener-prow gardener-prow Bot added the do-not-merge/hold Indicates that a PR should not merge because someone has issued a /hold command. label Jul 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cla: yes Indicates the PR's author has signed the cla-assistant.io CLA. do-not-merge/hold Indicates that a PR should not merge because someone has issued a /hold command. do-not-merge/needs-kind Indicates a PR lacks a `kind/foo` label and requires one. size/XL Denotes a PR that changes 500-999 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

De-couple drain and uncordoning

4 participants