github.com/kubewharf/katalyst-core@v0.5.3/pkg/controller/vpa/vparec.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 vpa 18 19 import ( 20 "context" 21 "fmt" 22 "time" 23 24 core "k8s.io/api/core/v1" 25 apiequality "k8s.io/apimachinery/pkg/api/equality" 26 "k8s.io/apimachinery/pkg/api/errors" 27 utilruntime "k8s.io/apimachinery/pkg/util/runtime" 28 "k8s.io/apimachinery/pkg/util/wait" 29 "k8s.io/client-go/tools/cache" 30 "k8s.io/client-go/util/workqueue" 31 "k8s.io/klog/v2" 32 33 apis "github.com/kubewharf/katalyst-api/pkg/apis/autoscaling/v1alpha1" 34 autoscalelister "github.com/kubewharf/katalyst-api/pkg/client/listers/autoscaling/v1alpha1" 35 apiconsts "github.com/kubewharf/katalyst-api/pkg/consts" 36 katalystbase "github.com/kubewharf/katalyst-core/cmd/base" 37 "github.com/kubewharf/katalyst-core/pkg/client/control" 38 "github.com/kubewharf/katalyst-core/pkg/config/controller" 39 "github.com/kubewharf/katalyst-core/pkg/config/generic" 40 "github.com/kubewharf/katalyst-core/pkg/consts" 41 "github.com/kubewharf/katalyst-core/pkg/controller/vpa/util" 42 "github.com/kubewharf/katalyst-core/pkg/metrics" 43 katalystutil "github.com/kubewharf/katalyst-core/pkg/util" 44 "github.com/kubewharf/katalyst-core/pkg/util/native" 45 ) 46 47 const vpaRecControllerName = "vpaRec" 48 49 const metricNameVPARecControlVPASyncCosts = "vpa_rec_vpa_sync_costs" 50 51 // VerticalPodAutoScaleRecommendationController is responsible to sync 52 // the recommendation results in vpa-rec CR to vpa CR. 53 // 54 // although we use informer index mechanism to speed up the looking 55 // efficiency, we can't assume that all function callers MUST use an 56 // indexed informer to look up objects. 57 type VerticalPodAutoScaleRecommendationController struct { 58 ctx context.Context 59 60 vpaUpdater control.VPAUpdater 61 vpaRecUpdater control.VPARecommendationUpdater 62 63 vpaRecIndexer cache.Indexer 64 65 vpaLister autoscalelister.KatalystVerticalPodAutoscalerLister 66 vpaRecLister autoscalelister.VerticalPodAutoscalerRecommendationLister 67 68 syncedFunc []cache.InformerSynced 69 70 vpaQueue workqueue.RateLimitingInterface 71 vpaRecQueue workqueue.RateLimitingInterface 72 73 metricsEmitter metrics.MetricEmitter 74 75 vpaSyncWorkers int 76 vparecSyncWorkers int 77 } 78 79 func NewVPARecommendationController(ctx context.Context, 80 controlCtx *katalystbase.GenericContext, 81 genericConf *generic.GenericConfiguration, 82 _ *controller.GenericControllerConfiguration, 83 conf *controller.VPAConfig, 84 ) (*VerticalPodAutoScaleRecommendationController, error) { 85 if controlCtx == nil { 86 return nil, fmt.Errorf("controlCtx is invalid") 87 } 88 89 vpaInformer := controlCtx.InternalInformerFactory.Autoscaling().V1alpha1().KatalystVerticalPodAutoscalers() 90 vpaRecInformer := controlCtx.InternalInformerFactory.Autoscaling().V1alpha1().VerticalPodAutoscalerRecommendations() 91 92 genericClient := controlCtx.Client 93 vpaRecController := &VerticalPodAutoScaleRecommendationController{ 94 ctx: ctx, 95 96 vpaUpdater: &control.DummyVPAUpdater{}, 97 vpaRecUpdater: &control.DummyVPARecommendationUpdater{}, 98 99 vpaRecIndexer: vpaRecInformer.Informer().GetIndexer(), 100 101 vpaLister: vpaInformer.Lister(), 102 vpaRecLister: vpaRecInformer.Lister(), 103 104 vpaQueue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "vpa"), 105 vpaRecQueue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "vpaRec"), 106 107 syncedFunc: []cache.InformerSynced{ 108 vpaInformer.Informer().HasSynced, 109 vpaRecInformer.Informer().HasSynced, 110 }, 111 vpaSyncWorkers: conf.VPASyncWorkers, 112 vparecSyncWorkers: conf.VPARecSyncWorkers, 113 } 114 115 vpaInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ 116 AddFunc: vpaRecController.addVPA, 117 UpdateFunc: vpaRecController.updateVPA, 118 }) 119 120 vpaRecInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ 121 AddFunc: vpaRecController.addVPARec, 122 UpdateFunc: vpaRecController.updateVPARec, 123 }) 124 125 // build index: vpa ---> vpaRec 126 if _, ok := vpaRecController.vpaRecIndexer.GetIndexers()[consts.OwnerReferenceIndex]; !ok { 127 err := vpaRecController.vpaRecIndexer.AddIndexers(cache.Indexers{ 128 consts.OwnerReferenceIndex: native.ObjectOwnerReferenceIndex, 129 }) 130 if err != nil { 131 klog.Errorf("failed to add owner vpa index") 132 return nil, err 133 } 134 } 135 136 if !genericConf.DryRun { 137 vpaRecController.vpaUpdater = control.NewRealVPAUpdater(genericClient.InternalClient) 138 vpaRecController.vpaRecUpdater = control.NewRealVPARecommendationUpdater(genericClient.InternalClient) 139 } 140 141 vpaRecController.metricsEmitter = controlCtx.EmitterPool.GetDefaultMetricsEmitter() 142 if vpaRecController.metricsEmitter == nil { 143 vpaRecController.metricsEmitter = metrics.DummyMetrics{} 144 } 145 146 return vpaRecController, nil 147 } 148 149 func (rec *VerticalPodAutoScaleRecommendationController) Run() { 150 defer utilruntime.HandleCrash() 151 defer rec.vpaRecQueue.ShutDown() 152 153 defer klog.Infof("[vpa-rec] shutting down %s controller", vpaRecControllerName) 154 155 if !cache.WaitForCacheSync(rec.ctx.Done(), rec.syncedFunc...) { 156 utilruntime.HandleError(fmt.Errorf("unable to sync caches for %s controller", vpaRecControllerName)) 157 return 158 } 159 klog.Infof("[vpa-rec] caches are synced for %s controller", vpaRecControllerName) 160 161 klog.Infof("[vpa-rec] start %v vpaSyncWorkers and %v vparecSyncWorkers", rec.vpaSyncWorkers, rec.vparecSyncWorkers) 162 163 for i := 0; i < rec.vparecSyncWorkers; i++ { 164 go wait.Until(rec.vpaRecWorker, time.Second, rec.ctx.Done()) 165 } 166 167 for i := 0; i < rec.vpaSyncWorkers; i++ { 168 go wait.Until(rec.vpaWorker, time.Second, rec.ctx.Done()) 169 } 170 171 <-rec.ctx.Done() 172 } 173 174 func (rec *VerticalPodAutoScaleRecommendationController) addVPA(obj interface{}) { 175 v, ok := obj.(*apis.KatalystVerticalPodAutoscaler) 176 if !ok { 177 klog.Errorf("[vpa-rec] cannot convert obj to *apis.KatalystVerticalPodAutoscaler: %v", obj) 178 return 179 } 180 181 klog.V(4).Infof("[vpa-rec] notice addition of KatalystVerticalPodAutoscaler %s", v.Name) 182 rec.enqueueVPA(v) 183 } 184 185 func (rec *VerticalPodAutoScaleRecommendationController) updateVPA(old, cur interface{}) { 186 oldVPA, ok := old.(*apis.KatalystVerticalPodAutoscaler) 187 if !ok { 188 klog.Errorf("[vpa-rec] cannot convert oldObj to *apis.KatalystVerticalPodAutoscaler: %v", old) 189 return 190 } 191 192 curVPA, ok := cur.(*apis.KatalystVerticalPodAutoscaler) 193 if !ok { 194 klog.Errorf("[vpa-rec] cannot convert curObj to *apis.KatalystVerticalPodAutoscaler: %v", cur) 195 return 196 } 197 198 klog.V(4).Infof("[vpa-rec] notice update of KatalystVerticalPodAutoscaler %s", curVPA.Name) 199 rec.enqueueVPA(curVPA) 200 201 if !apiequality.Semantic.DeepEqual(oldVPA.Spec, curVPA.Spec) || 202 !apiequality.Semantic.DeepEqual(oldVPA.Status, curVPA.Status) { 203 vpaRec, err := katalystutil.GetVPARecForVPA(curVPA, rec.vpaRecIndexer, rec.vpaRecLister) 204 if err != nil { 205 return 206 } 207 // if vpa spec changed, we also trigger vpa rec to update vpa status 208 rec.enqueueVPARec(vpaRec) 209 } 210 } 211 212 func (rec *VerticalPodAutoScaleRecommendationController) enqueueVPA(vpa *apis.KatalystVerticalPodAutoscaler) { 213 if vpa == nil { 214 klog.Warning("[spd] trying to enqueue a nil vpa") 215 return 216 } 217 218 key, err := cache.DeletionHandlingMetaNamespaceKeyFunc(vpa) 219 if err != nil { 220 utilruntime.HandleError(err) 221 return 222 } 223 rec.vpaQueue.Add(key) 224 } 225 226 func (rec *VerticalPodAutoScaleRecommendationController) vpaWorker() { 227 for rec.processNextVpa() { 228 } 229 } 230 231 func (rec *VerticalPodAutoScaleRecommendationController) processNextVpa() bool { 232 key, quit := rec.vpaQueue.Get() 233 if quit { 234 return false 235 } 236 defer rec.vpaQueue.Done(key) 237 238 err := rec.syncVPA(key.(string)) 239 if err == nil { 240 rec.vpaQueue.Forget(key) 241 return true 242 } 243 244 utilruntime.HandleError(fmt.Errorf("sync %q failed with %v", key, err)) 245 rec.vpaQueue.AddRateLimited(key) 246 247 return true 248 } 249 250 // syncVPA is mainly responsible to maintain vpaRec name in vpa annotations and 251 // update vpa status into vpaRec status 252 func (rec *VerticalPodAutoScaleRecommendationController) syncVPA(key string) error { 253 namespace, name, err := cache.SplitMetaNamespaceKey(key) 254 if err != nil { 255 klog.Errorf("[vpa-rec] failed to split namespace and name from key %s", key) 256 return err 257 } 258 259 vpa, err := rec.vpaLister.KatalystVerticalPodAutoscalers(namespace).Get(name) 260 if err != nil { 261 if errors.IsNotFound(err) { 262 klog.Warningf("[vpa-rec] vpa %s/%s is not found", namespace, name) 263 return nil 264 } 265 klog.Errorf("[vpa-rec] vpa %s/%s get error: %v", namespace, name, err) 266 return err 267 } 268 269 vpaRec, err := katalystutil.GetVPARecForVPA(vpa, rec.vpaRecIndexer, rec.vpaRecLister) 270 if err != nil { 271 klog.Errorf("[vpa-rec] vpa %s no longer matches with vpaRec %s: %v, clear", name, vpa.Name, err) 272 return rec.clearVPAAnnotations(vpa) 273 } 274 275 recPodResources, recContainerResources, err := util.GetVPARecResourceStatus(vpa.Status.PodResources, vpa.Status.ContainerResources) 276 if err != nil { 277 klog.Errorf("[vpa-rec] generate vpaRec resource for vpa %s error: %v", vpa.Name, err) 278 return err 279 } 280 281 if err := rec.updateVPARecStatus(vpaRec, recPodResources, recContainerResources); err != nil { 282 klog.Errorf("[vpa-rec] update vpaRec resource for vpa %s error: %v", vpa.Name, err) 283 return err 284 } 285 286 return rec.setVPAAnnotations(vpa, vpaRec.Name) 287 } 288 289 func (rec *VerticalPodAutoScaleRecommendationController) addVPARec(obj interface{}) { 290 v, ok := obj.(*apis.VerticalPodAutoscalerRecommendation) 291 if !ok { 292 klog.Errorf("[vpa-rec] cannot convert obj to *apis.VerticalPodAutoscalerRecommendation: %v", obj) 293 return 294 } 295 klog.V(4).Infof("[vpa-rec] notice addition of VerticalPodAutoscalerRecommendation %s", v.Name) 296 rec.enqueueVPARec(v) 297 } 298 299 func (rec *VerticalPodAutoScaleRecommendationController) updateVPARec(old, cur interface{}) { 300 oldVPA, ok := old.(*apis.VerticalPodAutoscalerRecommendation) 301 if !ok { 302 klog.Errorf("[vpa-rec] cannot convert oldObj to *apis.VerticalPodAutoscalerRecommendation: %v", old) 303 return 304 } 305 curVPA, ok := cur.(*apis.VerticalPodAutoscalerRecommendation) 306 if !ok { 307 klog.Errorf("[vpa-rec] cannot convert curObj to *apis.VerticalPodAutoscalerRecommendation: %v", cur) 308 return 309 } 310 311 // only handle vpaRec whose spec was updated 312 if !apiequality.Semantic.DeepEqual(oldVPA.Spec, curVPA.Spec) { 313 klog.V(4).Infof("[vpa-rec] notice update of VerticalPodAutoscalerRecommendation %s", curVPA.Name) 314 rec.enqueueVPARec(curVPA) 315 } 316 } 317 318 func (rec *VerticalPodAutoScaleRecommendationController) enqueueVPARec(vpaRec *apis.VerticalPodAutoscalerRecommendation) { 319 if vpaRec == nil { 320 klog.Warning("[spd] trying to enqueue a nil vpaRec") 321 return 322 } 323 324 key, err := cache.DeletionHandlingMetaNamespaceKeyFunc(vpaRec) 325 if err != nil { 326 utilruntime.HandleError(err) 327 return 328 } 329 rec.vpaRecQueue.Add(key) 330 } 331 332 func (rec *VerticalPodAutoScaleRecommendationController) vpaRecWorker() { 333 for rec.processNextVpaRec() { 334 } 335 } 336 337 func (rec *VerticalPodAutoScaleRecommendationController) processNextVpaRec() bool { 338 key, quit := rec.vpaRecQueue.Get() 339 if quit { 340 return false 341 } 342 defer rec.vpaRecQueue.Done(key) 343 344 err := rec.syncVPARec(key.(string)) 345 if err == nil { 346 rec.vpaRecQueue.Forget(key) 347 return true 348 } 349 350 utilruntime.HandleError(fmt.Errorf("sync %q failed with %v", key, err)) 351 rec.vpaRecQueue.AddRateLimited(key) 352 353 return true 354 } 355 356 // syncVPARec is mainly responsible to sync recommended spec in vpaRec to vpa status, 357 // as well as to vpa status (if vpa status is successfully updated) 358 func (rec *VerticalPodAutoScaleRecommendationController) syncVPARec(key string) error { 359 namespace, name, err := cache.SplitMetaNamespaceKey(key) 360 if err != nil { 361 klog.Errorf("[vpa-rec] failed to split namespace and name from key %s", key) 362 return err 363 } 364 365 begin := time.Now() 366 defer func() { 367 costs := time.Since(begin).Microseconds() 368 klog.Infof("[vpa-rec] syncing vpaRec [%v/%v] costs %v us", namespace, name, costs) 369 _ = rec.metricsEmitter.StoreInt64(metricNameVPARecControlVPASyncCosts, costs, metrics.MetricTypeNameRaw, 370 metrics.MetricTag{Key: "vpa_namespace", Val: namespace}, 371 metrics.MetricTag{Key: "vpa_name", Val: name}, 372 ) 373 }() 374 375 vpaRec, err := rec.vpaRecLister.VerticalPodAutoscalerRecommendations(namespace).Get(name) 376 if err != nil { 377 if errors.IsNotFound(err) { 378 klog.Warningf("[vpa-rec] vpaRec %s/%s is not found", namespace, name) 379 return nil 380 } 381 382 klog.Errorf("[vpa-rec] vpaRec %s/%s get error: %v", namespace, name, err) 383 return err 384 } 385 386 vpa, err := katalystutil.GetVPAForVPARec(vpaRec, rec.vpaLister) 387 if err != nil { 388 klog.Errorf("[vpa-rec] get vpa fpr vpaRec %s/%s error: %v", namespace, name, err) 389 return err 390 } 391 392 podResources, containerResources, err := util.GetVPAResourceStatusWithRecommendation(vpa, vpaRec.Spec.PodRecommendations, vpaRec.Spec.ContainerRecommendations) 393 if err != nil { 394 klog.Errorf("[vpa-rec] generate vpa resource for vpa %s error: %v", vpa.Name, err) 395 return err 396 } 397 398 if err := rec.updateVPAStatus(vpa, podResources, containerResources); err != nil { 399 klog.Errorf("[vpa-rec] update vpa resource for vpa %s error: %v", vpa.Name, err) 400 return err 401 } 402 return nil 403 } 404 405 // setVPAAnnotations add vpaRec name in vpa annotations 406 func (rec *VerticalPodAutoScaleRecommendationController) setVPAAnnotations(vpa *apis.KatalystVerticalPodAutoscaler, vpaRecName string) error { 407 if vpa.Annotations[apiconsts.VPAAnnotationVPARecNameKey] == vpaRecName { 408 return nil 409 } 410 411 vpaCopy := vpa.DeepCopy() 412 if vpaCopy.Annotations == nil { 413 vpaCopy.Annotations = make(map[string]string) 414 } 415 vpaCopy.Annotations[apiconsts.VPAAnnotationVPARecNameKey] = vpaRecName 416 417 _, err := rec.vpaUpdater.PatchVPA(rec.ctx, vpa, vpaCopy) 418 if err != nil { 419 return err 420 } 421 422 return err 423 } 424 425 // clearVPAAnnotations removes vpaRec name in pod annotations 426 func (rec *VerticalPodAutoScaleRecommendationController) clearVPAAnnotations(vpa *apis.KatalystVerticalPodAutoscaler) error { 427 if _, ok := vpa.Annotations[apiconsts.VPAAnnotationVPARecNameKey]; !ok { 428 return nil 429 } 430 431 vpaCopy := vpa.DeepCopy() 432 delete(vpaCopy.Annotations, apiconsts.VPAAnnotationVPARecNameKey) 433 434 _, err := rec.vpaUpdater.PatchVPA(rec.ctx, vpa, vpaCopy) 435 if err != nil { 436 return err 437 } 438 439 return err 440 } 441 442 // updateVPAStatus is used to set status for vpa 443 func (rec *VerticalPodAutoScaleRecommendationController) updateVPAStatus(vpa *apis.KatalystVerticalPodAutoscaler, 444 vpaPodResources []apis.PodResources, vpaContainerResources []apis.ContainerResources, 445 ) error { 446 vpaNew := vpa.DeepCopy() 447 vpaNew.Status.PodResources = vpaPodResources 448 vpaNew.Status.ContainerResources = vpaContainerResources 449 if apiequality.Semantic.DeepEqual(vpaNew.Status, vpa.Status) { 450 return nil 451 } 452 453 _, err := rec.vpaUpdater.PatchVPAStatus(rec.ctx, vpa, vpaNew) 454 if err != nil { 455 klog.Errorf("[vpa-rec] failed to set vpa %v status: %v", vpa.Name, err) 456 return err 457 } 458 return nil 459 } 460 461 // updateVPAStatus is used to set status for vpaRec 462 func (rec *VerticalPodAutoScaleRecommendationController) updateVPARecStatus(vpaRec *apis.VerticalPodAutoscalerRecommendation, 463 recPodResources []apis.RecommendedPodResources, recContainerResources []apis.RecommendedContainerResources, 464 ) error { 465 vpaRecNew := vpaRec.DeepCopy() 466 vpaRecNew.Status.PodRecommendations = recPodResources 467 vpaRecNew.Status.ContainerRecommendations = recContainerResources 468 if apiequality.Semantic.DeepEqual(vpaRecNew.Status, vpaRec.Status) { 469 for _, condition := range vpaRecNew.Status.Conditions { 470 if condition.Type == apis.RecommendationUpdatedToVPA && condition.Status == core.ConditionTrue { 471 return nil 472 } 473 } 474 475 return util.PatchVPARecConditions(rec.ctx, rec.vpaRecUpdater, vpaRec, apis.RecommendationUpdatedToVPA, core.ConditionTrue, util.VPARecConditionReasonUpdated, "") 476 } 477 478 err := rec.vpaRecUpdater.PatchVPARecommendationStatus(rec.ctx, vpaRec, vpaRecNew) 479 if err != nil { 480 klog.Errorf("[vpa-rec] failed to set vpaRec %v status: %v", vpaRec.Name, err) 481 if err := util.PatchVPARecConditions(rec.ctx, rec.vpaRecUpdater, vpaRec, apis.RecommendationUpdatedToVPA, core.ConditionFalse, util.VPARecConditionReasonUpdated, err.Error()); err != nil { 482 return err 483 } 484 485 return err 486 } 487 return util.PatchVPARecConditions(rec.ctx, rec.vpaRecUpdater, vpaRec, apis.RecommendationUpdatedToVPA, core.ConditionTrue, util.VPARecConditionReasonUpdated, "") 488 }