diff --git a/vertical-pod-autoscaler/pkg/admission-controller/main.go b/vertical-pod-autoscaler/pkg/admission-controller/main.go index f3b3c2a1751b..dd060c985635 100644 --- a/vertical-pod-autoscaler/pkg/admission-controller/main.go +++ b/vertical-pod-autoscaler/pkg/admission-controller/main.go @@ -24,6 +24,11 @@ import ( "time" apiv1 "k8s.io/api/core/v1" + "k8s.io/client-go/informers" + kube_client "k8s.io/client-go/kubernetes" + kube_flag "k8s.io/component-base/cli/flag" + "k8s.io/klog/v2" + "k8s.io/autoscaler/vertical-pod-autoscaler/common" "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/admission-controller/logic" "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/admission-controller/resource/pod" @@ -32,20 +37,20 @@ import ( "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/admission-controller/resource/vpa" vpa_clientset "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/client/clientset/versioned" "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/target" + controllerfetcher "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/target/controller_fetcher" "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/utils/limitrange" "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/utils/metrics" metrics_admission "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/utils/metrics/admission" "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/utils/status" vpa_api_util "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/utils/vpa" - "k8s.io/client-go/informers" - kube_client "k8s.io/client-go/kubernetes" - kube_flag "k8s.io/component-base/cli/flag" - "k8s.io/klog/v2" ) const ( - defaultResyncPeriod = 10 * time.Minute - statusUpdateInterval = 10 * time.Second + defaultResyncPeriod = 10 * time.Minute + statusUpdateInterval = 10 * time.Second + scaleCacheEntryLifetime time.Duration = time.Hour + scaleCacheEntryFreshnessTime time.Duration = 10 * time.Minute + scaleCacheEntryJitterFactor float64 = 1. ) var ( @@ -87,6 +92,7 @@ func main() { kubeClient := kube_client.NewForConfigOrDie(config) factory := informers.NewSharedInformerFactory(kubeClient, defaultResyncPeriod) targetSelectorFetcher := target.NewVpaTargetSelectorFetcher(config, kubeClient, factory) + controllerFetcher := controllerfetcher.NewControllerFetcher(config, kubeClient, factory, scaleCacheEntryFreshnessTime, scaleCacheEntryLifetime, scaleCacheEntryJitterFactor) podPreprocessor := pod.NewDefaultPreProcessor() vpaPreprocessor := vpa.NewDefaultPreProcessor() var limitRangeCalculator limitrange.LimitRangeCalculator @@ -96,7 +102,7 @@ func main() { limitRangeCalculator = limitrange.NewNoopLimitsCalculator() } recommendationProvider := recommendation.NewProvider(limitRangeCalculator, vpa_api_util.NewCappingRecommendationProcessor(limitRangeCalculator)) - vpaMatcher := vpa.NewMatcher(vpaLister, targetSelectorFetcher) + vpaMatcher := vpa.NewMatcher(vpaLister, targetSelectorFetcher, controllerFetcher) hostname, err := os.Hostname() if err != nil { diff --git a/vertical-pod-autoscaler/pkg/admission-controller/resource/vpa/matcher.go b/vertical-pod-autoscaler/pkg/admission-controller/resource/vpa/matcher.go index cdf4595b91ae..8c74638333d6 100644 --- a/vertical-pod-autoscaler/pkg/admission-controller/resource/vpa/matcher.go +++ b/vertical-pod-autoscaler/pkg/admission-controller/resource/vpa/matcher.go @@ -19,11 +19,13 @@ package vpa import ( core "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/labels" + "k8s.io/klog/v2" + vpa_types "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/apis/autoscaling.k8s.io/v1" vpa_lister "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/client/listers/autoscaling.k8s.io/v1" "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/target" + controllerfetcher "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/target/controller_fetcher" vpa_api_util "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/utils/vpa" - "k8s.io/klog/v2" ) // Matcher is capable of returning a single matching VPA object @@ -33,15 +35,18 @@ type Matcher interface { } type matcher struct { - vpaLister vpa_lister.VerticalPodAutoscalerLister - selectorFetcher target.VpaTargetSelectorFetcher + vpaLister vpa_lister.VerticalPodAutoscalerLister + selectorFetcher target.VpaTargetSelectorFetcher + controllerFetcher controllerfetcher.ControllerFetcher } // NewMatcher returns a new VPA matcher. func NewMatcher(vpaLister vpa_lister.VerticalPodAutoscalerLister, - selectorFetcher target.VpaTargetSelectorFetcher) Matcher { + selectorFetcher target.VpaTargetSelectorFetcher, + controllerFetcher controllerfetcher.ControllerFetcher) Matcher { return &matcher{vpaLister: vpaLister, - selectorFetcher: selectorFetcher} + selectorFetcher: selectorFetcher, + controllerFetcher: controllerFetcher} } func (m *matcher) GetMatchingVPA(pod *core.Pod) *vpa_types.VerticalPodAutoscaler { @@ -66,7 +71,7 @@ func (m *matcher) GetMatchingVPA(pod *core.Pod) *vpa_types.VerticalPodAutoscaler }) } klog.V(2).Infof("Let's choose from %d configs for pod %s/%s", len(onConfigs), pod.Namespace, pod.Name) - result := vpa_api_util.GetControllingVPAForPod(pod, onConfigs) + result := vpa_api_util.GetControllingVPAForPod(pod, onConfigs, m.controllerFetcher) if result != nil { return result.Vpa } diff --git a/vertical-pod-autoscaler/pkg/admission-controller/resource/vpa/matcher_test.go b/vertical-pod-autoscaler/pkg/admission-controller/resource/vpa/matcher_test.go index 19631ca7c2c2..035f64ed2079 100644 --- a/vertical-pod-autoscaler/pkg/admission-controller/resource/vpa/matcher_test.go +++ b/vertical-pod-autoscaler/pkg/admission-controller/resource/vpa/matcher_test.go @@ -19,10 +19,14 @@ package vpa import ( "testing" + appsv1 "k8s.io/api/apps/v1" + v1 "k8s.io/api/autoscaling/v1" core "k8s.io/api/core/v1" meta "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" + vpa_types "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/apis/autoscaling.k8s.io/v1" + controllerfetcher "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/target/controller_fetcher" target_mock "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/target/mock" "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/utils/test" @@ -37,8 +41,24 @@ func parseLabelSelector(selector string) labels.Selector { } func TestGetMatchingVpa(t *testing.T) { - podBuilder := test.Pod().WithName("test-pod").WithLabels(map[string]string{"app": "test"}). + sts := appsv1.StatefulSet{ + TypeMeta: meta.TypeMeta{ + Kind: "StatefulSet", + APIVersion: "apps/v1", + }, + ObjectMeta: meta.ObjectMeta{ + Name: "sts", + Namespace: "default", + }, + } + targetRef := &v1.CrossVersionObjectReference{ + Kind: sts.Kind, + Name: sts.Name, + APIVersion: sts.APIVersion, + } + podBuilderWithoutCreator := test.Pod().WithName("test-pod").WithLabels(map[string]string{"app": "test"}). AddContainer(test.Container().WithName("i-am-container").Get()) + podBuilder := podBuilderWithoutCreator.WithCreator(&sts.ObjectMeta, &sts.TypeMeta) vpaBuilder := test.VerticalPodAutoscaler().WithContainer("i-am-container") testCases := []struct { name string @@ -52,16 +72,24 @@ func TestGetMatchingVpa(t *testing.T) { name: "matching selector", pod: podBuilder.Get(), vpas: []*vpa_types.VerticalPodAutoscaler{ - vpaBuilder.WithUpdateMode(vpa_types.UpdateModeAuto).WithName("auto-vpa").Get(), + vpaBuilder.WithUpdateMode(vpa_types.UpdateModeAuto).WithName("auto-vpa").WithTargetRef(targetRef).Get(), }, labelSelector: "app = test", expectedFound: true, expectedVpaName: "auto-vpa", + }, { + name: "matching selector but not match ownerRef (orphan pod)", + pod: podBuilderWithoutCreator.Get(), + vpas: []*vpa_types.VerticalPodAutoscaler{ + vpaBuilder.WithUpdateMode(vpa_types.UpdateModeAuto).WithName("auto-vpa").WithTargetRef(targetRef).Get(), + }, + labelSelector: "app = test", + expectedFound: false, }, { name: "not matching selector", pod: podBuilder.Get(), vpas: []*vpa_types.VerticalPodAutoscaler{ - vpaBuilder.WithUpdateMode(vpa_types.UpdateModeAuto).WithName("auto-vpa").Get(), + vpaBuilder.WithUpdateMode(vpa_types.UpdateModeAuto).WithName("auto-vpa").WithTargetRef(targetRef).Get(), }, labelSelector: "app = differentApp", expectedFound: false, @@ -69,7 +97,7 @@ func TestGetMatchingVpa(t *testing.T) { name: "off mode", pod: podBuilder.Get(), vpas: []*vpa_types.VerticalPodAutoscaler{ - vpaBuilder.WithUpdateMode(vpa_types.UpdateModeOff).WithName("off-vpa").Get(), + vpaBuilder.WithUpdateMode(vpa_types.UpdateModeOff).WithName("off-vpa").WithTargetRef(targetRef).Get(), }, labelSelector: "app = test", expectedFound: false, @@ -77,8 +105,8 @@ func TestGetMatchingVpa(t *testing.T) { name: "two vpas one in off mode", pod: podBuilder.Get(), vpas: []*vpa_types.VerticalPodAutoscaler{ - vpaBuilder.WithUpdateMode(vpa_types.UpdateModeOff).WithName("off-vpa").Get(), - vpaBuilder.WithUpdateMode(vpa_types.UpdateModeAuto).WithName("auto-vpa").Get(), + vpaBuilder.WithUpdateMode(vpa_types.UpdateModeOff).WithName("off-vpa").WithTargetRef(targetRef).Get(), + vpaBuilder.WithUpdateMode(vpa_types.UpdateModeAuto).WithName("auto-vpa").WithTargetRef(targetRef).Get(), }, labelSelector: "app = test", expectedFound: true, @@ -87,7 +115,7 @@ func TestGetMatchingVpa(t *testing.T) { name: "initial mode", pod: podBuilder.Get(), vpas: []*vpa_types.VerticalPodAutoscaler{ - vpaBuilder.WithUpdateMode(vpa_types.UpdateModeInitial).WithName("initial-vpa").Get(), + vpaBuilder.WithUpdateMode(vpa_types.UpdateModeInitial).WithName("initial-vpa").WithTargetRef(targetRef).Get(), }, labelSelector: "app = test", expectedFound: true, @@ -114,7 +142,11 @@ func TestGetMatchingVpa(t *testing.T) { vpaLister.On("VerticalPodAutoscalers", "default").Return(vpaNamespaceLister) mockSelectorFetcher.EXPECT().Fetch(gomock.Any()).AnyTimes().Return(parseLabelSelector(tc.labelSelector), nil) - matcher := NewMatcher(vpaLister, mockSelectorFetcher) + // This test is using a FakeControllerFetcher which returns the same ownerRef that is passed to it. + // In other words, it cannot go through the hierarchy of controllers like "ReplicaSet => Deployment" + // For this reason we are using "StatefulSet" as the ownerRef kind in the test, since it is a direct link. + // The hierarchy part is being test in the "TestControllerFetcher" test. + matcher := NewMatcher(vpaLister, mockSelectorFetcher, controllerfetcher.FakeControllerFetcher{}) vpa := matcher.GetMatchingVPA(tc.pod) if tc.expectedFound && assert.NotNil(t, vpa) { diff --git a/vertical-pod-autoscaler/pkg/recommender/input/cluster_feeder.go b/vertical-pod-autoscaler/pkg/recommender/input/cluster_feeder.go index 316ab1ec9f70..6da25fea5eb4 100644 --- a/vertical-pod-autoscaler/pkg/recommender/input/cluster_feeder.go +++ b/vertical-pod-autoscaler/pkg/recommender/input/cluster_feeder.go @@ -36,13 +36,13 @@ import ( vpa_types "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/apis/autoscaling.k8s.io/v1" vpa_api "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/client/clientset/versioned/typed/autoscaling.k8s.io/v1" vpa_lister "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/client/listers/autoscaling.k8s.io/v1" - controllerfetcher "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/recommender/input/controller_fetcher" "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/recommender/input/history" "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/recommender/input/metrics" "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/recommender/input/oom" "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/recommender/input/spec" "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/recommender/model" "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/target" + "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/target/controller_fetcher" metrics_recommender "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/utils/metrics/recommender" ) diff --git a/vertical-pod-autoscaler/pkg/recommender/input/cluster_feeder_test.go b/vertical-pod-autoscaler/pkg/recommender/input/cluster_feeder_test.go index 7a2f3fef10ba..af431d8b908c 100644 --- a/vertical-pod-autoscaler/pkg/recommender/input/cluster_feeder_test.go +++ b/vertical-pod-autoscaler/pkg/recommender/input/cluster_feeder_test.go @@ -27,11 +27,12 @@ import ( autoscalingv1 "k8s.io/api/autoscaling/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" + vpa_types "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/apis/autoscaling.k8s.io/v1" - controllerfetcher "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/recommender/input/controller_fetcher" "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/recommender/input/history" "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/recommender/input/spec" "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/recommender/model" + "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/target/controller_fetcher" target_mock "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/target/mock" "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/utils/test" ) diff --git a/vertical-pod-autoscaler/pkg/recommender/main.go b/vertical-pod-autoscaler/pkg/recommender/main.go index 056953de55ae..595f06162701 100644 --- a/vertical-pod-autoscaler/pkg/recommender/main.go +++ b/vertical-pod-autoscaler/pkg/recommender/main.go @@ -19,9 +19,10 @@ package main import ( "context" "flag" - resourceclient "k8s.io/metrics/pkg/client/clientset/versioned/typed/metrics/v1beta1" "time" + resourceclient "k8s.io/metrics/pkg/client/clientset/versioned/typed/metrics/v1beta1" + apiv1 "k8s.io/api/core/v1" "k8s.io/client-go/informers" kube_client "k8s.io/client-go/kubernetes" @@ -32,13 +33,13 @@ import ( vpa_clientset "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/client/clientset/versioned" "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/recommender/checkpoint" "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/recommender/input" - controllerfetcher "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/recommender/input/controller_fetcher" "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/recommender/input/history" input_metrics "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/recommender/input/metrics" "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/recommender/logic" "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/recommender/model" "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/recommender/routines" "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/target" + "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/target/controller_fetcher" "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/utils/metrics" metrics_quality "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/utils/metrics/quality" metrics_recommender "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/utils/metrics/recommender" diff --git a/vertical-pod-autoscaler/pkg/recommender/model/cluster.go b/vertical-pod-autoscaler/pkg/recommender/model/cluster.go index e2abc4f010b6..2e6f1bd2017a 100644 --- a/vertical-pod-autoscaler/pkg/recommender/model/cluster.go +++ b/vertical-pod-autoscaler/pkg/recommender/model/cluster.go @@ -22,10 +22,11 @@ import ( apiv1 "k8s.io/api/core/v1" labels "k8s.io/apimachinery/pkg/labels" + "k8s.io/klog/v2" + vpa_types "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/apis/autoscaling.k8s.io/v1" - controllerfetcher "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/recommender/input/controller_fetcher" + "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/target/controller_fetcher" vpa_utils "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/utils/vpa" - "k8s.io/klog/v2" ) const ( diff --git a/vertical-pod-autoscaler/pkg/recommender/model/cluster_test.go b/vertical-pod-autoscaler/pkg/recommender/model/cluster_test.go index 067bc3953e08..a37658dbd810 100644 --- a/vertical-pod-autoscaler/pkg/recommender/model/cluster_test.go +++ b/vertical-pod-autoscaler/pkg/recommender/model/cluster_test.go @@ -26,10 +26,11 @@ import ( apiv1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" + "k8s.io/klog/v2" + vpa_types "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/apis/autoscaling.k8s.io/v1" - controllerfetcher "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/recommender/input/controller_fetcher" + "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/target/controller_fetcher" "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/utils/test" - "k8s.io/klog/v2" ) var ( diff --git a/vertical-pod-autoscaler/pkg/recommender/routines/recommender.go b/vertical-pod-autoscaler/pkg/recommender/routines/recommender.go index 33d3819a725a..5d7ec8ade709 100644 --- a/vertical-pod-autoscaler/pkg/recommender/routines/recommender.go +++ b/vertical-pod-autoscaler/pkg/recommender/routines/recommender.go @@ -26,9 +26,9 @@ import ( vpa_api "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/client/clientset/versioned/typed/autoscaling.k8s.io/v1" "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/recommender/checkpoint" "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/recommender/input" - controllerfetcher "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/recommender/input/controller_fetcher" "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/recommender/logic" "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/recommender/model" + "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/target/controller_fetcher" metrics_recommender "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/utils/metrics/recommender" vpa_utils "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/utils/vpa" ) diff --git a/vertical-pod-autoscaler/pkg/recommender/input/controller_fetcher/controller_cache_storage.go b/vertical-pod-autoscaler/pkg/target/controller_fetcher/controller_cache_storage.go similarity index 100% rename from vertical-pod-autoscaler/pkg/recommender/input/controller_fetcher/controller_cache_storage.go rename to vertical-pod-autoscaler/pkg/target/controller_fetcher/controller_cache_storage.go diff --git a/vertical-pod-autoscaler/pkg/recommender/input/controller_fetcher/controller_cache_storage_test.go b/vertical-pod-autoscaler/pkg/target/controller_fetcher/controller_cache_storage_test.go similarity index 100% rename from vertical-pod-autoscaler/pkg/recommender/input/controller_fetcher/controller_cache_storage_test.go rename to vertical-pod-autoscaler/pkg/target/controller_fetcher/controller_cache_storage_test.go diff --git a/vertical-pod-autoscaler/pkg/recommender/input/controller_fetcher/controller_fetcher.go b/vertical-pod-autoscaler/pkg/target/controller_fetcher/controller_fetcher.go similarity index 100% rename from vertical-pod-autoscaler/pkg/recommender/input/controller_fetcher/controller_fetcher.go rename to vertical-pod-autoscaler/pkg/target/controller_fetcher/controller_fetcher.go diff --git a/vertical-pod-autoscaler/pkg/target/controller_fetcher/controller_fetcher_fake.go b/vertical-pod-autoscaler/pkg/target/controller_fetcher/controller_fetcher_fake.go new file mode 100644 index 000000000000..2ff38be46e8a --- /dev/null +++ b/vertical-pod-autoscaler/pkg/target/controller_fetcher/controller_fetcher_fake.go @@ -0,0 +1,27 @@ +/* +Copyright 2019 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package controllerfetcher + +// FakeControllerFetcher should be used in test only. It returns exactly the same controllerKey +type FakeControllerFetcher struct{} + +// FindTopMostWellKnownOrScalable returns the same key for that fake implementation +func (f FakeControllerFetcher) FindTopMostWellKnownOrScalable(controller *ControllerKeyWithAPIVersion) (*ControllerKeyWithAPIVersion, error) { + return controller, nil +} + +var _ ControllerFetcher = &FakeControllerFetcher{} diff --git a/vertical-pod-autoscaler/pkg/recommender/input/controller_fetcher/controller_fetcher_test.go b/vertical-pod-autoscaler/pkg/target/controller_fetcher/controller_fetcher_test.go similarity index 100% rename from vertical-pod-autoscaler/pkg/recommender/input/controller_fetcher/controller_fetcher_test.go rename to vertical-pod-autoscaler/pkg/target/controller_fetcher/controller_fetcher_test.go diff --git a/vertical-pod-autoscaler/pkg/updater/logic/updater.go b/vertical-pod-autoscaler/pkg/updater/logic/updater.go index 55dcaee2fe09..a2c44f965c0d 100644 --- a/vertical-pod-autoscaler/pkg/updater/logic/updater.go +++ b/vertical-pod-autoscaler/pkg/updater/logic/updater.go @@ -26,23 +26,25 @@ import ( apiv1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/labels" + kube_client "k8s.io/client-go/kubernetes" + "k8s.io/client-go/kubernetes/fake" + "k8s.io/client-go/kubernetes/scheme" + clientv1 "k8s.io/client-go/kubernetes/typed/core/v1" + v1lister "k8s.io/client-go/listers/core/v1" + "k8s.io/client-go/tools/cache" + "k8s.io/client-go/tools/record" + "k8s.io/klog/v2" + vpa_types "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/apis/autoscaling.k8s.io/v1" vpa_clientset "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/client/clientset/versioned" vpa_lister "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/client/listers/autoscaling.k8s.io/v1" "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/target" + controllerfetcher "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/target/controller_fetcher" "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/updater/eviction" "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/updater/priority" metrics_updater "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/utils/metrics/updater" "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/utils/status" vpa_api_util "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/utils/vpa" - kube_client "k8s.io/client-go/kubernetes" - "k8s.io/client-go/kubernetes/fake" - "k8s.io/client-go/kubernetes/scheme" - clientv1 "k8s.io/client-go/kubernetes/typed/core/v1" - v1lister "k8s.io/client-go/listers/core/v1" - "k8s.io/client-go/tools/cache" - "k8s.io/client-go/tools/record" - "k8s.io/klog/v2" ) // Updater performs updates on pods if recommended by Vertical Pod Autoscaler @@ -63,6 +65,7 @@ type updater struct { selectorFetcher target.VpaTargetSelectorFetcher useAdmissionControllerStatus bool statusValidator status.Validator + controllerFetcher controllerfetcher.ControllerFetcher } // NewUpdater creates Updater with given configuration @@ -78,6 +81,7 @@ func NewUpdater( recommendationProcessor vpa_api_util.RecommendationProcessor, evictionAdmission priority.PodEvictionAdmission, selectorFetcher target.VpaTargetSelectorFetcher, + controllerFetcher controllerfetcher.ControllerFetcher, priorityProcessor priority.PriorityProcessor, namespace string, ) (Updater, error) { @@ -96,6 +100,7 @@ func NewUpdater( evictionAdmission: evictionAdmission, priorityProcessor: priorityProcessor, selectorFetcher: selectorFetcher, + controllerFetcher: controllerFetcher, useAdmissionControllerStatus: useAdmissionControllerStatus, statusValidator: status.NewValidator( kubeClient, @@ -167,7 +172,7 @@ func (u *updater) RunOnce(ctx context.Context) { controlledPods := make(map[*vpa_types.VerticalPodAutoscaler][]*apiv1.Pod) for _, pod := range allLivePods { - controllingVPA := vpa_api_util.GetControllingVPAForPod(pod, vpas) + controllingVPA := vpa_api_util.GetControllingVPAForPod(pod, vpas, u.controllerFetcher) if controllingVPA != nil { controlledPods[controllingVPA.Vpa] = append(controlledPods[controllingVPA.Vpa], pod) } diff --git a/vertical-pod-autoscaler/pkg/updater/logic/updater_test.go b/vertical-pod-autoscaler/pkg/updater/logic/updater_test.go index 5f64ae1641fa..9a3f10dcca20 100644 --- a/vertical-pod-autoscaler/pkg/updater/logic/updater_test.go +++ b/vertical-pod-autoscaler/pkg/updater/logic/updater_test.go @@ -23,6 +23,7 @@ import ( "time" "golang.org/x/time/rate" + v1 "k8s.io/api/autoscaling/v1" "github.com/golang/mock/gomock" "github.com/stretchr/testify/assert" @@ -30,7 +31,9 @@ import ( "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" + vpa_types "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/apis/autoscaling.k8s.io/v1" + controllerfetcher "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/target/controller_fetcher" target_mock "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/target/mock" "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/updater/eviction" "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/updater/priority" @@ -132,6 +135,10 @@ func testRunOnceBase( selector := parseLabelSelector("app = testingApp") containerName := "container1" rc := apiv1.ReplicationController{ + TypeMeta: metav1.TypeMeta{ + Kind: "ReplicationController", + APIVersion: "apps/v1", + }, ObjectMeta: metav1.ObjectMeta{ Name: "rc", Namespace: "default", @@ -159,13 +166,18 @@ func testRunOnceBase( podLister := &test.PodListerMock{} podLister.On("List").Return(pods, nil) - + targetRef := &v1.CrossVersionObjectReference{ + Kind: rc.Kind, + Name: rc.Name, + APIVersion: rc.APIVersion, + } vpaObj := test.VerticalPodAutoscaler(). WithContainer(containerName). WithTarget("2", "200M"). WithMinAllowed(containerName, "1", "100M"). WithMaxAllowed(containerName, "3", "1G"). - Get() + WithTargetRef(targetRef).Get() + vpaObj.Spec.UpdatePolicy = &vpa_types.PodUpdatePolicy{UpdateMode: &updateMode} vpaLister.On("List").Return([]*vpa_types.VerticalPodAutoscaler{vpaObj}, nil).Once() @@ -179,6 +191,7 @@ func testRunOnceBase( evictionAdmission: priority.NewDefaultPodEvictionAdmission(), recommendationProcessor: &test.FakeRecommendationProcessor{}, selectorFetcher: mockSelectorFetcher, + controllerFetcher: controllerfetcher.FakeControllerFetcher{}, useAdmissionControllerStatus: true, statusValidator: statusValidator, priorityProcessor: priority.NewProcessor(), diff --git a/vertical-pod-autoscaler/pkg/updater/main.go b/vertical-pod-autoscaler/pkg/updater/main.go index 4d2315dba7f3..c69b8c286494 100644 --- a/vertical-pod-autoscaler/pkg/updater/main.go +++ b/vertical-pod-autoscaler/pkg/updater/main.go @@ -23,9 +23,15 @@ import ( "time" apiv1 "k8s.io/api/core/v1" + "k8s.io/client-go/informers" + kube_client "k8s.io/client-go/kubernetes" + kube_flag "k8s.io/component-base/cli/flag" + "k8s.io/klog/v2" + "k8s.io/autoscaler/vertical-pod-autoscaler/common" vpa_clientset "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/client/clientset/versioned" "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/target" + controllerfetcher "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/target/controller_fetcher" updater "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/updater/logic" "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/updater/priority" "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/utils/limitrange" @@ -33,10 +39,6 @@ import ( metrics_updater "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/utils/metrics/updater" "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/utils/status" vpa_api_util "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/utils/vpa" - "k8s.io/client-go/informers" - kube_client "k8s.io/client-go/kubernetes" - kube_flag "k8s.io/component-base/cli/flag" - "k8s.io/klog/v2" ) var ( @@ -67,7 +69,12 @@ var ( vpaObjectNamespace = flag.String("vpa-object-namespace", apiv1.NamespaceAll, "Namespace to search for VPA objects. Empty means all namespaces will be used.") ) -const defaultResyncPeriod time.Duration = 10 * time.Minute +const ( + defaultResyncPeriod time.Duration = 10 * time.Minute + scaleCacheEntryLifetime time.Duration = time.Hour + scaleCacheEntryFreshnessTime time.Duration = 10 * time.Minute + scaleCacheEntryJitterFactor float64 = 1. +) func main() { klog.InitFlags(nil) @@ -83,6 +90,7 @@ func main() { vpaClient := vpa_clientset.NewForConfigOrDie(config) factory := informers.NewSharedInformerFactory(kubeClient, defaultResyncPeriod) targetSelectorFetcher := target.NewVpaTargetSelectorFetcher(config, kubeClient, factory) + controllerFetcher := controllerfetcher.NewControllerFetcher(config, kubeClient, factory, scaleCacheEntryFreshnessTime, scaleCacheEntryLifetime, scaleCacheEntryJitterFactor) var limitRangeCalculator limitrange.LimitRangeCalculator limitRangeCalculator, err := limitrange.NewLimitsRangeCalculator(factory) if err != nil { @@ -106,6 +114,7 @@ func main() { vpa_api_util.NewCappingRecommendationProcessor(limitRangeCalculator), priority.NewScalingDirectionPodEvictionAdmission(), targetSelectorFetcher, + controllerFetcher, priority.NewProcessor(), *vpaObjectNamespace, ) diff --git a/vertical-pod-autoscaler/pkg/utils/vpa/api.go b/vertical-pod-autoscaler/pkg/utils/vpa/api.go index 891c0e0bb3de..d6b9fbc22e5e 100644 --- a/vertical-pod-autoscaler/pkg/utils/vpa/api.go +++ b/vertical-pod-autoscaler/pkg/utils/vpa/api.go @@ -29,12 +29,14 @@ import ( "k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/tools/cache" + "k8s.io/klog/v2" + vpa_types "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/apis/autoscaling.k8s.io/v1" vpa_clientset "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/client/clientset/versioned" vpa_api "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/client/clientset/versioned/typed/autoscaling.k8s.io/v1" vpa_lister "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/client/listers/autoscaling.k8s.io/v1" - "k8s.io/client-go/tools/cache" - "k8s.io/klog/v2" + controllerfetcher "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/target/controller_fetcher" ) // VpaWithSelector is a pair of VPA and its selector. @@ -125,11 +127,45 @@ func stronger(a, b *vpa_types.VerticalPodAutoscaler) bool { } // GetControllingVPAForPod chooses the earliest created VPA from the input list that matches the given Pod. -func GetControllingVPAForPod(pod *core.Pod, vpas []*VpaWithSelector) *VpaWithSelector { +func GetControllingVPAForPod(pod *core.Pod, vpas []*VpaWithSelector, ctrlFetcher controllerfetcher.ControllerFetcher) *VpaWithSelector { + + var ownerRefrence *meta.OwnerReference + for i := range pod.OwnerReferences { + r := pod.OwnerReferences[i] + if r.Controller != nil && *r.Controller { + ownerRefrence = &r + } + } + if ownerRefrence == nil { + // If the pod has no ownerReference, it cannot be under a VPA. + return nil + } + k := &controllerfetcher.ControllerKeyWithAPIVersion{ + ControllerKey: controllerfetcher.ControllerKey{ + Namespace: pod.Namespace, + Kind: ownerRefrence.Kind, + Name: ownerRefrence.Name, + }, + ApiVersion: ownerRefrence.APIVersion, + } + parentController, err := ctrlFetcher.FindTopMostWellKnownOrScalable(k) + if err != nil { + klog.Errorf("fail to get pod controller: pod=%s err=%s", pod.Name, err.Error()) + return nil + } + var controlling *VpaWithSelector var controllingVpa *vpa_types.VerticalPodAutoscaler // Choose the strongest VPA from the ones that match this Pod. for _, vpaWithSelector := range vpas { + if vpaWithSelector.Vpa.Spec.TargetRef == nil { + continue + } + if vpaWithSelector.Vpa.Spec.TargetRef.Kind != parentController.Kind || + vpaWithSelector.Vpa.Namespace != parentController.Namespace || + vpaWithSelector.Vpa.Spec.TargetRef.Name != parentController.Name { + continue // This pod is not associated to the right controller + } if PodMatchesVPA(pod, vpaWithSelector) && stronger(vpaWithSelector.Vpa, controllingVpa) { controlling = vpaWithSelector controllingVpa = controlling.Vpa diff --git a/vertical-pod-autoscaler/pkg/utils/vpa/api_test.go b/vertical-pod-autoscaler/pkg/utils/vpa/api_test.go index 601b14f972a9..46787247eaea 100644 --- a/vertical-pod-autoscaler/pkg/utils/vpa/api_test.go +++ b/vertical-pod-autoscaler/pkg/utils/vpa/api_test.go @@ -22,12 +22,15 @@ import ( "time" "github.com/stretchr/testify/assert" + v1 "k8s.io/api/autoscaling/v1" core "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" meta "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" + vpa_types "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/apis/autoscaling.k8s.io/v1" vpa_fake "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/client/clientset/versioned/fake" + controllerfetcher "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/target/controller_fetcher" "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/utils/test" ) @@ -137,8 +140,17 @@ func TestPodMatchesVPA(t *testing.T) { } func TestGetControllingVPAForPod(t *testing.T) { + isController := true pod := test.Pod().WithName("test-pod").AddContainer(test.Container().WithName(containerName).WithCPURequest(resource.MustParse("1")).WithMemRequest(resource.MustParse("100M")).Get()).Get() pod.Labels = map[string]string{"app": "testingApp"} + pod.OwnerReferences = []meta.OwnerReference{ + { + APIVersion: "apps/v1", + Kind: "StatefulSet", + Name: "test-sts", + Controller: &isController, + }, + } vpaBuilder := test.VerticalPodAutoscaler(). WithContainer(containerName). @@ -148,12 +160,16 @@ func TestGetControllingVPAForPod(t *testing.T) { vpaA := vpaBuilder.WithCreationTimestamp(time.Unix(5, 0)).Get() vpaB := vpaBuilder.WithCreationTimestamp(time.Unix(10, 0)).Get() nonMatchingVPA := vpaBuilder.WithCreationTimestamp(time.Unix(2, 0)).Get() - + vpaA.Spec.TargetRef = &v1.CrossVersionObjectReference{ + Kind: "StatefulSet", + Name: "test-sts", + APIVersion: "apps/v1", + } chosen := GetControllingVPAForPod(pod, []*VpaWithSelector{ {vpaB, parseLabelSelector("app = testingApp")}, {vpaA, parseLabelSelector("app = testingApp")}, {nonMatchingVPA, parseLabelSelector("app = other")}, - }) + }, &controllerfetcher.FakeControllerFetcher{}) assert.Equal(t, vpaA, chosen.Vpa) }