@@ -21,10 +21,14 @@ import (
21
21
"strings"
22
22
23
23
core "k8s.io/api/core/v1"
24
+ "k8s.io/apimachinery/pkg/api/resource"
25
+ "k8s.io/klog/v2"
24
26
25
27
resource_admission "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/admission-controller/resource"
26
28
"k8s.io/autoscaler/vertical-pod-autoscaler/pkg/admission-controller/resource/pod/recommendation"
27
29
vpa_types "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/apis/autoscaling.k8s.io/v1"
30
+ "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/features"
31
+ "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/utils/annotations"
28
32
resourcehelpers "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/utils/resources"
29
33
vpa_api_util "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/utils/vpa"
30
34
)
@@ -37,13 +41,19 @@ const (
37
41
38
42
type resourcesUpdatesPatchCalculator struct {
39
43
recommendationProvider recommendation.Provider
44
+ maxAllowedCPUBoost resource.Quantity
40
45
}
41
46
42
47
// NewResourceUpdatesCalculator returns a calculator for
43
48
// resource update patches.
44
- func NewResourceUpdatesCalculator (recommendationProvider recommendation.Provider ) Calculator {
49
+ func NewResourceUpdatesCalculator (recommendationProvider recommendation.Provider , maxAllowedCPUBoost string ) Calculator {
50
+ var maxAllowedCPUBoostQuantity resource.Quantity
51
+ if maxAllowedCPUBoost != "" {
52
+ maxAllowedCPUBoostQuantity = resource .MustParse (maxAllowedCPUBoost )
53
+ }
45
54
return & resourcesUpdatesPatchCalculator {
46
55
recommendationProvider : recommendationProvider ,
56
+ maxAllowedCPUBoost : maxAllowedCPUBoostQuantity ,
47
57
}
48
58
}
49
59
@@ -52,11 +62,22 @@ func (*resourcesUpdatesPatchCalculator) PatchResourceTarget() PatchResourceTarge
52
62
}
53
63
54
64
func (c * resourcesUpdatesPatchCalculator ) CalculatePatches (pod * core.Pod , vpa * vpa_types.VerticalPodAutoscaler ) ([]resource_admission.PatchRecord , error ) {
65
+ klog .Infof ("Calculating patches for pod %s/%s with VPA %s" , pod .Namespace , pod .Name , vpa .Name )
55
66
result := []resource_admission.PatchRecord {}
56
67
57
68
containersResources , annotationsPerContainer , err := c .recommendationProvider .GetContainersResourcesForPod (pod , vpa )
58
69
if err != nil {
59
- return []resource_admission.PatchRecord {}, fmt .Errorf ("failed to calculate resource patch for pod %s/%s: %v" , pod .Namespace , pod .Name , err )
70
+ return nil , fmt .Errorf ("failed to calculate resource patch for pod %s/%s: %v" , pod .Namespace , pod .Name , err )
71
+ }
72
+
73
+ if vpa_api_util .GetUpdateMode (vpa ) == vpa_types .UpdateModeOff {
74
+ // If update mode is "Off", we don't want to apply any recommendations,
75
+ // but we still want to apply startup boost.
76
+ for i := range containersResources {
77
+ containersResources [i ].Requests = nil
78
+ containersResources [i ].Limits = nil
79
+ }
80
+ annotationsPerContainer = vpa_api_util.ContainerToAnnotationsMap {}
60
81
}
61
82
62
83
if annotationsPerContainer == nil {
@@ -65,9 +86,65 @@ func (c *resourcesUpdatesPatchCalculator) CalculatePatches(pod *core.Pod, vpa *v
65
86
66
87
updatesAnnotation := []string {}
67
88
for i , containerResources := range containersResources {
89
+ // Apply startup boost if configured
90
+ if features .Enabled (features .CPUStartupBoost ) {
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
+ klog .V (4 ).InfoS ("Not applying startup boost for container" , "containerName" , pod .Spec .Containers [i ].Name , "reason" , "scaling mode is Off" )
94
+ continue
95
+ } else {
96
+ startupBoostPolicy := getContainerStartupBoostPolicy (& pod .Spec .Containers [i ], vpa )
97
+ if startupBoostPolicy != nil {
98
+ originalRequest := pod .Spec .Containers [i ].Resources .Requests [core .ResourceCPU ]
99
+ boostedRequest , err := calculateBoostedCPU (originalRequest , startupBoostPolicy )
100
+ if err != nil {
101
+ return nil , err
102
+ }
103
+
104
+ if ! c .maxAllowedCPUBoost .IsZero () && boostedRequest .Cmp (c .maxAllowedCPUBoost ) > 0 {
105
+ boostedRequest = & c .maxAllowedCPUBoost
106
+ }
107
+ if containerResources .Requests == nil {
108
+ containerResources .Requests = core.ResourceList {}
109
+ }
110
+ controlledValues := vpa_api_util .GetContainerControlledValues (pod .Spec .Containers [i ].Name , vpa .Spec .ResourcePolicy )
111
+ resourceList := core.ResourceList {core .ResourceCPU : * boostedRequest }
112
+ if controlledValues == vpa_types .ContainerControlledValuesRequestsOnly {
113
+ vpa_api_util .CapRecommendationToContainerLimit (resourceList , pod .Spec .Containers [i ].Resources .Limits )
114
+ }
115
+ containerResources .Requests [core .ResourceCPU ] = resourceList [core .ResourceCPU ]
116
+
117
+ if controlledValues == vpa_types .ContainerControlledValuesRequestsAndLimits {
118
+ if containerResources .Limits == nil {
119
+ containerResources .Limits = core.ResourceList {}
120
+ }
121
+ originalLimit := pod .Spec .Containers [i ].Resources .Limits [core .ResourceCPU ]
122
+ if originalLimit .IsZero () {
123
+ originalLimit = pod .Spec .Containers [i ].Resources .Requests [core .ResourceCPU ]
124
+ }
125
+ boostedLimit , err := calculateBoostedCPU (originalLimit , startupBoostPolicy )
126
+ if err != nil {
127
+ return nil , err
128
+ }
129
+ if ! c .maxAllowedCPUBoost .IsZero () && boostedLimit .Cmp (c .maxAllowedCPUBoost ) > 0 {
130
+ boostedLimit = & c .maxAllowedCPUBoost
131
+ }
132
+ containerResources .Limits [core .ResourceCPU ] = * boostedLimit
133
+ }
134
+ originalResources , err := annotations .GetOriginalResourcesAnnotationValue (& pod .Spec .Containers [i ])
135
+ if err != nil {
136
+ return nil , err
137
+ }
138
+ result = append (result , GetAddAnnotationPatch (annotations .StartupCPUBoostAnnotation , originalResources ))
139
+ }
140
+ }
141
+ }
142
+
68
143
newPatches , newUpdatesAnnotation := getContainerPatch (pod , i , annotationsPerContainer , containerResources )
69
- result = append (result , newPatches ... )
70
- updatesAnnotation = append (updatesAnnotation , newUpdatesAnnotation )
144
+ if len (newPatches ) > 0 {
145
+ result = append (result , newPatches ... )
146
+ updatesAnnotation = append (updatesAnnotation , newUpdatesAnnotation )
147
+ }
71
148
}
72
149
73
150
if len (updatesAnnotation ) > 0 {
@@ -77,6 +154,49 @@ func (c *resourcesUpdatesPatchCalculator) CalculatePatches(pod *core.Pod, vpa *v
77
154
return result , nil
78
155
}
79
156
157
+ func getContainerStartupBoostPolicy (container * core.Container , vpa * vpa_types.VerticalPodAutoscaler ) * vpa_types.StartupBoost {
158
+ policy := vpa_api_util .GetContainerResourcePolicy (container .Name , vpa .Spec .ResourcePolicy )
159
+ startupBoost := vpa .Spec .StartupBoost
160
+ if policy != nil && policy .StartupBoost != nil {
161
+ startupBoost = policy .StartupBoost
162
+ }
163
+ return startupBoost
164
+ }
165
+
166
+ func calculateBoostedCPU (baseCPU resource.Quantity , startupBoost * vpa_types.StartupBoost ) (* resource.Quantity , error ) {
167
+ if startupBoost == nil {
168
+ return & baseCPU , nil
169
+ }
170
+
171
+ boostType := startupBoost .CPU .Type
172
+ if boostType == "" {
173
+ boostType = vpa_types .FactorStartupBoostType
174
+ }
175
+
176
+ switch boostType {
177
+ case vpa_types .FactorStartupBoostType :
178
+ if startupBoost .CPU .Factor == nil {
179
+ return nil , fmt .Errorf ("startupBoost.CPU.Factor is required when Type is Factor or not specified" )
180
+ }
181
+ factor := * startupBoost .CPU .Factor
182
+ if factor < 1 {
183
+ return nil , fmt .Errorf ("boost factor must be >= 1" )
184
+ }
185
+ boostedCPU := baseCPU .MilliValue ()
186
+ boostedCPU = int64 (float64 (boostedCPU ) * float64 (factor ))
187
+ return resource .NewMilliQuantity (boostedCPU , resource .DecimalSI ), nil
188
+ case vpa_types .QuantityStartupBoostType :
189
+ if startupBoost .CPU .Quantity == nil {
190
+ return nil , fmt .Errorf ("startupBoost.CPU.Quantity is required when Type is Quantity" )
191
+ }
192
+ quantity := * startupBoost .CPU .Quantity
193
+ boostedCPU := baseCPU .MilliValue () + quantity .MilliValue ()
194
+ return resource .NewMilliQuantity (boostedCPU , resource .DecimalSI ), nil
195
+ default :
196
+ return nil , fmt .Errorf ("unsupported startup boost type: %s" , startupBoost .CPU .Type )
197
+ }
198
+ }
199
+
80
200
func getContainerPatch (pod * core.Pod , i int , annotationsPerContainer vpa_api_util.ContainerToAnnotationsMap , containerResources vpa_api_util.ContainerResources ) ([]resource_admission.PatchRecord , string ) {
81
201
var patches []resource_admission.PatchRecord
82
202
// Add empty resources object if missing.
0 commit comments