-
Notifications
You must be signed in to change notification settings - Fork 758
feat: Adding a feature maxNodesToProcess to HighNodeUtilization Plugin to limit the nodes processed on each execution
#1706
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
|
Welcome @jonahjon! |
|
Hi @jonahjon. Thanks for your PR. I'm waiting for a kubernetes-sigs member to verify that this patch is reasonable to test. If it is, they should reply with Once the patch is verified, the new status will be reflected by the I understand the commands that are listed here. Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. |
|
[APPROVALNOTIFIER] This PR is NOT APPROVED This pull-request has been approved by: The full list of commands accepted by this bot can be found here.
Needs approval from an approver in each of these files:
Approvers can indicate their approval by writing |
maxNodesToProcess to limit the nodes processed on each execution
maxNodesToProcess to limit the nodes processed on each executionmaxNodesToProcess to HighNodeUtilization Plugin to limit the nodes processed on each execution
|
/ok-to-test |
| // MaxNodesToProcess limits the number of nodes to process in each | ||
| // the descheduling execution. This is useful to limit nodes descheduled each run | ||
| // when turning this plugin on within a cluster with many underutilized nodes. | ||
| MaxNodesToProcess int `json:"maxNodesToProcess,omitempty"` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can we add some unit test case in TestHighNodeUtilization? 😄
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, i will add those.
|
I think this change is reasonable to avoid too much change when large-scale nodes run the |
| |Name|Type| | ||
| |---|---| | ||
| |`thresholds`|map(string:int)| | ||
| |`numberOfNodes`|int| |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Worth deprecating numberOfNodes and renaming it to minNodesToProcess. Alternatively, introducing a new nodesToProcessLimits (or a better name) with min and max fields. Are min and max the only useful limits to introduce? Resp. should only a number be introduced? Or, percentages as well?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For us we're specifically looking to slow this down, so unsure if there is other use cases out there. The default settings are aggressive enough I'm not sure if min would be useful for company in any way.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe just supporting max and percentage would be fine? We might care more about avoiding evicting all high utilization nodes. 🤔
type NodeProcessingLimits struct {
Max int32 `json:"max,omitempty"`
MaxPercentage int32 `json:"maxPercentage,omitempty"`
}(The name is just an example, not recommendation.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree with @ingvagabund here, the numberOfNodes configuration relates a lot with the newly introduced maxNodesToProcess. To deprecate the first one and to make both part of the same structure (with min and max properties) would be better here.
I don't think we should be going as far as have "percentages" here except if there is a clear usecase.
|
|
||
| // limit the number of nodes processed each execution if `MaxNodesToProcess` is set | ||
| if h.args.MaxNodesToProcess > 0 && len(lowNodes) > h.args.MaxNodesToProcess { | ||
| lowNodes = lowNodes[:h.args.MaxNodesToProcess] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The index needs to rotate so all nodes are eventually processed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👀
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking at possible ways to do this I see a couple ways to solve it, but would love feedback.
- Add a new field to the struct to remove state from within the function/file
- Add a new constant within the file to track index
- Open to any other opinions/suggestions!
type HighNodeUtilization struct {
handle frameworktypes.Handle
args *HighNodeUtilizationArgs
podFilter func(pod *v1.Pod) bool
criteria []any
resourceNames []v1.ResourceName
highThresholds api.ResourceThresholds
usageClient usageClient
lastProcessedIndex int
}
...
if h.args.MaxNodesToProcess > 0 && len(lowNodes) > h.args.MaxNodesToProcess {
start := h.lastProcessedIndex % len(lowNodes)
rotated := append(lowNodes[start:], lowNodes[:start]...)
lowNodes = rotated[:h.args.MaxNodesToProcess]
h.lastProcessedIndex = (h.lastProcessedIndex + h.args.MaxNodesToProcess) % len(lowNodes)
}
...
// rotateStartIdx is a variable to track the rotation index of MaxNodesToProcess
var rotateStartIdx int
...
if h.args.MaxNodesToProcess > 0 && len(lowNodes) > h.args.MaxNodesToProcess {
start := rotateStartIdx % len(lowNodes)
end := start + h.args.MaxNodesToProcess
var selected []NodeInfo
if end <= len(lowNodes) {
selected = lowNodes[start:end]
} else {
selected = append(lowNodes[start:], lowNodes[:end%len(lowNodes)]...)
}
lowNodes = selected
rotateStartIdx = (rotateStartIdx + h.args.MaxNodesToProcess) % len(lowNodes)
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
An observation: it's not guaranteed the list of nodes will always be correctly ordered. I.e. even the rotated index may not select the nodes uniformly. If this leads to a sub-efficient behavior we can see if sort the nodes based on their names helps.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Option 1. looks good. So the second version of the code changes in the comment. Worth moving the code under a function so it can be unit test separately.
Also, MaxNodesToProcess needs to be at least as NumberOfNodes is if set.
|
PR needs rebase. Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. |
|
|
||
| // limit the number of nodes processed each execution if `MaxNodesToProcess` is set | ||
| if h.args.MaxNodesToProcess > 0 && len(lowNodes) > h.args.MaxNodesToProcess { | ||
| lowNodes = lowNodes[:h.args.MaxNodesToProcess] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
An observation: it's not guaranteed the list of nodes will always be correctly ordered. I.e. even the rotated index may not select the nodes uniformly. If this leads to a sub-efficient behavior we can see if sort the nodes based on their names helps.
|
|
||
| // limit the number of nodes processed each execution if `MaxNodesToProcess` is set | ||
| if h.args.MaxNodesToProcess > 0 && len(lowNodes) > h.args.MaxNodesToProcess { | ||
| lowNodes = lowNodes[:h.args.MaxNodesToProcess] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Option 1. looks good. So the second version of the code changes in the comment. Worth moving the code under a function so it can be unit test separately.
Also, MaxNodesToProcess needs to be at least as NumberOfNodes is if set.
| This parameter can be configured to activate the strategy only when the number of under utilized nodes | ||
| is above the configured value. This could be helpful in large clusters where a few nodes could go | ||
| under utilized frequently or for a short period of time. By default, `numberOfNodes` is set to zero. | ||
| under utilized frequently or for a short period of time. By default, `numberOfNodes` is set to zero. The parameter `maxNodesToProcess` is used to limit how many nodes should be processed by the descheduler plugin on each execution. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The current implementation for MaxNodesToProcess limits the number of underutilized nodes, not all the nodes. s/is used to limit how many nodes should be processed/is used to limit how many underutilized nodes should be processed/ to make this explicitly clear.
Can you also put the new sentence on a separate line? To limit the number of characters per line.
|
@jonahjon again apologies for the delay. I am currently short on resources to allocate more time for upstream reviews. |
| } | ||
|
|
||
| plugin, err := NewHighNodeUtilization(ctx, &HighNodeUtilizationArgs{ | ||
| MaxNodesToProcess: 1, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would not expect this to be necessary. Have you introduced this by mistake ?
| evictedPods: []string{"p1", "p2", "p3", "p4"}, // Any one of these is valid | ||
| evictionModes: nil, | ||
| // We'll set MaxNodesToProcess in the plugin args below | ||
| }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This new test is broken. Also, please make "maxNodesToProcess" part of the test definition so each test can set its own value.
|
The Kubernetes project currently lacks enough contributors to adequately respond to all PRs. This bot triages PRs according to the following rules:
You can:
Please send feedback to sig-contributor-experience at kubernetes/community. /lifecycle stale |
|
@jonahjon: The following tests failed, say
Full PR test history. Your PR dashboard. Please help us cut down on flakes by linking to an open issue when you hit one in your PR. Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. I understand the commands that are listed here. |
Adding a new settings on the HighNodeUtilization plugin called
maxNodesToProcess. This plugins purpose is to limit the amount of nodes processed during each descheduler execution.This is useful as our company is looking to implement descheduler for improved binpacking, but without this guardrail it can potentially impact several nodes at once which could cause service disruptions.
Similar goal to this other open PR. #1616