github.com/kubewharf/katalyst-core@v0.5.3/pkg/controller/monitor/cnr.go (about)

     1  /*
     2  Copyright 2022 The Katalyst 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 monitor
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"sync"
    23  	"time"
    24  
    25  	corev1 "k8s.io/api/core/v1"
    26  	utilruntime "k8s.io/apimachinery/pkg/util/runtime"
    27  	"k8s.io/apimachinery/pkg/util/wait"
    28  	coreinformers "k8s.io/client-go/informers/core/v1"
    29  	corelisters "k8s.io/client-go/listers/core/v1"
    30  	"k8s.io/client-go/tools/cache"
    31  	"k8s.io/client-go/util/workqueue"
    32  	"k8s.io/cri-api/pkg/errors"
    33  	"k8s.io/klog/v2"
    34  
    35  	apis "github.com/kubewharf/katalyst-api/pkg/apis/node/v1alpha1"
    36  	informers "github.com/kubewharf/katalyst-api/pkg/client/informers/externalversions/node/v1alpha1"
    37  	listers "github.com/kubewharf/katalyst-api/pkg/client/listers/node/v1alpha1"
    38  	"github.com/kubewharf/katalyst-core/pkg/client"
    39  	"github.com/kubewharf/katalyst-core/pkg/config/controller"
    40  	"github.com/kubewharf/katalyst-core/pkg/config/generic"
    41  	"github.com/kubewharf/katalyst-core/pkg/metrics"
    42  	"github.com/kubewharf/katalyst-core/pkg/util/native"
    43  )
    44  
    45  const (
    46  	cnrMonitorControllerName = "cnr-monitor"
    47  	cnrMonitorWorkerCount    = 1
    48  )
    49  
    50  const (
    51  	// maxToleranceLantency is the max tolerance lantency for cnr report lantency
    52  	maxToleranceLantency = 5 * time.Minute
    53  )
    54  
    55  type CNRMonitorController struct {
    56  	ctx context.Context
    57  
    58  	client *client.GenericClientSet
    59  
    60  	cnrListerSynced  cache.InformerSynced
    61  	cnrLister        listers.CustomNodeResourceLister
    62  	nodeListerSynced cache.InformerSynced
    63  	nodeLister       corelisters.NodeLister
    64  	podListerSynced  cache.InformerSynced
    65  	podLister        corelisters.PodLister
    66  
    67  	// queue for cnr
    68  	cnrSyncQueue workqueue.RateLimitingInterface
    69  
    70  	// metricsEmitter for emit metrics
    71  	metricsEmitter metrics.MetricEmitter
    72  
    73  	// podTimeMap for record pod scheduled time
    74  	podTimeMap sync.Map
    75  }
    76  
    77  // NewCNRMonitorController create a new CNRMonitorController
    78  func NewCNRMonitorController(
    79  	ctx context.Context,
    80  	genericConf *generic.GenericConfiguration,
    81  	_ *controller.GenericControllerConfiguration,
    82  	_ *controller.CNRMonitorConfig,
    83  	client *client.GenericClientSet,
    84  	nodeInformer coreinformers.NodeInformer,
    85  	podInformer coreinformers.PodInformer,
    86  	cnrInformer informers.CustomNodeResourceInformer,
    87  	metricsEmitter metrics.MetricEmitter,
    88  ) (*CNRMonitorController, error) {
    89  	cnrMonitorController := &CNRMonitorController{
    90  		ctx:          ctx,
    91  		client:       client,
    92  		cnrSyncQueue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), cnrMonitorControllerName),
    93  		podTimeMap:   sync.Map{},
    94  	}
    95  
    96  	// init cnr informer
    97  	cnrInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
    98  		AddFunc:    cnrMonitorController.addCNREventHandler,
    99  		UpdateFunc: cnrMonitorController.updateCNREventHandler,
   100  	})
   101  	// init cnr lister
   102  	cnrMonitorController.cnrLister = cnrInformer.Lister()
   103  	// init cnr synced
   104  	cnrMonitorController.cnrListerSynced = cnrInformer.Informer().HasSynced
   105  
   106  	// init node lister
   107  	cnrMonitorController.nodeLister = nodeInformer.Lister()
   108  	// init node synced
   109  	cnrMonitorController.nodeListerSynced = nodeInformer.Informer().HasSynced
   110  
   111  	// init pod informer
   112  	podInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
   113  		UpdateFunc: cnrMonitorController.updatePodEventHandler,
   114  	})
   115  	// init pod lister
   116  	cnrMonitorController.podLister = podInformer.Lister()
   117  	// init pod synced
   118  	cnrMonitorController.podListerSynced = podInformer.Informer().HasSynced
   119  
   120  	if metricsEmitter == nil {
   121  		// if metricsEmitter is nil, use dummy metrics
   122  		cnrMonitorController.metricsEmitter = metrics.DummyMetrics{}
   123  	} else {
   124  		// if metricsEmitter is not nil, use metricsEmitter with tags
   125  		cnrMonitorController.metricsEmitter = metricsEmitter.WithTags(cnrMonitorControllerName)
   126  	}
   127  
   128  	return cnrMonitorController, nil
   129  }
   130  
   131  func (ctrl *CNRMonitorController) Run() {
   132  	defer utilruntime.HandleCrash()
   133  	defer ctrl.cnrSyncQueue.ShutDown()
   134  	defer klog.Infof("Shutting down %s controller", cnrMonitorControllerName)
   135  
   136  	// wait for cnr cache sync
   137  	if !cache.WaitForCacheSync(ctrl.ctx.Done(), ctrl.cnrListerSynced, ctrl.nodeListerSynced, ctrl.podListerSynced) {
   138  		utilruntime.HandleError(fmt.Errorf("unable to sync caches for %s controller", cnrMonitorControllerName))
   139  		return
   140  	}
   141  
   142  	klog.Infof("Caches are synced for %s controller", cnrMonitorControllerName)
   143  	klog.Infof("start %d workers for %s controller", cnrMonitorWorkerCount, cnrMonitorControllerName)
   144  
   145  	for i := 0; i < cnrMonitorWorkerCount; i++ {
   146  		go wait.Until(ctrl.cnrMonitorWorker, time.Second, ctrl.ctx.Done())
   147  	}
   148  
   149  	// gc podTimeMap
   150  	klog.Infof("start gc podTimeMap...")
   151  	go wait.Until(ctrl.gcPodTimeMap, maxToleranceLantency, ctrl.ctx.Done())
   152  
   153  	<-ctrl.ctx.Done()
   154  }
   155  
   156  func (ctrl *CNRMonitorController) cnrMonitorWorker() {
   157  	for ctrl.processNextCNR() {
   158  	}
   159  }
   160  
   161  // processNextCNR dequeues items, processes them, and marks them done.
   162  // It enforces that the sync is never invoked concurrently with the same key.
   163  func (ctrl *CNRMonitorController) processNextCNR() bool {
   164  	key, quit := ctrl.cnrSyncQueue.Get()
   165  	if quit {
   166  		return false
   167  	}
   168  	defer ctrl.cnrSyncQueue.Done(key)
   169  
   170  	err := ctrl.syncCNR(key.(string))
   171  	if err == nil {
   172  		ctrl.cnrSyncQueue.Forget(key)
   173  		return true
   174  	}
   175  
   176  	// if err is not nil, requeue the key
   177  	utilruntime.HandleError(fmt.Errorf("sync %s failed with %v", key, err))
   178  	ctrl.cnrSyncQueue.AddRateLimited(key)
   179  
   180  	return true
   181  }
   182  
   183  func (ctrl *CNRMonitorController) syncCNR(key string) error {
   184  	_, name, err := cache.SplitMetaNamespaceKey(key)
   185  	if err != nil {
   186  		return err
   187  	}
   188  
   189  	cnr, err := ctrl.cnrLister.Get(name)
   190  	if errors.IsNotFound(err) {
   191  		// cnr is deleted, so we can skip
   192  		klog.Info("CNR has been deleted %v", key)
   193  		return nil
   194  	}
   195  	if err != nil {
   196  		return err
   197  	}
   198  
   199  	// hasAnomaly is used to record whether cnr has anomaly
   200  	hasAnomaly := false
   201  	// check numa exclusive anomaly
   202  	klog.Infof("Check Numa Exclusive Anomaly...")
   203  	if ctrl.checkNumaExclusiveAnomaly(cnr) {
   204  		hasAnomaly = true
   205  		klog.Infof("Emit Numa Exclusive Anomaly metric...")
   206  		err = ctrl.emitCNRAnomalyMetric(cnr, reasonNumaExclusiveAnomaly)
   207  		if err != nil {
   208  			return err
   209  		}
   210  	}
   211  	// check numa allocatable sum anomaly
   212  	klog.Infof("Check Numa Allocatable Sum Anomaly...")
   213  	if ctrl.checkNumaAllocatableSumAnomaly(cnr) {
   214  		hasAnomaly = true
   215  		klog.Infof("Emit Numa Allocatable Sum Anomaly metric...")
   216  		err = ctrl.emitCNRAnomalyMetric(cnr, reasonNumaAllocatableSumAnomaly)
   217  		if err != nil {
   218  			return err
   219  		}
   220  	}
   221  
   222  	// check pod allocation sum anomaly
   223  	klog.Infof("Check Pod Allocation Sum Anomaly...")
   224  	if ctrl.checkPodAllocationSumAnomaly(cnr) {
   225  		hasAnomaly = true
   226  		klog.Infof("Emit Pod Allocation Sum Anomaly metric...")
   227  		err = ctrl.emitCNRAnomalyMetric(cnr, reasonPodAllocationSumAnomaly)
   228  		if err != nil {
   229  			return err
   230  		}
   231  	}
   232  
   233  	// if hasAnomaly is true, re-enqueue cnr use AddAfter func with 30s duration
   234  	if hasAnomaly {
   235  		klog.Infof("CNR %s has anomaly, re-enqueue it after 30s", cnr.Name)
   236  		ctrl.cnrSyncQueue.AddAfter(key, 30*time.Second)
   237  	}
   238  
   239  	return nil
   240  }
   241  
   242  // enqueueCNR enqueues the given CNR in the work queue.
   243  func (ctrl *CNRMonitorController) enqueueCNR(cnr *apis.CustomNodeResource) {
   244  	if cnr == nil {
   245  		klog.Warning("trying to enqueue a nil cnr")
   246  		return
   247  	}
   248  
   249  	key, err := cache.DeletionHandlingMetaNamespaceKeyFunc(cnr)
   250  	if err != nil {
   251  		utilruntime.HandleError(fmt.Errorf("Cound't get key for CNR %+v: %v", cnr, err))
   252  		return
   253  	}
   254  	ctrl.cnrSyncQueue.Add(key)
   255  }
   256  
   257  func (ctrl *CNRMonitorController) addCNREventHandler(obj interface{}) {
   258  	klog.Infof("CNR create event found...")
   259  	cnr, ok := obj.(*apis.CustomNodeResource)
   260  	if !ok {
   261  		klog.Errorf("cannot convert obj to *apis.CNR")
   262  		return
   263  	}
   264  	klog.V(4).Infof("notice addition of cnr %s", cnr.Name)
   265  
   266  	ctrl.enqueueCNR(cnr)
   267  }
   268  
   269  func (ctrl *CNRMonitorController) updateCNREventHandler(_, newObj interface{}) {
   270  	klog.Infof("CNR update event found...")
   271  	cnr, ok := newObj.(*apis.CustomNodeResource)
   272  	if !ok {
   273  		klog.Errorf("cannot convert newObj to *apis.CNR")
   274  		return
   275  	}
   276  	klog.V(4).Infof("notice update of cnr %s", cnr.Name)
   277  
   278  	// check and emit cnr pod report lantency metric
   279  	klog.Infof("Check and Emit CNR Report Lantency metric...")
   280  	err := ctrl.checkAndEmitCNRReportLantencyMetric(cnr)
   281  	if err != nil {
   282  		klog.Errorf("check and emit cnr report lantency metric failed: %v", err)
   283  	}
   284  
   285  	ctrl.enqueueCNR(cnr)
   286  }
   287  
   288  func (ctrl *CNRMonitorController) updatePodEventHandler(oldObj, newObj interface{}) {
   289  	klog.Infof("Pod update event found...")
   290  	oldPod, ok := oldObj.(*corev1.Pod)
   291  	if !ok {
   292  		klog.Errorf("cannot convert oldObj to Pod")
   293  		return
   294  	}
   295  	newPod, ok := newObj.(*corev1.Pod)
   296  	if !ok {
   297  		klog.Errorf("cannot convert newObj to Pod")
   298  		return
   299  	}
   300  	if oldPod.Spec.NodeName == "" && newPod.Spec.NodeName != "" {
   301  		klog.Infof("notice pod: %v scheduled to node: %v", newPod.Name, newPod.Spec.NodeName)
   302  		// record pod scheduled time
   303  		ctrl.podTimeMap.Store(native.GenerateUniqObjectUIDKey(newPod), time.Now())
   304  	}
   305  }
   306  
   307  // gcPodTimeMap gc podTimeMap which over maxToleranceLantency not used
   308  func (ctrl *CNRMonitorController) gcPodTimeMap() {
   309  	klog.Infof("gc podTimeMap...")
   310  	ctrl.podTimeMap.Range(func(key, value interface{}) bool {
   311  		notUsedTime := time.Now().Sub(value.(time.Time))
   312  		if notUsedTime > maxToleranceLantency {
   313  			klog.Infof("gc podTimeMap: %v, which not used over %v minutes", key, notUsedTime.Minutes())
   314  			ctrl.podTimeMap.Delete(key)
   315  			namespace, podName, _, err := native.ParseUniqObjectUIDKey(key.(string))
   316  			if err != nil {
   317  				klog.Errorf("failed to parse uniq object uid key %s", key)
   318  				return true
   319  			}
   320  			pod, err := ctrl.podLister.Pods(namespace).Get(podName)
   321  			if err != nil {
   322  				klog.Errorf("failed to get pod %s/%s", namespace, podName)
   323  				return true
   324  			}
   325  			if !native.PodIsTerminated(pod) {
   326  				klog.Infof("Emit Timeout CNR Report Lantency metric...")
   327  				// emit timeout cnr report lantency metric
   328  				ctrl.emitCNRReportLantencyMetric(pod.Spec.NodeName, key.(string), maxToleranceLantency.Milliseconds(), "true")
   329  				if err != nil {
   330  					klog.Errorf("emit cnr report lantency metric failed: %v", err)
   331  				}
   332  			}
   333  		}
   334  		return true // return true to continue iterating
   335  	})
   336  }