github.com/kubewharf/katalyst-core@v0.5.3/pkg/webhook/mutating/pod/resource_mutator.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 pod
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  
    23  	core "k8s.io/api/core/v1"
    24  	"k8s.io/apimachinery/pkg/runtime/schema"
    25  	"k8s.io/client-go/tools/cache"
    26  	"k8s.io/klog/v2"
    27  
    28  	apis "github.com/kubewharf/katalyst-api/pkg/apis/autoscaling/v1alpha1"
    29  	autoscalelister "github.com/kubewharf/katalyst-api/pkg/client/listers/autoscaling/v1alpha1"
    30  	"github.com/kubewharf/katalyst-core/pkg/util"
    31  	katalystutil "github.com/kubewharf/katalyst-core/pkg/util"
    32  )
    33  
    34  var supportedResources = []core.ResourceName{core.ResourceCPU, core.ResourceMemory}
    35  
    36  type WebhookPodResourceMutator struct {
    37  	ctx context.Context
    38  
    39  	vpaIndexer cache.Indexer
    40  
    41  	vpaLister      autoscalelister.KatalystVerticalPodAutoscalerLister
    42  	workloadLister map[schema.GroupVersionKind]cache.GenericLister
    43  }
    44  
    45  // NewWebhookPodResourceMutator will mutate pod resource according to vpa
    46  func NewWebhookPodResourceMutator(
    47  	ctx context.Context,
    48  	vpaIndexer cache.Indexer,
    49  	vpaLister autoscalelister.KatalystVerticalPodAutoscalerLister,
    50  	workloadLister map[schema.GroupVersionKind]cache.GenericLister,
    51  ) *WebhookPodResourceMutator {
    52  	r := WebhookPodResourceMutator{
    53  		ctx:            ctx,
    54  		vpaIndexer:     vpaIndexer,
    55  		vpaLister:      vpaLister,
    56  		workloadLister: workloadLister,
    57  	}
    58  	return &r
    59  }
    60  
    61  // isVPANeededInWebhook returns if vpa is needed in webhook by checking its PodMatchingStrategy
    62  func (r *WebhookPodResourceMutator) isVPANeededInWebhook(vpa *apis.KatalystVerticalPodAutoscaler) bool {
    63  	if vpa == nil {
    64  		return false
    65  	}
    66  	return vpa.Spec.UpdatePolicy.PodMatchingStrategy != apis.PodMatchingStrategyForHistoricalPod
    67  }
    68  
    69  func (r *WebhookPodResourceMutator) mergeRecommendationIntoPodResource(pod *core.Pod, recommendations map[string]core.ResourceRequirements) error {
    70  	merge := func(resource core.ResourceList, recommendation core.ResourceList) core.ResourceList {
    71  		for _, name := range supportedResources {
    72  			if value, ok := recommendation[name]; ok {
    73  				resource[name] = value
    74  			}
    75  		}
    76  		return resource
    77  	}
    78  
    79  	if pod == nil {
    80  		err := fmt.Errorf("pod is nil")
    81  		klog.Error(err.Error())
    82  		return err
    83  	}
    84  	for i := 0; i < len(pod.Spec.Containers); i++ {
    85  		container := &pod.Spec.Containers[i]
    86  		container.Resources.Limits = merge(container.Resources.Limits, recommendations[container.Name].Limits)
    87  		container.Resources.Requests = merge(container.Resources.Requests, recommendations[container.Name].Requests)
    88  	}
    89  	return nil
    90  }
    91  
    92  func (r *WebhookPodResourceMutator) MutatePod(pod *core.Pod, namespace string) (mutated bool, err error) {
    93  	if pod == nil {
    94  		err := fmt.Errorf("pod is nil")
    95  		klog.Error(err.Error())
    96  		return false, err
    97  	}
    98  
    99  	pod.Namespace = namespace
   100  	vpa, err := katalystutil.GetVPAForPod(pod, r.vpaIndexer, r.workloadLister, r.vpaLister)
   101  	if err != nil || vpa == nil {
   102  		klog.Warningf("didn't to find vpa of pod %v/%v, err: %v", pod.Namespace, pod.Name, err)
   103  		return false, nil
   104  	}
   105  
   106  	gvk := schema.FromAPIVersionAndKind(vpa.Spec.TargetRef.APIVersion, vpa.Spec.TargetRef.Kind)
   107  	workloadLister, ok := r.workloadLister[gvk]
   108  	if !ok {
   109  		klog.Errorf("vpa %s/%s without workload lister", vpa.Namespace, vpa.Name)
   110  		return false, nil
   111  	}
   112  
   113  	workload, err := katalystutil.GetWorkloadForVPA(vpa, workloadLister)
   114  	if err != nil {
   115  		klog.Warning("didn't to find workload of pod %v/%v, err: %v", pod.Namespace, pod.Name, err)
   116  		return false, nil
   117  	}
   118  
   119  	// check if pod has been enabled with vpa functionality
   120  	if !katalystutil.CheckWorkloadEnableVPA(workload) {
   121  		klog.V(6).Infof("VPA of pod %s is not supported", pod.Name)
   122  		return false, nil
   123  	}
   124  
   125  	needVPA := r.isVPANeededInWebhook(vpa)
   126  	if !needVPA {
   127  		return false, nil
   128  	}
   129  
   130  	podResources, containerResources, err := util.GenerateVPAResourceMap(vpa)
   131  	if err != nil {
   132  		klog.Errorf("failed to get container resource from VPA %s", vpa.Name)
   133  		return false, err
   134  	}
   135  
   136  	annotationResource, err := util.GenerateVPAPodResizeResourceAnnotations(pod, podResources, containerResources)
   137  	if err != nil {
   138  		klog.Errorf("failed to exact pod %v resize resource annotation from container resource: %v", pod, err)
   139  		return false, err
   140  	}
   141  
   142  	isLegal, msg, err := katalystutil.CheckVPAStatusLegal(vpa, []*core.Pod{pod})
   143  	if err != nil {
   144  		klog.Errorf("failed to check pod %v status whether is legal: %v", pod, err)
   145  		return false, err
   146  	} else if !isLegal {
   147  		klog.Errorf("vpa status is illegal: %s", msg)
   148  		return false, nil
   149  	}
   150  
   151  	err = r.mergeRecommendationIntoPodResource(pod, annotationResource)
   152  	if err != nil {
   153  		klog.Error("failed to merge recommendation to pod resource")
   154  		return false, err
   155  	}
   156  
   157  	return true, nil
   158  }