@@ -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,19 @@ 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 string ) Calculator {
49+ var maxAllowedCPUBoostQuantity resource.Quantity
50+ if maxAllowedCPUBoost != "" {
51+ maxAllowedCPUBoostQuantity = resource .MustParse (maxAllowedCPUBoost )
52+ }
4553 return & resourcesUpdatesPatchCalculator {
4654 recommendationProvider : recommendationProvider ,
55+ maxAllowedCPUBoost : maxAllowedCPUBoostQuantity ,
4756 }
4857}
4958
@@ -59,15 +68,42 @@ func (c *resourcesUpdatesPatchCalculator) CalculatePatches(pod *core.Pod, vpa *v
5968 return []resource_admission.PatchRecord {}, fmt .Errorf ("failed to calculate resource patch for pod %s/%s: %v" , pod .Namespace , pod .Name , err )
6069 }
6170
71+ if vpa_api_util .GetUpdateMode (vpa ) == vpa_types .UpdateModeOff {
72+ // If update mode is "Off", we don't want to apply any recommendations,
73+ // but we still want to apply startup boost.
74+ for i := range containersResources {
75+ containersResources [i ].Requests = nil
76+ containersResources [i ].Limits = nil
77+ }
78+ annotationsPerContainer = vpa_api_util.ContainerToAnnotationsMap {}
79+ }
80+
6281 if annotationsPerContainer == nil {
6382 annotationsPerContainer = vpa_api_util.ContainerToAnnotationsMap {}
6483 }
6584
6685 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 )
86+ for i := range containersResources {
87+
88+ // Apply startup boost if configured
89+ if features .Enabled (features .CPUStartupBoost ) {
90+ // Get the container resource policy to check for scaling mode.
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+ continue
94+ }
95+ boostPatches , err := c .applyCPUStartupBoost (i , & pod .Spec .Containers [i ], vpa , & containersResources [i ])
96+ if err != nil {
97+ return nil , err
98+ }
99+ result = append (result , boostPatches ... )
100+ }
101+
102+ newPatches , newUpdatesAnnotation := getContainerPatch (pod , i , annotationsPerContainer , & containersResources [i ])
103+ if len (newPatches ) > 0 {
104+ result = append (result , newPatches ... )
105+ updatesAnnotation = append (updatesAnnotation , newUpdatesAnnotation )
106+ }
71107 }
72108
73109 if len (updatesAnnotation ) > 0 {
@@ -77,7 +113,7 @@ func (c *resourcesUpdatesPatchCalculator) CalculatePatches(pod *core.Pod, vpa *v
77113 return result , nil
78114}
79115
80- func getContainerPatch (pod * core.Pod , i int , annotationsPerContainer vpa_api_util.ContainerToAnnotationsMap , containerResources vpa_api_util.ContainerResources ) ([]resource_admission.PatchRecord , string ) {
116+ func getContainerPatch (pod * core.Pod , i int , annotationsPerContainer vpa_api_util.ContainerToAnnotationsMap , containerResources * vpa_api_util.ContainerResources ) ([]resource_admission.PatchRecord , string ) {
81117 var patches []resource_admission.PatchRecord
82118 // Add empty resources object if missing.
83119 requests , limits := resourcehelpers .ContainerRequestsAndLimits (pod .Spec .Containers [i ].Name , pod )
@@ -108,3 +144,114 @@ func appendPatchesAndAnnotations(patches []resource_admission.PatchRecord, annot
108144 }
109145 return patches , annotations
110146}
147+
148+ func (c * resourcesUpdatesPatchCalculator ) applyCPUStartupBoost (containerIndex int , container * core.Container , vpa * vpa_types.VerticalPodAutoscaler , containerResources * vpa_api_util.ContainerResources ) ([]resource_admission.PatchRecord , error ) {
149+ var patches []resource_admission.PatchRecord
150+
151+ startupBoostPolicy := getContainerStartupBoostPolicy (container , vpa )
152+ if startupBoostPolicy == nil {
153+ return nil , nil
154+ }
155+
156+ originalRequest := container .Resources .Requests [core .ResourceCPU ]
157+ boostedRequest , err := calculateBoostedCPU (originalRequest , startupBoostPolicy )
158+ if err != nil {
159+ return nil , err
160+ }
161+
162+ if ! c .maxAllowedCPUBoost .IsZero () && boostedRequest .Cmp (c .maxAllowedCPUBoost ) > 0 {
163+ boostedRequest = & c .maxAllowedCPUBoost
164+ }
165+
166+ controlledCPUResourcePatches , err := c .applyControlledCPUResources (containerIndex , container , vpa , containerResources , boostedRequest , startupBoostPolicy )
167+ if err != nil {
168+ return nil , err
169+ }
170+ patches = append (patches , controlledCPUResourcePatches ... )
171+
172+ originalResources , err := annotations .GetOriginalResourcesAnnotationValue (container )
173+ if err != nil {
174+ return nil , err
175+ }
176+ patches = append (patches , GetAddAnnotationPatch (annotations .StartupCPUBoostAnnotation , originalResources ))
177+
178+ return patches , nil
179+ }
180+
181+ func getContainerStartupBoostPolicy (container * core.Container , vpa * vpa_types.VerticalPodAutoscaler ) * vpa_types.StartupBoost {
182+ policy := vpa_api_util .GetContainerResourcePolicy (container .Name , vpa .Spec .ResourcePolicy )
183+ startupBoost := vpa .Spec .StartupBoost
184+ if policy != nil && policy .StartupBoost != nil {
185+ startupBoost = policy .StartupBoost
186+ }
187+ return startupBoost
188+ }
189+
190+ func calculateBoostedCPU (baseCPU resource.Quantity , startupBoost * vpa_types.StartupBoost ) (* resource.Quantity , error ) {
191+ if startupBoost == nil {
192+ return & baseCPU , nil
193+ }
194+
195+ boostType := startupBoost .CPU .Type
196+ if boostType == "" {
197+ boostType = vpa_types .FactorStartupBoostType
198+ }
199+
200+ switch boostType {
201+ case vpa_types .FactorStartupBoostType :
202+ if startupBoost .CPU .Factor == nil {
203+ return nil , fmt .Errorf ("startupBoost.CPU.Factor is required when Type is Factor or not specified" )
204+ }
205+ factor := * startupBoost .CPU .Factor
206+ if factor < 1 {
207+ return nil , fmt .Errorf ("boost factor must be >= 1" )
208+ }
209+ boostedCPU := baseCPU .MilliValue ()
210+ boostedCPU = int64 (float64 (boostedCPU ) * float64 (factor ))
211+ return resource .NewMilliQuantity (boostedCPU , resource .DecimalSI ), nil
212+ case vpa_types .QuantityStartupBoostType :
213+ if startupBoost .CPU .Quantity == nil {
214+ return nil , fmt .Errorf ("startupBoost.CPU.Quantity is required when Type is Quantity" )
215+ }
216+ quantity := * startupBoost .CPU .Quantity
217+ boostedCPU := baseCPU .MilliValue () + quantity .MilliValue ()
218+ return resource .NewMilliQuantity (boostedCPU , resource .DecimalSI ), nil
219+ default :
220+ return nil , fmt .Errorf ("unsupported startup boost type: %s" , startupBoost .CPU .Type )
221+ }
222+ }
223+
224+ func (c * resourcesUpdatesPatchCalculator ) applyControlledCPUResources (containerIndex int , container * core.Container , vpa * vpa_types.VerticalPodAutoscaler , containerResources * vpa_api_util.ContainerResources , boostedRequest * resource.Quantity , startupBoostPolicy * vpa_types.StartupBoost ) ([]resource_admission.PatchRecord , error ) {
225+ controlledValues := vpa_api_util .GetContainerControlledValues (container .Name , vpa .Spec .ResourcePolicy )
226+
227+ // Apply CPU Requests
228+ if containerResources .Requests == nil {
229+ containerResources .Requests = core.ResourceList {}
230+ }
231+ resourceList := core.ResourceList {core .ResourceCPU : * boostedRequest }
232+ if controlledValues == vpa_types .ContainerControlledValuesRequestsOnly {
233+ vpa_api_util .CapRecommendationToContainerLimit (resourceList , container .Resources .Limits )
234+ }
235+ containerResources .Requests [core .ResourceCPU ] = resourceList [core .ResourceCPU ]
236+
237+ // Apply CPU Limits
238+ if controlledValues == vpa_types .ContainerControlledValuesRequestsAndLimits {
239+ if containerResources .Limits == nil {
240+ containerResources .Limits = core.ResourceList {}
241+ }
242+ originalLimit := container .Resources .Limits [core .ResourceCPU ]
243+ if originalLimit .IsZero () {
244+ originalLimit = container .Resources .Requests [core .ResourceCPU ]
245+ }
246+ boostedLimit , err := calculateBoostedCPU (originalLimit , startupBoostPolicy )
247+ if err != nil {
248+ return nil , err
249+ }
250+ if ! c .maxAllowedCPUBoost .IsZero () && boostedLimit .Cmp (c .maxAllowedCPUBoost ) > 0 {
251+ boostedLimit = & c .maxAllowedCPUBoost
252+ }
253+ containerResources .Limits [core .ResourceCPU ] = * boostedLimit
254+ }
255+
256+ return nil , nil
257+ }
0 commit comments