@@ -21,10 +21,14 @@ import (
2121 "strings"
2222
2323 core "k8s.io/api/core/v1"
24+ "k8s.io/apimachinery/pkg/api/resource"
25+ "k8s.io/klog/v2"
2426
2527 resource_admission "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/admission-controller/resource"
2628 "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/admission-controller/resource/pod/recommendation"
2729 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"
2832 resourcehelpers "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/utils/resources"
2933 vpa_api_util "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/utils/vpa"
3034)
@@ -37,13 +41,19 @@ const (
3741
3842type resourcesUpdatesPatchCalculator struct {
3943 recommendationProvider recommendation.Provider
44+ maxAllowedCpu resource.Quantity
4045}
4146
4247// NewResourceUpdatesCalculator returns a calculator for
4348// 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+ }
4554 return & resourcesUpdatesPatchCalculator {
4655 recommendationProvider : recommendationProvider ,
56+ maxAllowedCpu : maxAllowedCpuQuantity ,
4757 }
4858}
4959
@@ -52,11 +62,22 @@ func (*resourcesUpdatesPatchCalculator) PatchResourceTarget() PatchResourceTarge
5262}
5363
5464func (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 )
5566 result := []resource_admission.PatchRecord {}
5667
5768 containersResources , annotationsPerContainer , err := c .recommendationProvider .GetContainersResourcesForPod (pod , vpa )
5869 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 {}
6081 }
6182
6283 if annotationsPerContainer == nil {
@@ -65,9 +86,44 @@ func (c *resourcesUpdatesPatchCalculator) CalculatePatches(pod *core.Pod, vpa *v
6586
6687 updatesAnnotation := []string {}
6788 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+
68122 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+ }
71127 }
72128
73129 if len (updatesAnnotation ) > 0 {
@@ -77,6 +133,48 @@ func (c *resourcesUpdatesPatchCalculator) CalculatePatches(pod *core.Pod, vpa *v
77133 return result , nil
78134}
79135
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+
80178func getContainerPatch (pod * core.Pod , i int , annotationsPerContainer vpa_api_util.ContainerToAnnotationsMap , containerResources vpa_api_util.ContainerResources ) ([]resource_admission.PatchRecord , string ) {
81179 var patches []resource_admission.PatchRecord
82180 // Add empty resources object if missing.
0 commit comments