k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/pkg/controller/tainteviction/taint_eviction.go (about) 1 /* 2 Copyright 2017 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package tainteviction 18 19 import ( 20 "context" 21 "fmt" 22 "hash/fnv" 23 "io" 24 "math" 25 "sync" 26 "time" 27 28 v1 "k8s.io/api/core/v1" 29 apierrors "k8s.io/apimachinery/pkg/api/errors" 30 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 31 "k8s.io/apimachinery/pkg/types" 32 utilruntime "k8s.io/apimachinery/pkg/util/runtime" 33 "k8s.io/apiserver/pkg/util/feature" 34 corev1informers "k8s.io/client-go/informers/core/v1" 35 clientset "k8s.io/client-go/kubernetes" 36 "k8s.io/client-go/kubernetes/scheme" 37 v1core "k8s.io/client-go/kubernetes/typed/core/v1" 38 corelisters "k8s.io/client-go/listers/core/v1" 39 "k8s.io/client-go/tools/cache" 40 "k8s.io/client-go/tools/record" 41 "k8s.io/client-go/util/workqueue" 42 "k8s.io/klog/v2" 43 apipod "k8s.io/kubernetes/pkg/api/v1/pod" 44 "k8s.io/kubernetes/pkg/apis/core/helper" 45 v1helper "k8s.io/kubernetes/pkg/apis/core/v1/helper" 46 "k8s.io/kubernetes/pkg/controller/tainteviction/metrics" 47 controllerutil "k8s.io/kubernetes/pkg/controller/util/node" 48 "k8s.io/kubernetes/pkg/features" 49 utilpod "k8s.io/kubernetes/pkg/util/pod" 50 ) 51 52 const ( 53 // TODO (k82cn): Figure out a reasonable number of workers/channels and propagate 54 // the number of workers up making it a parameter of Run() function. 55 56 // NodeUpdateChannelSize defines the size of channel for node update events. 57 NodeUpdateChannelSize = 10 58 // UpdateWorkerSize defines the size of workers for node update or/and pod update. 59 UpdateWorkerSize = 8 60 podUpdateChannelSize = 1 61 retries = 5 62 ) 63 64 type nodeUpdateItem struct { 65 nodeName string 66 } 67 68 type podUpdateItem struct { 69 podName string 70 podNamespace string 71 nodeName string 72 } 73 74 func hash(val string, max int) int { 75 hasher := fnv.New32a() 76 io.WriteString(hasher, val) 77 return int(hasher.Sum32() % uint32(max)) 78 } 79 80 // GetPodsByNodeNameFunc returns the list of pods assigned to the specified node. 81 type GetPodsByNodeNameFunc func(nodeName string) ([]*v1.Pod, error) 82 83 // Controller listens to Taint/Toleration changes and is responsible for removing Pods 84 // from Nodes tainted with NoExecute Taints. 85 type Controller struct { 86 name string 87 88 client clientset.Interface 89 broadcaster record.EventBroadcaster 90 recorder record.EventRecorder 91 podLister corelisters.PodLister 92 podListerSynced cache.InformerSynced 93 nodeLister corelisters.NodeLister 94 nodeListerSynced cache.InformerSynced 95 getPodsAssignedToNode GetPodsByNodeNameFunc 96 97 taintEvictionQueue *TimedWorkerQueue 98 // keeps a map from nodeName to all noExecute taints on that Node 99 taintedNodesLock sync.Mutex 100 taintedNodes map[string][]v1.Taint 101 102 nodeUpdateChannels []chan nodeUpdateItem 103 podUpdateChannels []chan podUpdateItem 104 105 nodeUpdateQueue workqueue.TypedInterface[nodeUpdateItem] 106 podUpdateQueue workqueue.TypedInterface[podUpdateItem] 107 } 108 109 func deletePodHandler(c clientset.Interface, emitEventFunc func(types.NamespacedName), controllerName string) func(ctx context.Context, fireAt time.Time, args *WorkArgs) error { 110 return func(ctx context.Context, fireAt time.Time, args *WorkArgs) error { 111 ns := args.NamespacedName.Namespace 112 name := args.NamespacedName.Name 113 klog.FromContext(ctx).Info("Deleting pod", "controller", controllerName, "pod", args.NamespacedName) 114 if emitEventFunc != nil { 115 emitEventFunc(args.NamespacedName) 116 } 117 var err error 118 for i := 0; i < retries; i++ { 119 err = addConditionAndDeletePod(ctx, c, name, ns) 120 if err == nil { 121 metrics.PodDeletionsTotal.Inc() 122 metrics.PodDeletionsLatency.Observe(float64(time.Since(fireAt) * time.Second)) 123 break 124 } 125 time.Sleep(10 * time.Millisecond) 126 } 127 return err 128 } 129 } 130 131 func addConditionAndDeletePod(ctx context.Context, c clientset.Interface, name, ns string) (err error) { 132 if feature.DefaultFeatureGate.Enabled(features.PodDisruptionConditions) { 133 pod, err := c.CoreV1().Pods(ns).Get(ctx, name, metav1.GetOptions{}) 134 if err != nil { 135 return err 136 } 137 newStatus := pod.Status.DeepCopy() 138 updated := apipod.UpdatePodCondition(newStatus, &v1.PodCondition{ 139 Type: v1.DisruptionTarget, 140 Status: v1.ConditionTrue, 141 Reason: "DeletionByTaintManager", 142 Message: "Taint manager: deleting due to NoExecute taint", 143 }) 144 if updated { 145 if _, _, _, err := utilpod.PatchPodStatus(ctx, c, pod.Namespace, pod.Name, pod.UID, pod.Status, *newStatus); err != nil { 146 return err 147 } 148 } 149 } 150 return c.CoreV1().Pods(ns).Delete(ctx, name, metav1.DeleteOptions{}) 151 } 152 153 func getNoExecuteTaints(taints []v1.Taint) []v1.Taint { 154 result := []v1.Taint{} 155 for i := range taints { 156 if taints[i].Effect == v1.TaintEffectNoExecute { 157 result = append(result, taints[i]) 158 } 159 } 160 return result 161 } 162 163 // getMinTolerationTime returns minimal toleration time from the given slice, or -1 if it's infinite. 164 func getMinTolerationTime(tolerations []v1.Toleration) time.Duration { 165 minTolerationTime := int64(math.MaxInt64) 166 if len(tolerations) == 0 { 167 return 0 168 } 169 170 for i := range tolerations { 171 if tolerations[i].TolerationSeconds != nil { 172 tolerationSeconds := *(tolerations[i].TolerationSeconds) 173 if tolerationSeconds <= 0 { 174 return 0 175 } else if tolerationSeconds < minTolerationTime { 176 minTolerationTime = tolerationSeconds 177 } 178 } 179 } 180 181 if minTolerationTime == int64(math.MaxInt64) { 182 return -1 183 } 184 return time.Duration(minTolerationTime) * time.Second 185 } 186 187 // New creates a new Controller that will use passed clientset to communicate with the API server. 188 func New(ctx context.Context, c clientset.Interface, podInformer corev1informers.PodInformer, nodeInformer corev1informers.NodeInformer, controllerName string) (*Controller, error) { 189 logger := klog.FromContext(ctx) 190 metrics.Register() 191 eventBroadcaster := record.NewBroadcaster(record.WithContext(ctx)) 192 recorder := eventBroadcaster.NewRecorder(scheme.Scheme, v1.EventSource{Component: controllerName}) 193 194 podIndexer := podInformer.Informer().GetIndexer() 195 196 tm := &Controller{ 197 name: controllerName, 198 199 client: c, 200 broadcaster: eventBroadcaster, 201 recorder: recorder, 202 podLister: podInformer.Lister(), 203 podListerSynced: podInformer.Informer().HasSynced, 204 nodeLister: nodeInformer.Lister(), 205 nodeListerSynced: nodeInformer.Informer().HasSynced, 206 getPodsAssignedToNode: func(nodeName string) ([]*v1.Pod, error) { 207 objs, err := podIndexer.ByIndex("spec.nodeName", nodeName) 208 if err != nil { 209 return nil, err 210 } 211 pods := make([]*v1.Pod, 0, len(objs)) 212 for _, obj := range objs { 213 pod, ok := obj.(*v1.Pod) 214 if !ok { 215 continue 216 } 217 pods = append(pods, pod) 218 } 219 return pods, nil 220 }, 221 taintedNodes: make(map[string][]v1.Taint), 222 223 nodeUpdateQueue: workqueue.NewTypedWithConfig(workqueue.TypedQueueConfig[nodeUpdateItem]{Name: "noexec_taint_node"}), 224 podUpdateQueue: workqueue.NewTypedWithConfig(workqueue.TypedQueueConfig[podUpdateItem]{Name: "noexec_taint_pod"}), 225 } 226 tm.taintEvictionQueue = CreateWorkerQueue(deletePodHandler(c, tm.emitPodDeletionEvent, tm.name)) 227 228 _, err := podInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ 229 AddFunc: func(obj interface{}) { 230 pod := obj.(*v1.Pod) 231 tm.PodUpdated(nil, pod) 232 }, 233 UpdateFunc: func(prev, obj interface{}) { 234 prevPod := prev.(*v1.Pod) 235 newPod := obj.(*v1.Pod) 236 tm.PodUpdated(prevPod, newPod) 237 }, 238 DeleteFunc: func(obj interface{}) { 239 pod, isPod := obj.(*v1.Pod) 240 // We can get DeletedFinalStateUnknown instead of *v1.Pod here and we need to handle that correctly. 241 if !isPod { 242 deletedState, ok := obj.(cache.DeletedFinalStateUnknown) 243 if !ok { 244 logger.Error(nil, "Received unexpected object", "object", obj) 245 return 246 } 247 pod, ok = deletedState.Obj.(*v1.Pod) 248 if !ok { 249 logger.Error(nil, "DeletedFinalStateUnknown contained non-Pod object", "object", deletedState.Obj) 250 return 251 } 252 } 253 tm.PodUpdated(pod, nil) 254 }, 255 }) 256 if err != nil { 257 return nil, fmt.Errorf("unable to add pod event handler: %w", err) 258 } 259 260 _, err = nodeInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ 261 AddFunc: controllerutil.CreateAddNodeHandler(func(node *v1.Node) error { 262 tm.NodeUpdated(nil, node) 263 return nil 264 }), 265 UpdateFunc: controllerutil.CreateUpdateNodeHandler(func(oldNode, newNode *v1.Node) error { 266 tm.NodeUpdated(oldNode, newNode) 267 return nil 268 }), 269 DeleteFunc: controllerutil.CreateDeleteNodeHandler(logger, func(node *v1.Node) error { 270 tm.NodeUpdated(node, nil) 271 return nil 272 }), 273 }) 274 if err != nil { 275 return nil, fmt.Errorf("unable to add node event handler: %w", err) 276 } 277 278 return tm, nil 279 } 280 281 // Run starts the controller which will run in loop until `stopCh` is closed. 282 func (tc *Controller) Run(ctx context.Context) { 283 defer utilruntime.HandleCrash() 284 logger := klog.FromContext(ctx) 285 logger.Info("Starting", "controller", tc.name) 286 defer logger.Info("Shutting down controller", "controller", tc.name) 287 288 // Start events processing pipeline. 289 tc.broadcaster.StartStructuredLogging(3) 290 if tc.client != nil { 291 logger.Info("Sending events to api server") 292 tc.broadcaster.StartRecordingToSink(&v1core.EventSinkImpl{Interface: tc.client.CoreV1().Events("")}) 293 } else { 294 logger.Error(nil, "kubeClient is nil", "controller", tc.name) 295 klog.FlushAndExit(klog.ExitFlushTimeout, 1) 296 } 297 defer tc.broadcaster.Shutdown() 298 defer tc.nodeUpdateQueue.ShutDown() 299 defer tc.podUpdateQueue.ShutDown() 300 301 // wait for the cache to be synced 302 if !cache.WaitForNamedCacheSync(tc.name, ctx.Done(), tc.podListerSynced, tc.nodeListerSynced) { 303 return 304 } 305 306 for i := 0; i < UpdateWorkerSize; i++ { 307 tc.nodeUpdateChannels = append(tc.nodeUpdateChannels, make(chan nodeUpdateItem, NodeUpdateChannelSize)) 308 tc.podUpdateChannels = append(tc.podUpdateChannels, make(chan podUpdateItem, podUpdateChannelSize)) 309 } 310 311 // Functions that are responsible for taking work items out of the workqueues and putting them 312 // into channels. 313 go func(stopCh <-chan struct{}) { 314 for { 315 nodeUpdate, shutdown := tc.nodeUpdateQueue.Get() 316 if shutdown { 317 break 318 } 319 hash := hash(nodeUpdate.nodeName, UpdateWorkerSize) 320 select { 321 case <-stopCh: 322 tc.nodeUpdateQueue.Done(nodeUpdate) 323 return 324 case tc.nodeUpdateChannels[hash] <- nodeUpdate: 325 // tc.nodeUpdateQueue.Done is called by the nodeUpdateChannels worker 326 } 327 } 328 }(ctx.Done()) 329 330 go func(stopCh <-chan struct{}) { 331 for { 332 podUpdate, shutdown := tc.podUpdateQueue.Get() 333 if shutdown { 334 break 335 } 336 // The fact that pods are processed by the same worker as nodes is used to avoid races 337 // between node worker setting tc.taintedNodes and pod worker reading this to decide 338 // whether to delete pod. 339 // It's possible that even without this assumption this code is still correct. 340 hash := hash(podUpdate.nodeName, UpdateWorkerSize) 341 select { 342 case <-stopCh: 343 tc.podUpdateQueue.Done(podUpdate) 344 return 345 case tc.podUpdateChannels[hash] <- podUpdate: 346 // tc.podUpdateQueue.Done is called by the podUpdateChannels worker 347 } 348 } 349 }(ctx.Done()) 350 351 wg := sync.WaitGroup{} 352 wg.Add(UpdateWorkerSize) 353 for i := 0; i < UpdateWorkerSize; i++ { 354 go tc.worker(ctx, i, wg.Done, ctx.Done()) 355 } 356 wg.Wait() 357 } 358 359 func (tc *Controller) worker(ctx context.Context, worker int, done func(), stopCh <-chan struct{}) { 360 defer done() 361 362 // When processing events we want to prioritize Node updates over Pod updates, 363 // as NodeUpdates that interest the controller should be handled as soon as possible - 364 // we don't want user (or system) to wait until PodUpdate queue is drained before it can 365 // start evicting Pods from tainted Nodes. 366 for { 367 select { 368 case <-stopCh: 369 return 370 case nodeUpdate := <-tc.nodeUpdateChannels[worker]: 371 tc.handleNodeUpdate(ctx, nodeUpdate) 372 tc.nodeUpdateQueue.Done(nodeUpdate) 373 case podUpdate := <-tc.podUpdateChannels[worker]: 374 // If we found a Pod update we need to empty Node queue first. 375 priority: 376 for { 377 select { 378 case nodeUpdate := <-tc.nodeUpdateChannels[worker]: 379 tc.handleNodeUpdate(ctx, nodeUpdate) 380 tc.nodeUpdateQueue.Done(nodeUpdate) 381 default: 382 break priority 383 } 384 } 385 // After Node queue is emptied we process podUpdate. 386 tc.handlePodUpdate(ctx, podUpdate) 387 tc.podUpdateQueue.Done(podUpdate) 388 } 389 } 390 } 391 392 // PodUpdated is used to notify NoExecuteTaintManager about Pod changes. 393 func (tc *Controller) PodUpdated(oldPod *v1.Pod, newPod *v1.Pod) { 394 podName := "" 395 podNamespace := "" 396 nodeName := "" 397 oldTolerations := []v1.Toleration{} 398 if oldPod != nil { 399 podName = oldPod.Name 400 podNamespace = oldPod.Namespace 401 nodeName = oldPod.Spec.NodeName 402 oldTolerations = oldPod.Spec.Tolerations 403 } 404 newTolerations := []v1.Toleration{} 405 if newPod != nil { 406 podName = newPod.Name 407 podNamespace = newPod.Namespace 408 nodeName = newPod.Spec.NodeName 409 newTolerations = newPod.Spec.Tolerations 410 } 411 412 if oldPod != nil && newPod != nil && helper.Semantic.DeepEqual(oldTolerations, newTolerations) && oldPod.Spec.NodeName == newPod.Spec.NodeName { 413 return 414 } 415 updateItem := podUpdateItem{ 416 podName: podName, 417 podNamespace: podNamespace, 418 nodeName: nodeName, 419 } 420 421 tc.podUpdateQueue.Add(updateItem) 422 } 423 424 // NodeUpdated is used to notify NoExecuteTaintManager about Node changes. 425 func (tc *Controller) NodeUpdated(oldNode *v1.Node, newNode *v1.Node) { 426 nodeName := "" 427 oldTaints := []v1.Taint{} 428 if oldNode != nil { 429 nodeName = oldNode.Name 430 oldTaints = getNoExecuteTaints(oldNode.Spec.Taints) 431 } 432 433 newTaints := []v1.Taint{} 434 if newNode != nil { 435 nodeName = newNode.Name 436 newTaints = getNoExecuteTaints(newNode.Spec.Taints) 437 } 438 439 if oldNode != nil && newNode != nil && helper.Semantic.DeepEqual(oldTaints, newTaints) { 440 return 441 } 442 updateItem := nodeUpdateItem{ 443 nodeName: nodeName, 444 } 445 446 tc.nodeUpdateQueue.Add(updateItem) 447 } 448 449 func (tc *Controller) cancelWorkWithEvent(logger klog.Logger, nsName types.NamespacedName) { 450 if tc.taintEvictionQueue.CancelWork(logger, nsName.String()) { 451 tc.emitCancelPodDeletionEvent(nsName) 452 } 453 } 454 455 func (tc *Controller) processPodOnNode( 456 ctx context.Context, 457 podNamespacedName types.NamespacedName, 458 nodeName string, 459 tolerations []v1.Toleration, 460 taints []v1.Taint, 461 now time.Time, 462 ) { 463 logger := klog.FromContext(ctx) 464 if len(taints) == 0 { 465 tc.cancelWorkWithEvent(logger, podNamespacedName) 466 } 467 allTolerated, usedTolerations := v1helper.GetMatchingTolerations(taints, tolerations) 468 if !allTolerated { 469 logger.V(2).Info("Not all taints are tolerated after update for pod on node", "pod", podNamespacedName.String(), "node", klog.KRef("", nodeName)) 470 // We're canceling scheduled work (if any), as we're going to delete the Pod right away. 471 tc.cancelWorkWithEvent(logger, podNamespacedName) 472 tc.taintEvictionQueue.AddWork(ctx, NewWorkArgs(podNamespacedName.Name, podNamespacedName.Namespace), now, now) 473 return 474 } 475 minTolerationTime := getMinTolerationTime(usedTolerations) 476 // getMinTolerationTime returns negative value to denote infinite toleration. 477 if minTolerationTime < 0 { 478 logger.V(4).Info("Current tolerations for pod tolerate forever, cancelling any scheduled deletion", "pod", podNamespacedName.String()) 479 tc.cancelWorkWithEvent(logger, podNamespacedName) 480 return 481 } 482 483 startTime := now 484 triggerTime := startTime.Add(minTolerationTime) 485 scheduledEviction := tc.taintEvictionQueue.GetWorkerUnsafe(podNamespacedName.String()) 486 if scheduledEviction != nil { 487 startTime = scheduledEviction.CreatedAt 488 if startTime.Add(minTolerationTime).Before(triggerTime) { 489 return 490 } 491 tc.cancelWorkWithEvent(logger, podNamespacedName) 492 } 493 tc.taintEvictionQueue.AddWork(ctx, NewWorkArgs(podNamespacedName.Name, podNamespacedName.Namespace), startTime, triggerTime) 494 } 495 496 func (tc *Controller) handlePodUpdate(ctx context.Context, podUpdate podUpdateItem) { 497 pod, err := tc.podLister.Pods(podUpdate.podNamespace).Get(podUpdate.podName) 498 logger := klog.FromContext(ctx) 499 if err != nil { 500 if apierrors.IsNotFound(err) { 501 // Delete 502 podNamespacedName := types.NamespacedName{Namespace: podUpdate.podNamespace, Name: podUpdate.podName} 503 logger.V(4).Info("Noticed pod deletion", "pod", podNamespacedName) 504 tc.cancelWorkWithEvent(logger, podNamespacedName) 505 return 506 } 507 utilruntime.HandleError(fmt.Errorf("could not get pod %s/%s: %v", podUpdate.podName, podUpdate.podNamespace, err)) 508 return 509 } 510 511 // We key the workqueue and shard workers by nodeName. If we don't match the current state we should not be the one processing the current object. 512 if pod.Spec.NodeName != podUpdate.nodeName { 513 return 514 } 515 516 // Create or Update 517 podNamespacedName := types.NamespacedName{Namespace: pod.Namespace, Name: pod.Name} 518 logger.V(4).Info("Noticed pod update", "pod", podNamespacedName) 519 nodeName := pod.Spec.NodeName 520 if nodeName == "" { 521 return 522 } 523 taints, ok := func() ([]v1.Taint, bool) { 524 tc.taintedNodesLock.Lock() 525 defer tc.taintedNodesLock.Unlock() 526 taints, ok := tc.taintedNodes[nodeName] 527 return taints, ok 528 }() 529 // It's possible that Node was deleted, or Taints were removed before, which triggered 530 // eviction cancelling if it was needed. 531 if !ok { 532 return 533 } 534 tc.processPodOnNode(ctx, podNamespacedName, nodeName, pod.Spec.Tolerations, taints, time.Now()) 535 } 536 537 func (tc *Controller) handleNodeUpdate(ctx context.Context, nodeUpdate nodeUpdateItem) { 538 node, err := tc.nodeLister.Get(nodeUpdate.nodeName) 539 logger := klog.FromContext(ctx) 540 if err != nil { 541 if apierrors.IsNotFound(err) { 542 // Delete 543 logger.V(4).Info("Noticed node deletion", "node", klog.KRef("", nodeUpdate.nodeName)) 544 tc.taintedNodesLock.Lock() 545 defer tc.taintedNodesLock.Unlock() 546 delete(tc.taintedNodes, nodeUpdate.nodeName) 547 return 548 } 549 utilruntime.HandleError(fmt.Errorf("cannot get node %s: %v", nodeUpdate.nodeName, err)) 550 return 551 } 552 553 // Create or Update 554 logger.V(4).Info("Noticed node update", "node", klog.KObj(node)) 555 taints := getNoExecuteTaints(node.Spec.Taints) 556 func() { 557 tc.taintedNodesLock.Lock() 558 defer tc.taintedNodesLock.Unlock() 559 logger.V(4).Info("Updating known taints on node", "node", klog.KObj(node), "taints", taints) 560 if len(taints) == 0 { 561 delete(tc.taintedNodes, node.Name) 562 } else { 563 tc.taintedNodes[node.Name] = taints 564 } 565 }() 566 567 // This is critical that we update tc.taintedNodes before we call getPodsAssignedToNode: 568 // getPodsAssignedToNode can be delayed as long as all future updates to pods will call 569 // tc.PodUpdated which will use tc.taintedNodes to potentially delete delayed pods. 570 pods, err := tc.getPodsAssignedToNode(node.Name) 571 if err != nil { 572 logger.Error(err, "Failed to get pods assigned to node", "node", klog.KObj(node)) 573 return 574 } 575 if len(pods) == 0 { 576 return 577 } 578 // Short circuit, to make this controller a bit faster. 579 if len(taints) == 0 { 580 logger.V(4).Info("All taints were removed from the node. Cancelling all evictions...", "node", klog.KObj(node)) 581 for i := range pods { 582 tc.cancelWorkWithEvent(logger, types.NamespacedName{Namespace: pods[i].Namespace, Name: pods[i].Name}) 583 } 584 return 585 } 586 587 now := time.Now() 588 for _, pod := range pods { 589 podNamespacedName := types.NamespacedName{Namespace: pod.Namespace, Name: pod.Name} 590 tc.processPodOnNode(ctx, podNamespacedName, node.Name, pod.Spec.Tolerations, taints, now) 591 } 592 } 593 594 func (tc *Controller) emitPodDeletionEvent(nsName types.NamespacedName) { 595 if tc.recorder == nil { 596 return 597 } 598 ref := &v1.ObjectReference{ 599 APIVersion: "v1", 600 Kind: "Pod", 601 Name: nsName.Name, 602 Namespace: nsName.Namespace, 603 } 604 tc.recorder.Eventf(ref, v1.EventTypeNormal, "TaintManagerEviction", "Marking for deletion Pod %s", nsName.String()) 605 } 606 607 func (tc *Controller) emitCancelPodDeletionEvent(nsName types.NamespacedName) { 608 if tc.recorder == nil { 609 return 610 } 611 ref := &v1.ObjectReference{ 612 APIVersion: "v1", 613 Kind: "Pod", 614 Name: nsName.Name, 615 Namespace: nsName.Namespace, 616 } 617 tc.recorder.Eventf(ref, v1.EventTypeNormal, "TaintManagerEviction", "Cancelling deletion of Pod %s", nsName.String()) 618 }