Skip to content

Commit 2a777c8

Browse files
committed
VPA: Add features gates; add InPlaceVerticalScaling feature gate
Signed-off-by: Max Cao <[email protected]>
1 parent acd5a15 commit 2a777c8

File tree

4 files changed

+87
-14
lines changed

4 files changed

+87
-14
lines changed
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/*
2+
Copyright 2025 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package features
18+
19+
import (
20+
"k8s.io/apimachinery/pkg/util/runtime"
21+
"k8s.io/component-base/featuregate"
22+
)
23+
24+
const (
25+
// Every feature gate should add method here following this template:
26+
//
27+
// // alpha: v1.X
28+
// MyFeature featuregate.Feature = "MyFeature".
29+
30+
// InPlaceVerticalScaling is a feature gate for enabling the InPlaceOrRecreate update mode to be used.
31+
// Requires InPlacePodVerticalScaling feature-gate to be enabled on the Kubernetes cluster itself.
32+
//
33+
// TODO(maxcao13): fill this in
34+
// alpha: v...
35+
// beta: v...
36+
// GA: v...
37+
InPlaceVerticalScaling featuregate.Feature = "InPlaceVerticalScaling"
38+
)
39+
40+
var MutableFeatureGate featuregate.MutableFeatureGate = featuregate.NewFeatureGate()
41+
42+
// defaultVPAFeatureGates consists of all known vertical-pod-autoscaler-specific feature keys.
43+
var defaultVPAFeatureGates = map[featuregate.Feature]featuregate.FeatureSpec{
44+
InPlaceVerticalScaling: {Default: false, PreRelease: featuregate.Alpha},
45+
}
46+
47+
func Enabled(f featuregate.Feature) bool {
48+
return MutableFeatureGate.Enabled(f)
49+
}
50+
51+
func init() {
52+
runtime.Must(MutableFeatureGate.Add(defaultVPAFeatureGates))
53+
}

vertical-pod-autoscaler/pkg/updater/eviction/pods_eviction_restriction.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import (
3636

3737
resource_updates "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/admission-controller/resource"
3838
"k8s.io/autoscaler/vertical-pod-autoscaler/pkg/admission-controller/resource/pod/patch"
39+
"k8s.io/autoscaler/vertical-pod-autoscaler/pkg/features"
3940

4041
vpa_types "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/apis/autoscaling.k8s.io/v1"
4142
)
@@ -433,6 +434,9 @@ func setUpInformer(kubeClient kube_client.Interface, kind controllerKind) (cache
433434

434435
// CanInPlaceUpdate performs the same checks
435436
func (e *podsEvictionRestrictionImpl) CanInPlaceUpdate(pod *apiv1.Pod) bool {
437+
if !features.Enabled(features.InPlaceVerticalScaling) {
438+
return false
439+
}
436440
cr, present := e.podToReplicaCreatorMap[GetPodID(pod)]
437441
if present {
438442
// If our QoS class is guaranteed, we can't change the resources without a restart

vertical-pod-autoscaler/pkg/updater/logic/updater.go

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ import (
4343
vpa_clientset "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/client/clientset/versioned"
4444
"k8s.io/autoscaler/vertical-pod-autoscaler/pkg/client/clientset/versioned/scheme"
4545
vpa_lister "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/client/listers/autoscaling.k8s.io/v1"
46+
"k8s.io/autoscaler/vertical-pod-autoscaler/pkg/features"
4647
"k8s.io/autoscaler/vertical-pod-autoscaler/pkg/target"
4748
controllerfetcher "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/target/controller_fetcher"
4849
"k8s.io/autoscaler/vertical-pod-autoscaler/pkg/updater/eviction"
@@ -235,28 +236,34 @@ func (u *updater) RunOnce(ctx context.Context) {
235236
vpaSize := len(livePods)
236237
controlledPodsCounter.Add(vpaSize, vpaSize)
237238
evictionLimiter := u.evictionFactory.NewPodsEvictionRestriction(livePods, vpa, u.patchCalculators)
238-
podsForUpdate := u.getPodsUpdateOrder(filterNonEvictablePods(livePods, evictionLimiter), vpa)
239+
podsForUpdate := u.getPodsUpdateOrder(filterNonUpdatablePods(livePods, evictionLimiter), vpa)
239240
evictablePodsCounter.Add(vpaSize, len(podsForUpdate))
240241

241242
withInPlaceUpdatable := false
242243
withInPlaceUpdated := false
243244
withEvictable := false
244245
withEvicted := false
245246

247+
logDisabledFeatureGate := false
248+
246249
for _, pod := range podsForUpdate {
247250
if vpa_api_util.GetUpdateMode(vpa) == vpa_types.UpdateModeInPlaceOrRecreate {
248-
withInPlaceUpdatable = true
249-
fallBackToEviction, err := u.AttemptInPlaceUpdate(ctx, vpa, pod, evictionLimiter)
250-
if err != nil {
251-
klog.V(0).InfoS("In-place update failed", "error", err, "pod", klog.KObj(pod))
252-
return
253-
}
254-
if fallBackToEviction {
255-
klog.V(4).InfoS("Falling back to eviction for pod", "pod", klog.KObj(pod))
251+
if features.Enabled(features.InPlaceVerticalScaling) {
252+
withInPlaceUpdatable = true
253+
fallBackToEviction, err := u.AttemptInPlaceUpdate(ctx, vpa, pod, evictionLimiter)
254+
if err != nil {
255+
klog.V(0).InfoS("In-place update failed", "error", err, "pod", klog.KObj(pod))
256+
return
257+
}
258+
if fallBackToEviction {
259+
klog.V(4).InfoS("Falling back to eviction for pod", "pod", klog.KObj(pod))
260+
} else {
261+
withInPlaceUpdated = true
262+
metrics_updater.AddInPlaceUpdatedPod(vpaSize)
263+
continue
264+
}
256265
} else {
257-
withInPlaceUpdated = true
258-
metrics_updater.AddInPlaceUpdatedPod(vpaSize)
259-
continue
266+
logDisabledFeatureGate = true
260267
}
261268
}
262269

@@ -291,6 +298,9 @@ func (u *updater) RunOnce(ctx context.Context) {
291298
if withEvicted {
292299
vpasWithEvictedPodsCounter.Add(vpaSize, 1)
293300
}
301+
if logDisabledFeatureGate {
302+
klog.InfoS("Warning: feature gate is not enabled for this updateMode", "featuregate", features.InPlaceVerticalScaling, "updateMode", vpa_types.UpdateModeInPlaceOrRecreate)
303+
}
294304
}
295305
timer.ObserveStep("EvictPods")
296306
}
@@ -323,10 +333,10 @@ func (u *updater) getPodsUpdateOrder(pods []*apiv1.Pod, vpa *vpa_types.VerticalP
323333
return priorityCalculator.GetSortedPods(u.evictionAdmission)
324334
}
325335

326-
func filterNonEvictablePods(pods []*apiv1.Pod, evictionRestriction eviction.PodsEvictionRestriction) []*apiv1.Pod {
336+
func filterNonUpdatablePods(pods []*apiv1.Pod, evictionRestriction eviction.PodsEvictionRestriction) []*apiv1.Pod {
327337
result := make([]*apiv1.Pod, 0)
328338
for _, pod := range pods {
329-
if evictionRestriction.CanEvict(pod) {
339+
if evictionRestriction.CanEvict(pod) || evictionRestriction.CanInPlaceUpdate(pod) {
330340
result = append(result, pod)
331341
}
332342
}
@@ -375,6 +385,9 @@ func newEventRecorder(kubeClient kube_client.Interface) record.EventRecorder {
375385
}
376386

377387
func (u *updater) AttemptInPlaceUpdate(ctx context.Context, vpa *vpa_types.VerticalPodAutoscaler, pod *apiv1.Pod, evictionLimiter eviction.PodsEvictionRestriction) (fallBackToEviction bool, err error) {
388+
if features.Enabled(features.InPlaceVerticalScaling) {
389+
return false, fmt.Errorf("can't in-place update pod, %s not enabled", features.InPlaceVerticalScaling)
390+
}
378391
klog.V(4).InfoS("Checking preconditions for attemping in-place update", "pod", klog.KObj(pod))
379392
if !evictionLimiter.CanInPlaceUpdate(pod) {
380393
if pod.Status.QOSClass == apiv1.PodQOSGuaranteed {

vertical-pod-autoscaler/pkg/updater/main.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import (
3838
"k8s.io/autoscaler/vertical-pod-autoscaler/common"
3939
"k8s.io/autoscaler/vertical-pod-autoscaler/pkg/admission-controller/resource/pod/patch"
4040
vpa_clientset "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/client/clientset/versioned"
41+
"k8s.io/autoscaler/vertical-pod-autoscaler/pkg/features"
4142
"k8s.io/autoscaler/vertical-pod-autoscaler/pkg/target"
4243
controllerfetcher "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/target/controller_fetcher"
4344
"k8s.io/autoscaler/vertical-pod-autoscaler/pkg/updater/inplace"
@@ -90,6 +91,8 @@ func main() {
9091
leaderElection := defaultLeaderElectionConfiguration()
9192
componentbaseoptions.BindLeaderElectionFlags(&leaderElection, pflag.CommandLine)
9293

94+
features.MutableFeatureGate.AddFlag(pflag.CommandLine)
95+
9396
kube_flag.InitFlags()
9497
klog.V(1).InfoS("Vertical Pod Autoscaler Updater", "version", common.VerticalPodAutoscalerVersion())
9598

0 commit comments

Comments
 (0)