@@ -21,10 +21,13 @@ import (
21
21
"strings"
22
22
23
23
core "k8s.io/api/core/v1"
24
+ "k8s.io/apimachinery/pkg/api/resource"
24
25
25
26
resource_admission "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/admission-controller/resource"
26
27
"k8s.io/autoscaler/vertical-pod-autoscaler/pkg/admission-controller/resource/pod/recommendation"
27
28
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"
28
31
resourcehelpers "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/utils/resources"
29
32
vpa_api_util "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/utils/vpa"
30
33
)
@@ -37,13 +40,15 @@ const (
37
40
38
41
type resourcesUpdatesPatchCalculator struct {
39
42
recommendationProvider recommendation.Provider
43
+ maxAllowedCPUBoost resource.Quantity
40
44
}
41
45
42
46
// NewResourceUpdatesCalculator returns a calculator for
43
47
// resource update patches.
44
- func NewResourceUpdatesCalculator (recommendationProvider recommendation.Provider ) Calculator {
48
+ func NewResourceUpdatesCalculator (recommendationProvider recommendation.Provider , maxAllowedCPUBoost resource. QuantityValue ) Calculator {
45
49
return & resourcesUpdatesPatchCalculator {
46
50
recommendationProvider : recommendationProvider ,
51
+ maxAllowedCPUBoost : maxAllowedCPUBoost .Quantity ,
47
52
}
48
53
}
49
54
@@ -59,15 +64,43 @@ func (c *resourcesUpdatesPatchCalculator) CalculatePatches(pod *core.Pod, vpa *v
59
64
return []resource_admission.PatchRecord {}, fmt .Errorf ("failed to calculate resource patch for pod %s/%s: %v" , pod .Namespace , pod .Name , err )
60
65
}
61
66
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
+
62
77
if annotationsPerContainer == nil {
63
78
annotationsPerContainer = vpa_api_util.ContainerToAnnotationsMap {}
64
79
}
65
80
66
81
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
+ }
71
104
}
72
105
73
106
if len (updatesAnnotation ) > 0 {
@@ -108,3 +141,121 @@ func appendPatchesAndAnnotations(patches []resource_admission.PatchRecord, annot
108
141
}
109
142
return patches , annotations
110
143
}
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