@@ -21,10 +21,13 @@ import (
2121 "strings"
2222
2323 core "k8s.io/api/core/v1"
24+ "k8s.io/apimachinery/pkg/api/resource"
2425
2526 resource_admission "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/admission-controller/resource"
2627 "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/admission-controller/resource/pod/recommendation"
2728 vpa_types "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/apis/autoscaling.k8s.io/v1"
29+ "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/features"
30+ "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/utils/annotations"
2831 resourcehelpers "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/utils/resources"
2932 vpa_api_util "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/utils/vpa"
3033)
@@ -37,13 +40,15 @@ const (
3740
3841type resourcesUpdatesPatchCalculator struct {
3942 recommendationProvider recommendation.Provider
43+ maxAllowedCPUBoost resource.Quantity
4044}
4145
4246// NewResourceUpdatesCalculator returns a calculator for
4347// resource update patches.
44- func NewResourceUpdatesCalculator (recommendationProvider recommendation.Provider ) Calculator {
48+ func NewResourceUpdatesCalculator (recommendationProvider recommendation.Provider , maxAllowedCPUBoost resource. QuantityValue ) Calculator {
4549 return & resourcesUpdatesPatchCalculator {
4650 recommendationProvider : recommendationProvider ,
51+ maxAllowedCPUBoost : maxAllowedCPUBoost .Quantity ,
4752 }
4853}
4954
@@ -59,15 +64,43 @@ func (c *resourcesUpdatesPatchCalculator) CalculatePatches(pod *core.Pod, vpa *v
5964 return []resource_admission.PatchRecord {}, fmt .Errorf ("failed to calculate resource patch for pod %s/%s: %v" , pod .Namespace , pod .Name , err )
6065 }
6166
67+ if vpa_api_util .GetUpdateMode (vpa ) == vpa_types .UpdateModeOff {
68+ // If update mode is "Off", we don't want to apply any recommendations,
69+ // but we still want to apply startup boost.
70+ for i := range containersResources {
71+ containersResources [i ].Requests = nil
72+ containersResources [i ].Limits = nil
73+ }
74+ annotationsPerContainer = vpa_api_util.ContainerToAnnotationsMap {}
75+ }
76+
6277 if annotationsPerContainer == nil {
6378 annotationsPerContainer = vpa_api_util.ContainerToAnnotationsMap {}
6479 }
6580
6681 updatesAnnotation := []string {}
67- for i , containerResources := range containersResources {
68- newPatches , newUpdatesAnnotation := getContainerPatch (pod , i , annotationsPerContainer , containerResources )
69- result = append (result , newPatches ... )
70- updatesAnnotation = append (updatesAnnotation , newUpdatesAnnotation )
82+ cpuStartupBoostEnabled := features .Enabled (features .CPUStartupBoost )
83+ for i := range containersResources {
84+
85+ // Apply startup boost if configured
86+ if cpuStartupBoostEnabled {
87+ // Get the container resource policy to check for scaling mode.
88+ policy := vpa_api_util .GetContainerResourcePolicy (pod .Spec .Containers [i ].Name , vpa .Spec .ResourcePolicy )
89+ if policy != nil && policy .Mode != nil && * policy .Mode == vpa_types .ContainerScalingModeOff {
90+ continue
91+ }
92+ boostPatches , err := c .applyCPUStartupBoost (& pod .Spec .Containers [i ], vpa , & containersResources [i ])
93+ if err != nil {
94+ return nil , err
95+ }
96+ result = append (result , boostPatches ... )
97+ }
98+
99+ newPatches , newUpdatesAnnotation := getContainerPatch (pod , i , annotationsPerContainer , containersResources [i ])
100+ if len (newPatches ) > 0 {
101+ result = append (result , newPatches ... )
102+ updatesAnnotation = append (updatesAnnotation , newUpdatesAnnotation )
103+ }
71104 }
72105
73106 if len (updatesAnnotation ) > 0 {
@@ -108,3 +141,121 @@ func appendPatchesAndAnnotations(patches []resource_admission.PatchRecord, annot
108141 }
109142 return patches , annotations
110143}
144+
145+ func (c * resourcesUpdatesPatchCalculator ) applyCPUStartupBoost (container * core.Container , vpa * vpa_types.VerticalPodAutoscaler , containerResources * vpa_api_util.ContainerResources ) ([]resource_admission.PatchRecord , error ) {
146+ var patches []resource_admission.PatchRecord
147+
148+ startupBoostPolicy := getContainerStartupBoostPolicy (container , vpa )
149+ if startupBoostPolicy == nil {
150+ return nil , nil
151+ }
152+
153+ err := c .applyControlledCPUResources (container , vpa , containerResources , startupBoostPolicy )
154+ if err != nil {
155+ return nil , err
156+ }
157+
158+ originalResources , err := annotations .GetOriginalResourcesAnnotationValue (container )
159+ if err != nil {
160+ return nil , err
161+ }
162+ patches = append (patches , GetAddAnnotationPatch (annotations .StartupCPUBoostAnnotation , originalResources ))
163+
164+ return patches , nil
165+ }
166+
167+ func getContainerStartupBoostPolicy (container * core.Container , vpa * vpa_types.VerticalPodAutoscaler ) * vpa_types.StartupBoost {
168+ policy := vpa_api_util .GetContainerResourcePolicy (container .Name , vpa .Spec .ResourcePolicy )
169+ startupBoost := vpa .Spec .StartupBoost
170+ if policy != nil && policy .StartupBoost != nil {
171+ startupBoost = policy .StartupBoost
172+ }
173+ return startupBoost
174+ }
175+
176+ func (c * resourcesUpdatesPatchCalculator ) calculateBoostedCPUValue (baseCPU resource.Quantity , startupBoost * vpa_types.StartupBoost ) (* resource.Quantity , error ) {
177+ boostType := startupBoost .CPU .Type
178+ if boostType == "" {
179+ boostType = vpa_types .FactorStartupBoostType
180+ }
181+
182+ switch boostType {
183+ case vpa_types .FactorStartupBoostType :
184+ if startupBoost .CPU .Factor == nil {
185+ return nil , fmt .Errorf ("startupBoost.CPU.Factor is required when Type is Factor or not specified" )
186+ }
187+ factor := * startupBoost .CPU .Factor
188+ if factor < 1 {
189+ return nil , fmt .Errorf ("boost factor must be >= 1" )
190+ }
191+ boostedCPUMilli := baseCPU .MilliValue ()
192+ boostedCPUMilli = int64 (float64 (boostedCPUMilli ) * float64 (factor ))
193+ return resource .NewMilliQuantity (boostedCPUMilli , resource .DecimalSI ), nil
194+ case vpa_types .QuantityStartupBoostType :
195+ if startupBoost .CPU .Quantity == nil {
196+ return nil , fmt .Errorf ("startupBoost.CPU.Quantity is required when Type is Quantity" )
197+ }
198+ quantity := * startupBoost .CPU .Quantity
199+ boostedCPUMilli := baseCPU .MilliValue () + quantity .MilliValue ()
200+ return resource .NewMilliQuantity (boostedCPUMilli , resource .DecimalSI ), nil
201+ default :
202+ return nil , fmt .Errorf ("unsupported startup boost type: %s" , startupBoost .CPU .Type )
203+ }
204+ }
205+
206+ func (c * resourcesUpdatesPatchCalculator ) calculateBoostedCPU (recommendedCPU , originalCPU resource.Quantity , startupBoost * vpa_types.StartupBoost ) (* resource.Quantity , error ) {
207+ baseCPU := recommendedCPU
208+ if baseCPU .IsZero () {
209+ baseCPU = originalCPU
210+ }
211+
212+ if startupBoost == nil {
213+ return & baseCPU , nil
214+ }
215+
216+ boostedCPU , err := c .calculateBoostedCPUValue (baseCPU , startupBoost )
217+ if err != nil {
218+ return nil , err
219+ }
220+
221+ if ! c .maxAllowedCPUBoost .IsZero () && boostedCPU .Cmp (c .maxAllowedCPUBoost ) > 0 {
222+ return & c .maxAllowedCPUBoost , nil
223+ }
224+ return boostedCPU , nil
225+ }
226+
227+ func (c * resourcesUpdatesPatchCalculator ) applyControlledCPUResources (container * core.Container , vpa * vpa_types.VerticalPodAutoscaler , containerResources * vpa_api_util.ContainerResources , startupBoostPolicy * vpa_types.StartupBoost ) error {
228+ controlledValues := vpa_api_util .GetContainerControlledValues (container .Name , vpa .Spec .ResourcePolicy )
229+
230+ recommendedRequest := containerResources .Requests [core .ResourceCPU ]
231+ originalRequest := container .Resources .Requests [core .ResourceCPU ]
232+ boostedRequest , err := c .calculateBoostedCPU (recommendedRequest , originalRequest , startupBoostPolicy )
233+ if err != nil {
234+ return err
235+ }
236+
237+ if containerResources .Requests == nil {
238+ containerResources .Requests = core.ResourceList {}
239+ }
240+ containerResources .Requests [core .ResourceCPU ] = * boostedRequest
241+
242+ switch controlledValues {
243+ case vpa_types .ContainerControlledValuesRequestsOnly :
244+ vpa_api_util .CapRecommendationToContainerLimit (containerResources .Requests , container .Resources .Limits )
245+ case vpa_types .ContainerControlledValuesRequestsAndLimits :
246+ if containerResources .Limits == nil {
247+ containerResources .Limits = core.ResourceList {}
248+ }
249+ originalLimit := container .Resources .Limits [core .ResourceCPU ]
250+ if originalLimit .IsZero () {
251+ originalLimit = container .Resources .Requests [core .ResourceCPU ]
252+ }
253+ recommendedLimit := containerResources .Limits [core .ResourceCPU ]
254+ boostedLimit , err := c .calculateBoostedCPU (recommendedLimit , originalLimit , startupBoostPolicy )
255+ if err != nil {
256+ return err
257+ }
258+ containerResources .Limits [core .ResourceCPU ] = * boostedLimit
259+ }
260+ return nil
261+ }
0 commit comments