@@ -21,10 +21,14 @@ import (
2121 "strings"
2222
2323 core "k8s.io/api/core/v1"
24+ "k8s.io/apimachinery/pkg/api/resource"
25+ "k8s.io/klog/v2"
2426
2527 resource_admission "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/admission-controller/resource"
2628 "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/admission-controller/resource/pod/recommendation"
2729 vpa_types "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/apis/autoscaling.k8s.io/v1"
30+ "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/features"
31+ "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/utils/annotations"
2832 resourcehelpers "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/utils/resources"
2933 vpa_api_util "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/utils/vpa"
3034)
@@ -37,13 +41,19 @@ const (
3741
3842type resourcesUpdatesPatchCalculator struct {
3943 recommendationProvider recommendation.Provider
44+ maxAllowedCPUBoost resource.Quantity
4045}
4146
4247// NewResourceUpdatesCalculator returns a calculator for
4348// resource update patches.
44- func NewResourceUpdatesCalculator (recommendationProvider recommendation.Provider ) Calculator {
49+ func NewResourceUpdatesCalculator (recommendationProvider recommendation.Provider , maxAllowedCPUBoost string ) Calculator {
50+ var maxAllowedCPUBoostQuantity resource.Quantity
51+ if maxAllowedCPUBoost != "" {
52+ maxAllowedCPUBoostQuantity = resource .MustParse (maxAllowedCPUBoost )
53+ }
4554 return & resourcesUpdatesPatchCalculator {
4655 recommendationProvider : recommendationProvider ,
56+ maxAllowedCPUBoost : maxAllowedCPUBoostQuantity ,
4757 }
4858}
4959
@@ -52,11 +62,22 @@ func (*resourcesUpdatesPatchCalculator) PatchResourceTarget() PatchResourceTarge
5262}
5363
5464func (c * resourcesUpdatesPatchCalculator ) CalculatePatches (pod * core.Pod , vpa * vpa_types.VerticalPodAutoscaler ) ([]resource_admission.PatchRecord , error ) {
65+ klog .Infof ("Calculating patches for pod %s/%s with VPA %s" , pod .Namespace , pod .Name , vpa .Name )
5566 result := []resource_admission.PatchRecord {}
5667
5768 containersResources , annotationsPerContainer , err := c .recommendationProvider .GetContainersResourcesForPod (pod , vpa )
5869 if err != nil {
59- return []resource_admission.PatchRecord {}, fmt .Errorf ("failed to calculate resource patch for pod %s/%s: %v" , pod .Namespace , pod .Name , err )
70+ return nil , fmt .Errorf ("failed to calculate resource patch for pod %s/%s: %v" , pod .Namespace , pod .Name , err )
71+ }
72+
73+ if vpa_api_util .GetUpdateMode (vpa ) == vpa_types .UpdateModeOff {
74+ // If update mode is "Off", we don't want to apply any recommendations,
75+ // but we still want to apply startup boost.
76+ for i := range containersResources {
77+ containersResources [i ].Requests = nil
78+ containersResources [i ].Limits = nil
79+ }
80+ annotationsPerContainer = vpa_api_util.ContainerToAnnotationsMap {}
6081 }
6182
6283 if annotationsPerContainer == nil {
@@ -65,9 +86,65 @@ func (c *resourcesUpdatesPatchCalculator) CalculatePatches(pod *core.Pod, vpa *v
6586
6687 updatesAnnotation := []string {}
6788 for i , containerResources := range containersResources {
89+ // Apply startup boost if configured
90+ if features .Enabled (features .CPUStartupBoost ) {
91+ policy := vpa_api_util .GetContainerResourcePolicy (pod .Spec .Containers [i ].Name , vpa .Spec .ResourcePolicy )
92+ if policy != nil && policy .Mode != nil && * policy .Mode == vpa_types .ContainerScalingModeOff {
93+ klog .V (4 ).InfoS ("Not applying startup boost for container" , "containerName" , pod .Spec .Containers [i ].Name , "reason" , "scaling mode is Off" )
94+ continue
95+ } else {
96+ startupBoostPolicy := getContainerStartupBoostPolicy (& pod .Spec .Containers [i ], vpa )
97+ if startupBoostPolicy != nil {
98+ originalRequest := pod .Spec .Containers [i ].Resources .Requests [core .ResourceCPU ]
99+ boostedRequest , err := calculateBoostedCPU (originalRequest , startupBoostPolicy )
100+ if err != nil {
101+ return nil , err
102+ }
103+
104+ if ! c .maxAllowedCPUBoost .IsZero () && boostedRequest .Cmp (c .maxAllowedCPUBoost ) > 0 {
105+ boostedRequest = & c .maxAllowedCPUBoost
106+ }
107+ if containerResources .Requests == nil {
108+ containerResources .Requests = core.ResourceList {}
109+ }
110+ controlledValues := vpa_api_util .GetContainerControlledValues (pod .Spec .Containers [i ].Name , vpa .Spec .ResourcePolicy )
111+ resourceList := core.ResourceList {core .ResourceCPU : * boostedRequest }
112+ if controlledValues == vpa_types .ContainerControlledValuesRequestsOnly {
113+ vpa_api_util .CapRecommendationToContainerLimit (resourceList , pod .Spec .Containers [i ].Resources .Limits )
114+ }
115+ containerResources .Requests [core .ResourceCPU ] = resourceList [core .ResourceCPU ]
116+
117+ if controlledValues == vpa_types .ContainerControlledValuesRequestsAndLimits {
118+ if containerResources .Limits == nil {
119+ containerResources .Limits = core.ResourceList {}
120+ }
121+ originalLimit := pod .Spec .Containers [i ].Resources .Limits [core .ResourceCPU ]
122+ if originalLimit .IsZero () {
123+ originalLimit = pod .Spec .Containers [i ].Resources .Requests [core .ResourceCPU ]
124+ }
125+ boostedLimit , err := calculateBoostedCPU (originalLimit , startupBoostPolicy )
126+ if err != nil {
127+ return nil , err
128+ }
129+ if ! c .maxAllowedCPUBoost .IsZero () && boostedLimit .Cmp (c .maxAllowedCPUBoost ) > 0 {
130+ boostedLimit = & c .maxAllowedCPUBoost
131+ }
132+ containerResources .Limits [core .ResourceCPU ] = * boostedLimit
133+ }
134+ originalResources , err := annotations .GetOriginalResourcesAnnotationValue (& pod .Spec .Containers [i ])
135+ if err != nil {
136+ return nil , err
137+ }
138+ result = append (result , GetAddAnnotationPatch (annotations .StartupCPUBoostAnnotation , originalResources ))
139+ }
140+ }
141+ }
142+
68143 newPatches , newUpdatesAnnotation := getContainerPatch (pod , i , annotationsPerContainer , containerResources )
69- result = append (result , newPatches ... )
70- updatesAnnotation = append (updatesAnnotation , newUpdatesAnnotation )
144+ if len (newPatches ) > 0 {
145+ result = append (result , newPatches ... )
146+ updatesAnnotation = append (updatesAnnotation , newUpdatesAnnotation )
147+ }
71148 }
72149
73150 if len (updatesAnnotation ) > 0 {
@@ -77,6 +154,49 @@ func (c *resourcesUpdatesPatchCalculator) CalculatePatches(pod *core.Pod, vpa *v
77154 return result , nil
78155}
79156
157+ func getContainerStartupBoostPolicy (container * core.Container , vpa * vpa_types.VerticalPodAutoscaler ) * vpa_types.StartupBoost {
158+ policy := vpa_api_util .GetContainerResourcePolicy (container .Name , vpa .Spec .ResourcePolicy )
159+ startupBoost := vpa .Spec .StartupBoost
160+ if policy != nil && policy .StartupBoost != nil {
161+ startupBoost = policy .StartupBoost
162+ }
163+ return startupBoost
164+ }
165+
166+ func calculateBoostedCPU (baseCPU resource.Quantity , startupBoost * vpa_types.StartupBoost ) (* resource.Quantity , error ) {
167+ if startupBoost == nil {
168+ return & baseCPU , nil
169+ }
170+
171+ boostType := startupBoost .CPU .Type
172+ if boostType == "" {
173+ boostType = vpa_types .FactorStartupBoostType
174+ }
175+
176+ switch boostType {
177+ case vpa_types .FactorStartupBoostType :
178+ if startupBoost .CPU .Factor == nil {
179+ return nil , fmt .Errorf ("startupBoost.CPU.Factor is required when Type is Factor or not specified" )
180+ }
181+ factor := * startupBoost .CPU .Factor
182+ if factor < 1 {
183+ return nil , fmt .Errorf ("boost factor must be >= 1" )
184+ }
185+ boostedCPU := baseCPU .MilliValue ()
186+ boostedCPU = int64 (float64 (boostedCPU ) * float64 (factor ))
187+ return resource .NewMilliQuantity (boostedCPU , resource .DecimalSI ), nil
188+ case vpa_types .QuantityStartupBoostType :
189+ if startupBoost .CPU .Quantity == nil {
190+ return nil , fmt .Errorf ("startupBoost.CPU.Quantity is required when Type is Quantity" )
191+ }
192+ quantity := * startupBoost .CPU .Quantity
193+ boostedCPU := baseCPU .MilliValue () + quantity .MilliValue ()
194+ return resource .NewMilliQuantity (boostedCPU , resource .DecimalSI ), nil
195+ default :
196+ return nil , fmt .Errorf ("unsupported startup boost type: %s" , startupBoost .CPU .Type )
197+ }
198+ }
199+
80200func getContainerPatch (pod * core.Pod , i int , annotationsPerContainer vpa_api_util.ContainerToAnnotationsMap , containerResources vpa_api_util.ContainerResources ) ([]resource_admission.PatchRecord , string ) {
81201 var patches []resource_admission.PatchRecord
82202 // Add empty resources object if missing.
0 commit comments