From 3a608a590afbdcc508988eb719a50246dbd6226e Mon Sep 17 00:00:00 2001 From: Simone Tiraboschi Date: Fri, 10 Oct 2025 16:36:13 +0200 Subject: [PATCH] feat(eviction): add annotations to eviction requests for observability Although eviction requests (policy/v1) are not persisted long term, their API still implements the full metav1.ObjectMeta struct. While name and namespace refer to the pod being evicted, eviction requests can still carry annotations. This change adds annotations to descheduler-initiated evictions, including the requester, reason, and the strategy or plugin that triggered them. While these details are already logged by the descheduler, exposing them as annotations allows external webhooks or controllers to provide clearer context about each eviction request, both for tracking and prioritization purposes. Signed-off-by: Simone Tiraboschi --- pkg/descheduler/evictions/evictions.go | 14 ++++++++++++-- pkg/descheduler/evictions/evictions_test.go | 9 +++++++-- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/pkg/descheduler/evictions/evictions.go b/pkg/descheduler/evictions/evictions.go index 258c3fa8f..df6964d4d 100644 --- a/pkg/descheduler/evictions/evictions.go +++ b/pkg/descheduler/evictions/evictions.go @@ -42,6 +42,12 @@ import ( "sigs.k8s.io/descheduler/pkg/tracing" ) +const ( + deschedulerGlobalName = "sigs.k8s.io/descheduler" + reasonAnnotationKey = "reason" + requestedByAnnotationKey = "requested-by" +) + var ( assumedEvictionRequestTimeoutSeconds uint = 10 * 60 // 10 minutes evictionRequestsCacheResyncPeriod time.Duration = 10 * time.Minute @@ -522,7 +528,7 @@ func (pe *PodEvictor) EvictPod(ctx context.Context, pod *v1.Pod, opts EvictOptio return err } - ignore, err := pe.evictPod(ctx, pod) + ignore, err := pe.evictPod(ctx, pod, opts) if err != nil { // err is used only for logging purposes span.AddEvent("Eviction Failed", trace.WithAttributes(attribute.String("node", pod.Spec.NodeName), attribute.String("err", err.Error()))) @@ -569,7 +575,7 @@ func (pe *PodEvictor) EvictPod(ctx context.Context, pod *v1.Pod, opts EvictOptio } // return (ignore, err) -func (pe *PodEvictor) evictPod(ctx context.Context, pod *v1.Pod) (bool, error) { +func (pe *PodEvictor) evictPod(ctx context.Context, pod *v1.Pod, opts EvictOptions) (bool, error) { deleteOptions := &metav1.DeleteOptions{ GracePeriodSeconds: pe.gracePeriodSeconds, } @@ -582,6 +588,10 @@ func (pe *PodEvictor) evictPod(ctx context.Context, pod *v1.Pod) (bool, error) { ObjectMeta: metav1.ObjectMeta{ Name: pod.Name, Namespace: pod.Namespace, + Annotations: map[string]string{ + "reason": fmt.Sprintf("triggered by %v/%v: %v", opts.ProfileName, opts.StrategyName, opts.Reason), + "requested-by": deschedulerGlobalName, + }, }, DeleteOptions: deleteOptions, } diff --git a/pkg/descheduler/evictions/evictions_test.go b/pkg/descheduler/evictions/evictions_test.go index b210ad0eb..4feb71f5c 100644 --- a/pkg/descheduler/evictions/evictions_test.go +++ b/pkg/descheduler/evictions/evictions_test.go @@ -20,6 +20,7 @@ import ( "context" "fmt" "reflect" + "strings" "testing" "time" @@ -114,7 +115,7 @@ func TestEvictPod(t *testing.T) { t.Fatalf("Unexpected error when creating a pod evictor: %v", err) } - _, got := podEvictor.evictPod(ctx, test.evictedPod) + _, got := podEvictor.evictPod(ctx, test.evictedPod, EvictOptions{}) if got != test.wantErr { t.Errorf("Test error for Desc: %s. Expected %v pod eviction to be %v, got %v", test.description, test.evictedPod.Name, test.wantErr, got) } @@ -418,7 +419,11 @@ func TestEvictionRequestsCacheCleanup(t *testing.T) { } if eviction, matched := createAct.Object.(*policy.Eviction); matched { podName := eviction.GetName() - if podName == "p1" || podName == "p2" { + annotations := eviction.GetAnnotations() + if (podName == "p1" || podName == "p2") && annotations[requestedByAnnotationKey] == deschedulerGlobalName && strings.HasPrefix( + annotations[reasonAnnotationKey], + "triggered by", + ) { return true, nil, &apierrors.StatusError{ ErrStatus: metav1.Status{ Reason: metav1.StatusReasonTooManyRequests,