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  }