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