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 }