Skip to content

Commit

Permalink
add ignoreMinNodesCheck to allow disabling minimum node size check
Browse files Browse the repository at this point in the history
Signed-off-by: Amir Alavi <amiralavi7@gmail.com>
  • Loading branch information
a7i committed Feb 11, 2024
1 parent 6c865fd commit 27e7675
Show file tree
Hide file tree
Showing 12 changed files with 99 additions and 8 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ These are top level keys in the Descheduler Policy that you can use to configure
| `nodeSelector` |`string`| `nil` | limiting the nodes which are processed. Only used when `nodeFit`=`true` and only by the PreEvictionFilter Extension Point |
| `maxNoOfPodsToEvictPerNode` |`int`| `nil` | maximum number of pods evicted from each node (summed through all strategies) |
| `maxNoOfPodsToEvictPerNamespace` |`int`| `nil` | maximum number of pods evicted from each namespace (summed through all strategies) |
| `ignoreMinNodesCheck` |`bool`| `nil` | descheduler requires more than one node in the cluster, setting this to `true` will disable the check |

### Evictor Plugin configuration (Default Evictor)

Expand Down
1 change: 1 addition & 0 deletions docs/deprecated/v1alpha1.md
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ evictLocalStoragePods: true
evictSystemCriticalPods: true
maxNoOfPodsToEvictPerNode: 40
ignorePvcPods: false
ignoreMinNodesCheck: false
strategies:
...
```
Expand Down
3 changes: 3 additions & 0 deletions pkg/api/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ type DeschedulerPolicy struct {

// MaxNoOfPodsToEvictPerNamespace restricts maximum of pods to be evicted per namespace.
MaxNoOfPodsToEvictPerNamespace *uint

// IgnoreMinNodesCheck allows skipping the min nodes check, if set to true.
IgnoreMinNodesCheck *bool
}

// Namespaces carries a list of included/excluded namespaces
Expand Down
1 change: 1 addition & 0 deletions pkg/api/v1alpha1/conversion.go
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,7 @@ func V1alpha1ToInternal(
out.NodeSelector = deschedulerPolicy.NodeSelector
out.MaxNoOfPodsToEvictPerNamespace = deschedulerPolicy.MaxNoOfPodsToEvictPerNamespace
out.MaxNoOfPodsToEvictPerNode = deschedulerPolicy.MaxNoOfPodsToEvictPerNode
out.IgnoreMinNodesCheck = deschedulerPolicy.IgnoreMinNodesCheck

return nil
}
Expand Down
3 changes: 3 additions & 0 deletions pkg/api/v1alpha1/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ type DeschedulerPolicy struct {

// MaxNoOfPodsToEvictPerNamespace restricts maximum of pods to be evicted per namespace.
MaxNoOfPodsToEvictPerNamespace *uint `json:"maxNoOfPodsToEvictPerNamespace,omitempty"`

// IgnoreMinNodesCheck allows skipping the min nodes check, if set to true.
IgnoreMinNodesCheck *bool `json:"ignoreMinNodesCheck,omitempty"`
}

type (
Expand Down
5 changes: 5 additions & 0 deletions pkg/api/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions pkg/api/v1alpha2/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ type DeschedulerPolicy struct {

// MaxNoOfPodsToEvictPerNamespace restricts maximum of pods to be evicted per namespace.
MaxNoOfPodsToEvictPerNamespace *uint `json:"maxNoOfPodsToEvictPerNamespace,omitempty"`

// IgnoreMinNodesCheck allows skipping the min nodes check, if set to true.
IgnoreMinNodesCheck *bool `json:"ignoreMinNodesCheck,omitempty"`
}

type DeschedulerProfile struct {
Expand Down
2 changes: 2 additions & 0 deletions pkg/api/v1alpha2/zz_generated.conversion.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions pkg/api/v1alpha2/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions pkg/api/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 9 additions & 8 deletions pkg/descheduler/descheduler.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,25 +25,25 @@ import (

"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/trace"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/client-go/discovery"
"k8s.io/client-go/informers"
"k8s.io/client-go/tools/events"
componentbaseconfig "k8s.io/component-base/config"
"k8s.io/klog/v2"

v1 "k8s.io/api/core/v1"
policy "k8s.io/api/policy/v1"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
utilversion "k8s.io/apimachinery/pkg/util/version"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/client-go/discovery"
"k8s.io/client-go/informers"
clientset "k8s.io/client-go/kubernetes"
fakeclientset "k8s.io/client-go/kubernetes/fake"
listersv1 "k8s.io/client-go/listers/core/v1"
schedulingv1 "k8s.io/client-go/listers/scheduling/v1"
core "k8s.io/client-go/testing"
"k8s.io/client-go/tools/events"
componentbaseconfig "k8s.io/component-base/config"
"k8s.io/klog/v2"
"k8s.io/utils/ptr"

"sigs.k8s.io/descheduler/pkg/descheduler/client"
eutils "sigs.k8s.io/descheduler/pkg/descheduler/evictions/utils"
Expand Down Expand Up @@ -116,8 +116,9 @@ func (d *descheduler) runDeschedulerLoop(ctx context.Context, nodes []*v1.Node)
metrics.DeschedulerLoopDuration.With(map[string]string{}).Observe(time.Since(loopStartDuration).Seconds())
}(time.Now())

shouldCheckMinNodes := !ptr.Deref(d.deschedulerPolicy.IgnoreMinNodesCheck, false)
// if len is still <= 1 error out
if len(nodes) <= 1 {
if shouldCheckMinNodes && len(nodes) <= 1 {
klog.V(1).InfoS("The cluster size is 0 or 1 meaning eviction causes service disruption or degradation. So aborting..")
return fmt.Errorf("the cluster size is 0 or 1")
}
Expand Down
61 changes: 61 additions & 0 deletions pkg/descheduler/descheduler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,11 @@ import (
"k8s.io/apimachinery/pkg/runtime"
apiversion "k8s.io/apimachinery/pkg/version"
fakediscovery "k8s.io/client-go/discovery/fake"
"k8s.io/client-go/informers"
fakeclientset "k8s.io/client-go/kubernetes/fake"
core "k8s.io/client-go/testing"
"k8s.io/client-go/tools/events"
"k8s.io/utils/ptr"
"sigs.k8s.io/descheduler/cmd/descheduler/app/options"
"sigs.k8s.io/descheduler/pkg/api"
"sigs.k8s.io/descheduler/pkg/api/v1alpha1"
Expand Down Expand Up @@ -248,6 +251,64 @@ func TestRootCancelWithNoInterval(t *testing.T) {
}
}

func TestRunDeschedulerLoop(t *testing.T) {
n1 := test.BuildTestNode("n1", 2000, 3000, 10, nil)
n2 := test.BuildTestNode("n2", 2000, 3000, 10, nil)

testCases := map[string]struct {
expectError bool
deschedulerPolicy *api.DeschedulerPolicy
nodes []*v1.Node
}{
"nodes=1, IgnoreMinNodesCheck=true, no error": {
expectError: false,
deschedulerPolicy: &api.DeschedulerPolicy{IgnoreMinNodesCheck: ptr.To[bool](true)},
nodes: []*v1.Node{n1},
},
"nodes=1, IgnoreMinNodesCheck=false, has error": {
expectError: true,
deschedulerPolicy: &api.DeschedulerPolicy{IgnoreMinNodesCheck: ptr.To[bool](false)},
nodes: []*v1.Node{n1},
},
"nodes=1, IgnoreMinNodesCheck=nil, has error": {
expectError: true,
deschedulerPolicy: &api.DeschedulerPolicy{IgnoreMinNodesCheck: nil},
nodes: []*v1.Node{n1},
},
"nodes=2, IgnoreMinNodesCheck=false, no error": {
expectError: false,
deschedulerPolicy: &api.DeschedulerPolicy{IgnoreMinNodesCheck: ptr.To[bool](false)},
nodes: []*v1.Node{n1, n2},
},
"nodes=2, IgnoreMinNodesCheck=true, no error": {
expectError: false,
deschedulerPolicy: &api.DeschedulerPolicy{IgnoreMinNodesCheck: ptr.To[bool](true)},
nodes: []*v1.Node{n1, n2},
},
}

for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
ctx := context.Background()
fakeClient := fakeclientset.NewSimpleClientset(n1, n2)

eventRecorder := &events.FakeRecorder{}
sharedInformerFactory := informers.NewSharedInformerFactory(fakeClient, 0)

instance, err := newDescheduler(ctx, &options.DeschedulerServer{}, tc.deschedulerPolicy, "v1", eventRecorder, sharedInformerFactory)
if err != nil {
t.Fatalf("unable to initialize descheduler")
}

err = instance.runDeschedulerLoop(ctx, tc.nodes)
hasError := err != nil
if tc.expectError != hasError {
t.Errorf("unexpected descheduler loop behavior. err %v", err)
}
})
}
}

func TestValidateVersionCompatibility(t *testing.T) {
type testCase struct {
name string
Expand Down

0 comments on commit 27e7675

Please sign in to comment.