mirror of
https://github.com/kubernetes-sigs/descheduler.git
synced 2026-01-26 05:14:13 +01:00
feat: enable pod protection based on storage classes
this commit introduces a new customization on the existing PodsWithPVC
protection. this new customization allow users to make pods that refer
to a given storage class unevictable.
for example, to protect pods referring to `storage-class-0` and
`storage-class-1` this configuration can be used:
```yaml
apiVersion: "descheduler/v1alpha2"
kind: "DeschedulerPolicy"
profiles:
- name: ProfileName
pluginConfig:
- name: "DefaultEvictor"
args:
podProtections:
extraEnabled:
- PodsWithPVC
config:
PodsWithPVC:
protectedStorageClasses:
- name: storage-class-0
- name: storage-class-1
```
changes introduced by this pr:
1. the descheduler starts to observe persistent volume claims.
1. a new api field was introduced to allow per pod protection config.
1. rbac had to be adjusted (+persistentvolumeclaims).
This commit is contained in:
@@ -16,6 +16,7 @@ package defaultevictor
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"slices"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -28,6 +29,7 @@ import (
|
||||
"k8s.io/apimachinery/pkg/util/uuid"
|
||||
"k8s.io/client-go/informers"
|
||||
"k8s.io/client-go/kubernetes/fake"
|
||||
"k8s.io/klog/v2"
|
||||
"k8s.io/utils/ptr"
|
||||
"sigs.k8s.io/descheduler/pkg/api"
|
||||
evictionutils "sigs.k8s.io/descheduler/pkg/descheduler/evictions/utils"
|
||||
@@ -55,6 +57,7 @@ type testCase struct {
|
||||
ignorePodsWithoutPDB bool
|
||||
podProtections PodProtections
|
||||
noEvictionPolicy NoEvictionPolicy
|
||||
pvcs []*v1.PersistentVolumeClaim
|
||||
}
|
||||
|
||||
func TestDefaultEvictorPreEvictionFilter(t *testing.T) {
|
||||
@@ -879,6 +882,144 @@ func TestDefaultEvictorFilter(t *testing.T) {
|
||||
},
|
||||
result: false,
|
||||
},
|
||||
{
|
||||
description: "Pod using StorageClass is not evicted because 'PodsWithPVC' is in ExtraEnabled",
|
||||
pods: []*v1.Pod{
|
||||
test.BuildTestPod("p23", 400, 0, n1.Name, func(pod *v1.Pod) {
|
||||
pod.ObjectMeta.OwnerReferences = test.GetNormalPodOwnerRefList()
|
||||
pod.Spec.Volumes = []v1.Volume{
|
||||
{
|
||||
Name: "pvc", VolumeSource: v1.VolumeSource{
|
||||
PersistentVolumeClaim: &v1.PersistentVolumeClaimVolumeSource{
|
||||
ClaimName: "foo",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}),
|
||||
},
|
||||
podProtections: PodProtections{
|
||||
ExtraEnabled: []PodProtection{PodsWithPVC},
|
||||
Config: &PodProtectionsConfig{
|
||||
PodsWithPVC: &PodsWithPVCConfig{
|
||||
ProtectedStorageClasses: []ProtectedStorageClass{
|
||||
{
|
||||
Name: "standard",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
pvcs: []*v1.PersistentVolumeClaim{
|
||||
test.BuildTestPVC("foo", "standard"),
|
||||
},
|
||||
result: false,
|
||||
},
|
||||
{
|
||||
description: "Pod using unprotected StorageClass is evicted even though 'PodsWithPVC' is in ExtraEnabled",
|
||||
pods: []*v1.Pod{
|
||||
test.BuildTestPod("p24", 400, 0, n1.Name, func(pod *v1.Pod) {
|
||||
pod.ObjectMeta.OwnerReferences = test.GetNormalPodOwnerRefList()
|
||||
pod.Spec.Volumes = []v1.Volume{
|
||||
{
|
||||
Name: "pvc", VolumeSource: v1.VolumeSource{
|
||||
PersistentVolumeClaim: &v1.PersistentVolumeClaimVolumeSource{
|
||||
ClaimName: "foo",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}),
|
||||
},
|
||||
podProtections: PodProtections{
|
||||
ExtraEnabled: []PodProtection{PodsWithPVC},
|
||||
Config: &PodProtectionsConfig{
|
||||
PodsWithPVC: &PodsWithPVCConfig{
|
||||
ProtectedStorageClasses: []ProtectedStorageClass{
|
||||
{
|
||||
Name: "protected",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
pvcs: []*v1.PersistentVolumeClaim{
|
||||
test.BuildTestPVC("foo", "unprotected"),
|
||||
},
|
||||
result: true,
|
||||
},
|
||||
{
|
||||
description: "Pod using unexisting PVC is not evicted because we cannot determine if storage class is protected or not",
|
||||
pods: []*v1.Pod{
|
||||
test.BuildTestPod("p25", 400, 0, n1.Name, func(pod *v1.Pod) {
|
||||
pod.ObjectMeta.OwnerReferences = test.GetNormalPodOwnerRefList()
|
||||
pod.Spec.Volumes = []v1.Volume{
|
||||
{
|
||||
Name: "pvc", VolumeSource: v1.VolumeSource{
|
||||
PersistentVolumeClaim: &v1.PersistentVolumeClaimVolumeSource{
|
||||
ClaimName: "foo",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}),
|
||||
},
|
||||
podProtections: PodProtections{
|
||||
ExtraEnabled: []PodProtection{PodsWithPVC},
|
||||
Config: &PodProtectionsConfig{
|
||||
PodsWithPVC: &PodsWithPVCConfig{
|
||||
ProtectedStorageClasses: []ProtectedStorageClass{
|
||||
{
|
||||
Name: "protected",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
pvcs: []*v1.PersistentVolumeClaim{},
|
||||
result: false,
|
||||
},
|
||||
{
|
||||
description: "Pod using protected and unprotected StorageClasses is not evicted",
|
||||
pods: []*v1.Pod{
|
||||
test.BuildTestPod("p26", 400, 0, n1.Name, func(pod *v1.Pod) {
|
||||
pod.ObjectMeta.OwnerReferences = test.GetNormalPodOwnerRefList()
|
||||
pod.Spec.Volumes = []v1.Volume{
|
||||
{
|
||||
Name: "protected-pvc", VolumeSource: v1.VolumeSource{
|
||||
PersistentVolumeClaim: &v1.PersistentVolumeClaimVolumeSource{
|
||||
ClaimName: "protected",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "unprotected-pvc", VolumeSource: v1.VolumeSource{
|
||||
PersistentVolumeClaim: &v1.PersistentVolumeClaimVolumeSource{
|
||||
ClaimName: "unprotected",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}),
|
||||
},
|
||||
podProtections: PodProtections{
|
||||
ExtraEnabled: []PodProtection{PodsWithPVC},
|
||||
Config: &PodProtectionsConfig{
|
||||
PodsWithPVC: &PodsWithPVCConfig{
|
||||
ProtectedStorageClasses: []ProtectedStorageClass{
|
||||
{
|
||||
Name: "protected",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
pvcs: []*v1.PersistentVolumeClaim{
|
||||
test.BuildTestPVC("protected", "protected"),
|
||||
test.BuildTestPVC("unprotected", "unprotected"),
|
||||
},
|
||||
result: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
@@ -953,12 +1094,16 @@ func initializePlugin(ctx context.Context, test testCase) (frameworktypes.Plugin
|
||||
for _, pdb := range test.pdbs {
|
||||
objs = append(objs, pdb)
|
||||
}
|
||||
for _, pvc := range test.pvcs {
|
||||
objs = append(objs, pvc)
|
||||
}
|
||||
|
||||
fakeClient := fake.NewSimpleClientset(objs...)
|
||||
|
||||
sharedInformerFactory := informers.NewSharedInformerFactory(fakeClient, 0)
|
||||
podInformer := sharedInformerFactory.Core().V1().Pods().Informer()
|
||||
_ = sharedInformerFactory.Policy().V1().PodDisruptionBudgets().Lister()
|
||||
_ = sharedInformerFactory.Core().V1().PersistentVolumeClaims().Lister()
|
||||
|
||||
getPodsAssignedToNode, err := podutil.BuildGetPodsAssignedToNodeFunc(podInformer)
|
||||
if err != nil {
|
||||
@@ -1117,3 +1262,58 @@ func slicesEqualUnordered(expected, actual []PodProtection) bool {
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func Test_protectedPVCStorageClasses(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
args *DefaultEvictorArgs
|
||||
expected []ProtectedStorageClass
|
||||
}{
|
||||
{
|
||||
name: "no PodProtections config",
|
||||
args: &DefaultEvictorArgs{},
|
||||
expected: nil,
|
||||
},
|
||||
{
|
||||
name: "no PodsWithPVC config",
|
||||
args: &DefaultEvictorArgs{
|
||||
PodProtections: PodProtections{
|
||||
Config: &PodProtectionsConfig{},
|
||||
},
|
||||
},
|
||||
expected: nil,
|
||||
},
|
||||
{
|
||||
name: "storage classes specified",
|
||||
args: &DefaultEvictorArgs{
|
||||
PodProtections: PodProtections{
|
||||
Config: &PodProtectionsConfig{
|
||||
PodsWithPVC: &PodsWithPVCConfig{
|
||||
ProtectedStorageClasses: []ProtectedStorageClass{
|
||||
{Name: "sc1"},
|
||||
{Name: "sc2"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: []ProtectedStorageClass{
|
||||
{Name: "sc1"},
|
||||
{Name: "sc2"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
ev := &DefaultEvictor{
|
||||
logger: klog.NewKlogr(),
|
||||
args: test.args,
|
||||
}
|
||||
result := protectedPVCStorageClasses(ev)
|
||||
if !reflect.DeepEqual(result, test.expected) {
|
||||
t.Errorf("Expected %v, got %v", test.expected, result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user