@@ -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,19 @@ 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 string ) Calculator {
49
+ var maxAllowedCPUBoostQuantity resource.Quantity
50
+ if maxAllowedCPUBoost != "" {
51
+ maxAllowedCPUBoostQuantity = resource .MustParse (maxAllowedCPUBoost )
52
+ }
45
53
return & resourcesUpdatesPatchCalculator {
46
54
recommendationProvider : recommendationProvider ,
55
+ maxAllowedCPUBoost : maxAllowedCPUBoostQuantity ,
47
56
}
48
57
}
49
58
@@ -59,15 +68,42 @@ func (c *resourcesUpdatesPatchCalculator) CalculatePatches(pod *core.Pod, vpa *v
59
68
return []resource_admission.PatchRecord {}, fmt .Errorf ("failed to calculate resource patch for pod %s/%s: %v" , pod .Namespace , pod .Name , err )
60
69
}
61
70
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
+
62
81
if annotationsPerContainer == nil {
63
82
annotationsPerContainer = vpa_api_util.ContainerToAnnotationsMap {}
64
83
}
65
84
66
85
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
+ }
71
107
}
72
108
73
109
if len (updatesAnnotation ) > 0 {
@@ -77,7 +113,7 @@ func (c *resourcesUpdatesPatchCalculator) CalculatePatches(pod *core.Pod, vpa *v
77
113
return result , nil
78
114
}
79
115
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 ) {
81
117
var patches []resource_admission.PatchRecord
82
118
// Add empty resources object if missing.
83
119
requests , limits := resourcehelpers .ContainerRequestsAndLimits (pod .Spec .Containers [i ].Name , pod )
@@ -108,3 +144,114 @@ func appendPatchesAndAnnotations(patches []resource_admission.PatchRecord, annot
108
144
}
109
145
return patches , annotations
110
146
}
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