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 }