@@ -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
+ maxAllowedCpu 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 , maxAllowedCpu string ) Calculator {
50
+ var maxAllowedCpuQuantity resource.Quantity
51
+ if maxAllowedCpu != "" {
52
+ maxAllowedCpuQuantity = resource .MustParse (maxAllowedCpu )
53
+ }
45
54
return & resourcesUpdatesPatchCalculator {
46
55
recommendationProvider : recommendationProvider ,
56
+ maxAllowedCpu : maxAllowedCpuQuantity ,
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,44 @@ 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 ).Infof ("Not applying startup boost for container %s since its scaling mode is Off" , pod .Spec .Containers [i ].Name )
94
+ continue
95
+ } else {
96
+ boost , err := getStartupBoost (& pod .Spec .Containers [i ], vpa )
97
+ if err != nil {
98
+ return nil , err
99
+ }
100
+ if boost != nil {
101
+ if ! c .maxAllowedCpu .IsZero () && boost .Cmp (c .maxAllowedCpu ) > 0 {
102
+ cappedBoost := c .maxAllowedCpu
103
+ boost = & cappedBoost
104
+ }
105
+ if containerResources .Requests == nil {
106
+ containerResources .Requests = core.ResourceList {}
107
+ }
108
+ containerResources .Requests [core .ResourceCPU ] = * boost
109
+ if containerResources .Limits == nil {
110
+ containerResources .Limits = core.ResourceList {}
111
+ }
112
+ containerResources .Limits [core .ResourceCPU ] = * boost
113
+ originalResources , err := annotations .GetOriginalResourcesAnnotationValue (& pod .Spec .Containers [i ])
114
+ if err != nil {
115
+ return nil , err
116
+ }
117
+ result = append (result , GetAddAnnotationPatch (annotations .StartupCPUBoostAnnotation , originalResources ))
118
+ }
119
+ }
120
+ }
121
+
68
122
newPatches , newUpdatesAnnotation := getContainerPatch (pod , i , annotationsPerContainer , containerResources )
69
- result = append (result , newPatches ... )
70
- updatesAnnotation = append (updatesAnnotation , newUpdatesAnnotation )
123
+ if len (newPatches ) > 0 {
124
+ result = append (result , newPatches ... )
125
+ updatesAnnotation = append (updatesAnnotation , newUpdatesAnnotation )
126
+ }
71
127
}
72
128
73
129
if len (updatesAnnotation ) > 0 {
@@ -77,6 +133,48 @@ func (c *resourcesUpdatesPatchCalculator) CalculatePatches(pod *core.Pod, vpa *v
77
133
return result , nil
78
134
}
79
135
136
+ func getStartupBoost (container * core.Container , vpa * vpa_types.VerticalPodAutoscaler ) (* resource.Quantity , error ) {
137
+ policy := vpa_api_util .GetContainerResourcePolicy (container .Name , vpa .Spec .ResourcePolicy )
138
+ startupBoost := vpa .Spec .StartupBoost
139
+ if policy != nil && policy .StartupBoost != nil {
140
+ startupBoost = policy .StartupBoost
141
+ }
142
+ if startupBoost == nil {
143
+ return nil , nil
144
+ }
145
+
146
+ cpuRequest := container .Resources .Requests [core .ResourceCPU ]
147
+ boostType := startupBoost .CPU .Type
148
+ if boostType == "" {
149
+ boostType = vpa_types .FactorStartupBoostType
150
+ }
151
+
152
+ switch boostType {
153
+ case vpa_types .FactorStartupBoostType :
154
+ if startupBoost .CPU .Factor == nil {
155
+ return nil , fmt .Errorf ("startupBoost.CPU.Factor is required when Type is Factor or not specified" )
156
+ }
157
+ factor := * startupBoost .CPU .Factor
158
+ if factor < 1 {
159
+ return nil , fmt .Errorf ("boost factor must be >= 1" )
160
+ }
161
+ boostedCPU := cpuRequest .MilliValue ()
162
+ boostedCPU = int64 (float64 (boostedCPU ) * float64 (factor ))
163
+ return resource .NewMilliQuantity (boostedCPU , resource .DecimalSI ), nil
164
+ case vpa_types .QuantityStartupBoostType :
165
+ if startupBoost .CPU .Quantity == nil {
166
+ return nil , fmt .Errorf ("startupBoost.CPU.Quantity is required when Type is Quantity" )
167
+ }
168
+ quantity := * startupBoost .CPU .Quantity
169
+ if quantity .Cmp (cpuRequest ) < 0 {
170
+ return nil , fmt .Errorf ("boost quantity %s is less than container's request %s" , quantity .String (), cpuRequest .String ())
171
+ }
172
+ return & quantity , nil
173
+ default :
174
+ return nil , fmt .Errorf ("unsupported startup boost type: %s" , startupBoost .CPU .Type )
175
+ }
176
+ }
177
+
80
178
func getContainerPatch (pod * core.Pod , i int , annotationsPerContainer vpa_api_util.ContainerToAnnotationsMap , containerResources vpa_api_util.ContainerResources ) ([]resource_admission.PatchRecord , string ) {
81
179
var patches []resource_admission.PatchRecord
82
180
// Add empty resources object if missing.
0 commit comments