diff --git a/README.md b/README.md index 9903034621..3c9aae0fee 100644 --- a/README.md +++ b/README.md @@ -637,7 +637,7 @@ This strategy evicts pods that are older than `maxPodLifeTimeSeconds`. You can also specify `states` parameter to **only** evict pods matching the following conditions: - [Pod Phase](https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#pod-phase) status of: `Running`, `Pending` - - [Container State Waiting](https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#container-state-waiting) condition of: `PodInitializing`, `ContainerCreating` + - [Container State Waiting](https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#container-state-waiting) condition of: `PodInitializing`, `ContainerCreating`, `ImagePullBackOff` If a value for `states` or `podStatusPhases` is not specified, Pods in any state (even `Running`) are considered for eviction. diff --git a/pkg/framework/plugins/podlifetime/pod_lifetime_test.go b/pkg/framework/plugins/podlifetime/pod_lifetime_test.go index 086acf3abb..2105da4569 100644 --- a/pkg/framework/plugins/podlifetime/pod_lifetime_test.go +++ b/pkg/framework/plugins/podlifetime/pod_lifetime_test.go @@ -28,6 +28,7 @@ import ( "k8s.io/client-go/informers" "k8s.io/client-go/kubernetes/fake" "k8s.io/client-go/tools/events" + utilptr "k8s.io/utils/ptr" "sigs.k8s.io/descheduler/pkg/descheduler/evictions" podutil "sigs.k8s.io/descheduler/pkg/descheduler/pod" frameworkfake "sigs.k8s.io/descheduler/pkg/framework/fake" @@ -149,6 +150,7 @@ func TestPodLifeTime(t *testing.T) { ignorePvcPods bool maxPodsToEvictPerNode *uint maxPodsToEvictPerNamespace *uint + applyPodsFunc func(pods []*v1.Pod) }{ { description: "Two pods in the `dev` Namespace, 1 is new and 1 very is old. 1 should be evicted.", @@ -303,7 +305,7 @@ func TestPodLifeTime(t *testing.T) { }, pods: []*v1.Pod{p1, p2, p9}, nodes: []*v1.Node{node1}, - maxPodsToEvictPerNamespace: func(i uint) *uint { return &i }(1), + maxPodsToEvictPerNamespace: utilptr.To[uint](1), expectedEvictedPodCount: 1, }, { @@ -313,9 +315,47 @@ func TestPodLifeTime(t *testing.T) { }, pods: []*v1.Pod{p1, p2, p9}, nodes: []*v1.Node{node1}, - maxPodsToEvictPerNode: func(i uint) *uint { return &i }(1), + maxPodsToEvictPerNode: utilptr.To[uint](1), expectedEvictedPodCount: 1, }, + { + description: "1 pod with container status ImagePullBackOff should be evicted", + args: &PodLifeTimeArgs{ + MaxPodLifeTimeSeconds: &maxLifeTime, + States: []string{"ImagePullBackOff"}, + }, + pods: []*v1.Pod{p9}, + nodes: []*v1.Node{node1}, + expectedEvictedPodCount: 1, + applyPodsFunc: func(pods []*v1.Pod) { + pods[0].Status.ContainerStatuses = []v1.ContainerStatus{ + { + State: v1.ContainerState{ + Waiting: &v1.ContainerStateWaiting{Reason: "ImagePullBackOff"}, + }, + }, + } + }, + }, + { + description: "1 pod without ImagePullBackOff States should be ignored", + args: &PodLifeTimeArgs{ + MaxPodLifeTimeSeconds: &maxLifeTime, + States: []string{"ContainerCreating"}, + }, + pods: []*v1.Pod{p9}, + nodes: []*v1.Node{node1}, + expectedEvictedPodCount: 0, + applyPodsFunc: func(pods []*v1.Pod) { + pods[0].Status.ContainerStatuses = []v1.ContainerStatus{ + { + State: v1.ContainerState{ + Waiting: &v1.ContainerStateWaiting{Reason: "ImagePullBackOff"}, + }, + }, + } + }, + }, } for _, tc := range testCases { @@ -323,6 +363,10 @@ func TestPodLifeTime(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() + if tc.applyPodsFunc != nil { + tc.applyPodsFunc(tc.pods) + } + var objs []runtime.Object for _, node := range tc.nodes { objs = append(objs, node) diff --git a/pkg/framework/plugins/podlifetime/validation.go b/pkg/framework/plugins/podlifetime/validation.go index 973dceb6af..b48368a357 100644 --- a/pkg/framework/plugins/podlifetime/validation.go +++ b/pkg/framework/plugins/podlifetime/validation.go @@ -50,6 +50,9 @@ func ValidatePodLifeTimeArgs(obj runtime.Object) error { // Container state reasons: https://github.com/kubernetes/kubernetes/blob/release-1.24/pkg/kubelet/kubelet_pods.go#L76-L79 "PodInitializing", "ContainerCreating", + + // containerStatuses[*].state.waiting.reason: ImagePullBackOff + "ImagePullBackOff", ) if !podLifeTimeAllowedStates.HasAll(args.States...) {