mirror of
https://github.com/k3s-io/kubernetes.git
synced 2026-08-08 23:37:11 +00:00
Forget pod from scheduler's cache immediately when it's deleted or has DeletionTimestamp set
This commit is contained in:
9
pkg/scheduler/backend/cache/cache.go
vendored
9
pkg/scheduler/backend/cache/cache.go
vendored
@@ -419,12 +419,15 @@ func (cache *cacheImpl) ForgetPod(logger klog.Logger, pod *v1.Pod) error {
|
||||
defer cache.mu.Unlock()
|
||||
|
||||
currState, ok := cache.podStates[key]
|
||||
if ok && currState.pod.Spec.NodeName != pod.Spec.NodeName {
|
||||
if !ok {
|
||||
// Pod does not exist in the cache anymore.
|
||||
return nil
|
||||
}
|
||||
if currState.pod.Spec.NodeName != pod.Spec.NodeName {
|
||||
return fmt.Errorf("pod %v(%v) was assumed on %v but assigned to %v", key, klog.KObj(pod), pod.Spec.NodeName, currState.pod.Spec.NodeName)
|
||||
}
|
||||
|
||||
// Only assumed pod can be forgotten.
|
||||
if ok && cache.assumedPods.Has(key) {
|
||||
if cache.assumedPods.Has(key) {
|
||||
return cache.removePod(logger, pod)
|
||||
}
|
||||
return fmt.Errorf("pod %v(%v) wasn't assumed so cannot be forgotten", key, klog.KObj(pod))
|
||||
|
||||
6
pkg/scheduler/backend/cache/cache_test.go
vendored
6
pkg/scheduler/backend/cache/cache_test.go
vendored
@@ -1043,9 +1043,9 @@ func TestForgetPod(t *testing.T) {
|
||||
if err := isForgottenFromCache(pod, cache); err != nil {
|
||||
t.Errorf("pod %q: %v", pod.Name, err)
|
||||
}
|
||||
// trying to forget a pod already forgotten should return an error
|
||||
if err := cache.ForgetPod(logger, pod); err == nil {
|
||||
t.Error("expected error, no error found")
|
||||
// trying to forget a pod already forgotten should return nil
|
||||
if err := cache.ForgetPod(logger, pod); err != nil {
|
||||
t.Error("expected no error, error found")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -231,6 +231,49 @@ func (sched *Scheduler) syncPodWithDispatcher(pod *v1.Pod) *v1.Pod {
|
||||
return enrichedPod
|
||||
}
|
||||
|
||||
// handleAssumedPodDeletion is an event handler that deals with the deletion of an assumed pod.
|
||||
// We must remove it from the scheduler's cache immediately to prevent it from blocking resources for other pending pods,
|
||||
// causing unnecessary preemption attempts. Note that PreBinding/Binding will continue, but is eventually expected to fail
|
||||
// as the pod does not exist in the kube-apiserver anymore and so in the scheduler cache.
|
||||
func (sched *Scheduler) handleAssumedPodDeletion(pod *v1.Pod) {
|
||||
logger := sched.logger
|
||||
// We must operate on the pod from the scheduler's cache, not the one from the event.
|
||||
// The cached version has the assigned NodeName and represents the resources being consumed.
|
||||
assumedPod, err := sched.Cache.GetPod(pod)
|
||||
if err != nil {
|
||||
// This is not an error. The pod may have already completed its binding cycle and been
|
||||
// removed from the cache. Nothing more to do.
|
||||
logger.V(5).Info("Assumed pod was already forgotten", "pod", klog.KObj(pod))
|
||||
return
|
||||
}
|
||||
pod = assumedPod
|
||||
|
||||
fwk, err := sched.frameworkForPod(pod)
|
||||
if err != nil {
|
||||
// This shouldn't happen, because we only accept for scheduling the pods
|
||||
// which specify a scheduler name that matches one of the profiles.
|
||||
utilruntime.HandleErrorWithLogger(logger, err, "Unable to get profile for pod", "pod", klog.KObj(pod))
|
||||
return
|
||||
}
|
||||
|
||||
// The pod might be in one of two states:
|
||||
// 1. If the pod is waiting on WaitOnPermit, we reject it. This causes the pod's scheduling
|
||||
// cycle to quickly fail gracefully, and it will clean itself up via `handleBindingCycleError`.
|
||||
if !fwk.RejectWaitingPod(pod.UID) {
|
||||
// 2. If the pod is no longer waiting (e.g., it's in PreBind or Bind), we can't quickly reject it.
|
||||
// We must explicitly remove it from the cache here to free up its assumed resources.
|
||||
if err := sched.Cache.ForgetPod(logger, pod); err != nil {
|
||||
utilruntime.HandleErrorWithLogger(logger, err, "Scheduler cache ForgetPod failed", "pod", klog.KObj(pod))
|
||||
}
|
||||
}
|
||||
|
||||
// The removal of this assumed pod may have freed up resources. We trigger the AssignedPodDelete event
|
||||
// to move other unscheduled pods, giving them a chance to be scheduled.
|
||||
// If the forgotten pod reserved some resources in memory,
|
||||
// it will wake up the pods again after freeing up the resources in `handleBindingCycleError`.
|
||||
sched.SchedulingQueue.MoveAllToActiveOrBackoffQueue(logger, framework.EventAssignedPodDelete, pod, nil, nil)
|
||||
}
|
||||
|
||||
func (sched *Scheduler) updatePodInSchedulingQueue(oldPod, newPod *v1.Pod) {
|
||||
start := time.Now()
|
||||
logger := sched.logger
|
||||
@@ -258,6 +301,11 @@ func (sched *Scheduler) updatePodInSchedulingQueue(oldPod, newPod *v1.Pod) {
|
||||
utilruntime.HandleErrorWithLogger(logger, err, "Failed to check whether pod is assumed", "pod", klog.KObj(newPod))
|
||||
}
|
||||
if isAssumed {
|
||||
if newPod.DeletionTimestamp != nil && oldPod.DeletionTimestamp == nil {
|
||||
// Assumed pod deletion has started. We should handle that differently,
|
||||
// because we can't update such pod in any structure directly.
|
||||
sched.handleAssumedPodDeletion(newPod)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -290,22 +338,18 @@ func (sched *Scheduler) deletePodFromSchedulingQueue(pod *v1.Pod, inBinding bool
|
||||
// once the https://github.com/kubernetes/kubernetes/issues/134859 is fixed.
|
||||
return
|
||||
}
|
||||
fwk, err := sched.frameworkForPod(pod)
|
||||
isAssumed, err := sched.Cache.IsAssumedPod(pod)
|
||||
if err != nil {
|
||||
// This shouldn't happen, because we only accept for scheduling the pods
|
||||
// which specify a scheduler name that matches one of the profiles.
|
||||
utilruntime.HandleErrorWithLogger(logger, err, "Unable to get profile", "pod", klog.KObj(pod))
|
||||
return
|
||||
utilruntime.HandleErrorWithLogger(logger, err, "Failed to check whether pod is assumed", "pod", klog.KObj(pod))
|
||||
}
|
||||
// If a waiting pod is rejected, it indicates it's previously assumed and we're
|
||||
// removing it from the scheduler cache. In this case, signal a AssignedPodDelete
|
||||
// event to immediately retry some unscheduled Pods.
|
||||
// Similarly when a pod that had nominated node is deleted, it can unblock scheduling of other pods,
|
||||
// because the lower or equal priority pods treat such a pod as if it was assigned.
|
||||
if fwk.RejectWaitingPod(pod.UID) {
|
||||
sched.SchedulingQueue.MoveAllToActiveOrBackoffQueue(logger, framework.EventAssignedPodDelete, pod, nil, nil)
|
||||
if isAssumed {
|
||||
// Assumed pod is deleted. We should handle that differently,
|
||||
// because we can't delete such pod from any structure directly.
|
||||
sched.handleAssumedPodDeletion(pod)
|
||||
} else if pod.Status.NominatedNodeName != "" {
|
||||
// Note that a nominated pod can fall into `RejectWaitingPod` case as well,
|
||||
// When a pod that had nominated node is deleted, it can unblock scheduling of other pods,
|
||||
// because the lower or equal priority pods treat such a pod as if it was assigned.
|
||||
// Note that a nominated pod can fall into `handleAssumedPodDeletion` case as well,
|
||||
// but in that case the `MoveAllToActiveOrBackoffQueue` already covered lower priority pods.
|
||||
sched.SchedulingQueue.MoveAllToActiveOrBackoffQueue(logger, framework.EventAssignedPodDelete, pod, nil, getLEPriorityPreCheck(corev1helpers.PodPriority(pod)))
|
||||
}
|
||||
|
||||
@@ -739,6 +739,8 @@ func TestUpdatePod(t *testing.T) {
|
||||
pod := st.MakePod().Name("pod1").Namespace("ns1").UID("pod1").SchedulerName("supported-scheduler").Obj()
|
||||
updatedPod := st.MakePod().Name("pod1").Namespace("ns1").UID("pod1").Labels(map[string]string{"foo": "bar"}).ResourceVersion("2").SchedulerName("supported-scheduler").Obj()
|
||||
|
||||
podWithDeletionTimestamp := st.MakePod().Name("pod1").Namespace("ns1").UID("pod1").Terminating().ResourceVersion("2").SchedulerName("supported-scheduler").Obj()
|
||||
|
||||
otherPod := st.MakePod().Name("pod1").Namespace("ns1").UID("pod1").SchedulerName("other-scheduler").Obj()
|
||||
updatedOtherPod := st.MakePod().Name("pod1").Namespace("ns1").UID("pod1").Labels(map[string]string{"foo": "bar"}).ResourceVersion("2").SchedulerName("other-scheduler").Obj()
|
||||
|
||||
@@ -814,6 +816,12 @@ func TestUpdatePod(t *testing.T) {
|
||||
newPod: scheduledPodOtherNode,
|
||||
expectInCache: scheduledPodOtherNode,
|
||||
},
|
||||
{
|
||||
name: "delete assumed pod with deletion timestamp",
|
||||
oldPod: pod,
|
||||
assumedPod: scheduledPod,
|
||||
newPod: podWithDeletionTimestamp,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
@@ -907,6 +915,12 @@ func TestDeletePod(t *testing.T) {
|
||||
initialPod: pod,
|
||||
podToDelete: cache.DeletedFinalStateUnknown{Obj: pod},
|
||||
},
|
||||
{
|
||||
name: "delete assumed pod",
|
||||
initialPod: scheduledPod,
|
||||
assumed: true,
|
||||
podToDelete: pod,
|
||||
},
|
||||
{
|
||||
name: "delete scheduled pod",
|
||||
initialPod: scheduledPod,
|
||||
|
||||
@@ -417,6 +417,12 @@ func (p *PodWrapper) ZeroTerminationGracePeriod() *PodWrapper {
|
||||
return p
|
||||
}
|
||||
|
||||
// TerminationGracePeriodSeconds sets the TerminationGracePeriodSeconds of the inner pod.
|
||||
func (p *PodWrapper) TerminationGracePeriodSeconds(s int64) *PodWrapper {
|
||||
p.Spec.TerminationGracePeriodSeconds = &s
|
||||
return p
|
||||
}
|
||||
|
||||
// Node sets `s` as the nodeName of the inner pod.
|
||||
func (p *PodWrapper) Node(s string) *PodWrapper {
|
||||
p.Spec.NodeName = s
|
||||
|
||||
Reference in New Issue
Block a user