Skip to content

Commit a2f4cac

Browse files
authored
Merge pull request #6460 from DataDog/david.benque/validate-owner-ref
[VPA] check OwnerRef against TargetRef to confirm VPA/Pod association
2 parents 7e95c7e + 4f9f840 commit a2f4cac

File tree

19 files changed

+204
-51
lines changed

19 files changed

+204
-51
lines changed

vertical-pod-autoscaler/pkg/admission-controller/main.go

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@ import (
2424
"time"
2525

2626
apiv1 "k8s.io/api/core/v1"
27+
"k8s.io/client-go/informers"
28+
kube_client "k8s.io/client-go/kubernetes"
29+
kube_flag "k8s.io/component-base/cli/flag"
30+
"k8s.io/klog/v2"
31+
2732
"k8s.io/autoscaler/vertical-pod-autoscaler/common"
2833
"k8s.io/autoscaler/vertical-pod-autoscaler/pkg/admission-controller/logic"
2934
"k8s.io/autoscaler/vertical-pod-autoscaler/pkg/admission-controller/resource/pod"
@@ -32,20 +37,20 @@ import (
3237
"k8s.io/autoscaler/vertical-pod-autoscaler/pkg/admission-controller/resource/vpa"
3338
vpa_clientset "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/client/clientset/versioned"
3439
"k8s.io/autoscaler/vertical-pod-autoscaler/pkg/target"
40+
controllerfetcher "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/target/controller_fetcher"
3541
"k8s.io/autoscaler/vertical-pod-autoscaler/pkg/utils/limitrange"
3642
"k8s.io/autoscaler/vertical-pod-autoscaler/pkg/utils/metrics"
3743
metrics_admission "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/utils/metrics/admission"
3844
"k8s.io/autoscaler/vertical-pod-autoscaler/pkg/utils/status"
3945
vpa_api_util "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/utils/vpa"
40-
"k8s.io/client-go/informers"
41-
kube_client "k8s.io/client-go/kubernetes"
42-
kube_flag "k8s.io/component-base/cli/flag"
43-
"k8s.io/klog/v2"
4446
)
4547

4648
const (
47-
defaultResyncPeriod = 10 * time.Minute
48-
statusUpdateInterval = 10 * time.Second
49+
defaultResyncPeriod = 10 * time.Minute
50+
statusUpdateInterval = 10 * time.Second
51+
scaleCacheEntryLifetime time.Duration = time.Hour
52+
scaleCacheEntryFreshnessTime time.Duration = 10 * time.Minute
53+
scaleCacheEntryJitterFactor float64 = 1.
4954
)
5055

5156
var (
@@ -87,6 +92,7 @@ func main() {
8792
kubeClient := kube_client.NewForConfigOrDie(config)
8893
factory := informers.NewSharedInformerFactory(kubeClient, defaultResyncPeriod)
8994
targetSelectorFetcher := target.NewVpaTargetSelectorFetcher(config, kubeClient, factory)
95+
controllerFetcher := controllerfetcher.NewControllerFetcher(config, kubeClient, factory, scaleCacheEntryFreshnessTime, scaleCacheEntryLifetime, scaleCacheEntryJitterFactor)
9096
podPreprocessor := pod.NewDefaultPreProcessor()
9197
vpaPreprocessor := vpa.NewDefaultPreProcessor()
9298
var limitRangeCalculator limitrange.LimitRangeCalculator
@@ -96,7 +102,7 @@ func main() {
96102
limitRangeCalculator = limitrange.NewNoopLimitsCalculator()
97103
}
98104
recommendationProvider := recommendation.NewProvider(limitRangeCalculator, vpa_api_util.NewCappingRecommendationProcessor(limitRangeCalculator))
99-
vpaMatcher := vpa.NewMatcher(vpaLister, targetSelectorFetcher)
105+
vpaMatcher := vpa.NewMatcher(vpaLister, targetSelectorFetcher, controllerFetcher)
100106

101107
hostname, err := os.Hostname()
102108
if err != nil {

vertical-pod-autoscaler/pkg/admission-controller/resource/vpa/matcher.go

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,13 @@ package vpa
1919
import (
2020
core "k8s.io/api/core/v1"
2121
"k8s.io/apimachinery/pkg/labels"
22+
"k8s.io/klog/v2"
23+
2224
vpa_types "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/apis/autoscaling.k8s.io/v1"
2325
vpa_lister "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/client/listers/autoscaling.k8s.io/v1"
2426
"k8s.io/autoscaler/vertical-pod-autoscaler/pkg/target"
27+
controllerfetcher "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/target/controller_fetcher"
2528
vpa_api_util "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/utils/vpa"
26-
"k8s.io/klog/v2"
2729
)
2830

2931
// Matcher is capable of returning a single matching VPA object
@@ -33,15 +35,18 @@ type Matcher interface {
3335
}
3436

3537
type matcher struct {
36-
vpaLister vpa_lister.VerticalPodAutoscalerLister
37-
selectorFetcher target.VpaTargetSelectorFetcher
38+
vpaLister vpa_lister.VerticalPodAutoscalerLister
39+
selectorFetcher target.VpaTargetSelectorFetcher
40+
controllerFetcher controllerfetcher.ControllerFetcher
3841
}
3942

4043
// NewMatcher returns a new VPA matcher.
4144
func NewMatcher(vpaLister vpa_lister.VerticalPodAutoscalerLister,
42-
selectorFetcher target.VpaTargetSelectorFetcher) Matcher {
45+
selectorFetcher target.VpaTargetSelectorFetcher,
46+
controllerFetcher controllerfetcher.ControllerFetcher) Matcher {
4347
return &matcher{vpaLister: vpaLister,
44-
selectorFetcher: selectorFetcher}
48+
selectorFetcher: selectorFetcher,
49+
controllerFetcher: controllerFetcher}
4550
}
4651

4752
func (m *matcher) GetMatchingVPA(pod *core.Pod) *vpa_types.VerticalPodAutoscaler {
@@ -66,7 +71,7 @@ func (m *matcher) GetMatchingVPA(pod *core.Pod) *vpa_types.VerticalPodAutoscaler
6671
})
6772
}
6873
klog.V(2).Infof("Let's choose from %d configs for pod %s/%s", len(onConfigs), pod.Namespace, pod.Name)
69-
result := vpa_api_util.GetControllingVPAForPod(pod, onConfigs)
74+
result := vpa_api_util.GetControllingVPAForPod(pod, onConfigs, m.controllerFetcher)
7075
if result != nil {
7176
return result.Vpa
7277
}

vertical-pod-autoscaler/pkg/admission-controller/resource/vpa/matcher_test.go

Lines changed: 40 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,14 @@ package vpa
1919
import (
2020
"testing"
2121

22+
appsv1 "k8s.io/api/apps/v1"
23+
v1 "k8s.io/api/autoscaling/v1"
2224
core "k8s.io/api/core/v1"
2325
meta "k8s.io/apimachinery/pkg/apis/meta/v1"
2426
"k8s.io/apimachinery/pkg/labels"
27+
2528
vpa_types "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/apis/autoscaling.k8s.io/v1"
29+
controllerfetcher "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/target/controller_fetcher"
2630
target_mock "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/target/mock"
2731
"k8s.io/autoscaler/vertical-pod-autoscaler/pkg/utils/test"
2832

@@ -37,8 +41,24 @@ func parseLabelSelector(selector string) labels.Selector {
3741
}
3842

3943
func TestGetMatchingVpa(t *testing.T) {
40-
podBuilder := test.Pod().WithName("test-pod").WithLabels(map[string]string{"app": "test"}).
44+
sts := appsv1.StatefulSet{
45+
TypeMeta: meta.TypeMeta{
46+
Kind: "StatefulSet",
47+
APIVersion: "apps/v1",
48+
},
49+
ObjectMeta: meta.ObjectMeta{
50+
Name: "sts",
51+
Namespace: "default",
52+
},
53+
}
54+
targetRef := &v1.CrossVersionObjectReference{
55+
Kind: sts.Kind,
56+
Name: sts.Name,
57+
APIVersion: sts.APIVersion,
58+
}
59+
podBuilderWithoutCreator := test.Pod().WithName("test-pod").WithLabels(map[string]string{"app": "test"}).
4160
AddContainer(test.Container().WithName("i-am-container").Get())
61+
podBuilder := podBuilderWithoutCreator.WithCreator(&sts.ObjectMeta, &sts.TypeMeta)
4262
vpaBuilder := test.VerticalPodAutoscaler().WithContainer("i-am-container")
4363
testCases := []struct {
4464
name string
@@ -52,33 +72,41 @@ func TestGetMatchingVpa(t *testing.T) {
5272
name: "matching selector",
5373
pod: podBuilder.Get(),
5474
vpas: []*vpa_types.VerticalPodAutoscaler{
55-
vpaBuilder.WithUpdateMode(vpa_types.UpdateModeAuto).WithName("auto-vpa").Get(),
75+
vpaBuilder.WithUpdateMode(vpa_types.UpdateModeAuto).WithName("auto-vpa").WithTargetRef(targetRef).Get(),
5676
},
5777
labelSelector: "app = test",
5878
expectedFound: true,
5979
expectedVpaName: "auto-vpa",
80+
}, {
81+
name: "matching selector but not match ownerRef (orphan pod)",
82+
pod: podBuilderWithoutCreator.Get(),
83+
vpas: []*vpa_types.VerticalPodAutoscaler{
84+
vpaBuilder.WithUpdateMode(vpa_types.UpdateModeAuto).WithName("auto-vpa").WithTargetRef(targetRef).Get(),
85+
},
86+
labelSelector: "app = test",
87+
expectedFound: false,
6088
}, {
6189
name: "not matching selector",
6290
pod: podBuilder.Get(),
6391
vpas: []*vpa_types.VerticalPodAutoscaler{
64-
vpaBuilder.WithUpdateMode(vpa_types.UpdateModeAuto).WithName("auto-vpa").Get(),
92+
vpaBuilder.WithUpdateMode(vpa_types.UpdateModeAuto).WithName("auto-vpa").WithTargetRef(targetRef).Get(),
6593
},
6694
labelSelector: "app = differentApp",
6795
expectedFound: false,
6896
}, {
6997
name: "off mode",
7098
pod: podBuilder.Get(),
7199
vpas: []*vpa_types.VerticalPodAutoscaler{
72-
vpaBuilder.WithUpdateMode(vpa_types.UpdateModeOff).WithName("off-vpa").Get(),
100+
vpaBuilder.WithUpdateMode(vpa_types.UpdateModeOff).WithName("off-vpa").WithTargetRef(targetRef).Get(),
73101
},
74102
labelSelector: "app = test",
75103
expectedFound: false,
76104
}, {
77105
name: "two vpas one in off mode",
78106
pod: podBuilder.Get(),
79107
vpas: []*vpa_types.VerticalPodAutoscaler{
80-
vpaBuilder.WithUpdateMode(vpa_types.UpdateModeOff).WithName("off-vpa").Get(),
81-
vpaBuilder.WithUpdateMode(vpa_types.UpdateModeAuto).WithName("auto-vpa").Get(),
108+
vpaBuilder.WithUpdateMode(vpa_types.UpdateModeOff).WithName("off-vpa").WithTargetRef(targetRef).Get(),
109+
vpaBuilder.WithUpdateMode(vpa_types.UpdateModeAuto).WithName("auto-vpa").WithTargetRef(targetRef).Get(),
82110
},
83111
labelSelector: "app = test",
84112
expectedFound: true,
@@ -87,7 +115,7 @@ func TestGetMatchingVpa(t *testing.T) {
87115
name: "initial mode",
88116
pod: podBuilder.Get(),
89117
vpas: []*vpa_types.VerticalPodAutoscaler{
90-
vpaBuilder.WithUpdateMode(vpa_types.UpdateModeInitial).WithName("initial-vpa").Get(),
118+
vpaBuilder.WithUpdateMode(vpa_types.UpdateModeInitial).WithName("initial-vpa").WithTargetRef(targetRef).Get(),
91119
},
92120
labelSelector: "app = test",
93121
expectedFound: true,
@@ -114,7 +142,11 @@ func TestGetMatchingVpa(t *testing.T) {
114142
vpaLister.On("VerticalPodAutoscalers", "default").Return(vpaNamespaceLister)
115143

116144
mockSelectorFetcher.EXPECT().Fetch(gomock.Any()).AnyTimes().Return(parseLabelSelector(tc.labelSelector), nil)
117-
matcher := NewMatcher(vpaLister, mockSelectorFetcher)
145+
// This test is using a FakeControllerFetcher which returns the same ownerRef that is passed to it.
146+
// In other words, it cannot go through the hierarchy of controllers like "ReplicaSet => Deployment"
147+
// For this reason we are using "StatefulSet" as the ownerRef kind in the test, since it is a direct link.
148+
// The hierarchy part is being test in the "TestControllerFetcher" test.
149+
matcher := NewMatcher(vpaLister, mockSelectorFetcher, controllerfetcher.FakeControllerFetcher{})
118150

119151
vpa := matcher.GetMatchingVPA(tc.pod)
120152
if tc.expectedFound && assert.NotNil(t, vpa) {

vertical-pod-autoscaler/pkg/recommender/input/cluster_feeder.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,13 @@ import (
3636
vpa_types "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/apis/autoscaling.k8s.io/v1"
3737
vpa_api "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/client/clientset/versioned/typed/autoscaling.k8s.io/v1"
3838
vpa_lister "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/client/listers/autoscaling.k8s.io/v1"
39-
controllerfetcher "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/recommender/input/controller_fetcher"
4039
"k8s.io/autoscaler/vertical-pod-autoscaler/pkg/recommender/input/history"
4140
"k8s.io/autoscaler/vertical-pod-autoscaler/pkg/recommender/input/metrics"
4241
"k8s.io/autoscaler/vertical-pod-autoscaler/pkg/recommender/input/oom"
4342
"k8s.io/autoscaler/vertical-pod-autoscaler/pkg/recommender/input/spec"
4443
"k8s.io/autoscaler/vertical-pod-autoscaler/pkg/recommender/model"
4544
"k8s.io/autoscaler/vertical-pod-autoscaler/pkg/target"
45+
"k8s.io/autoscaler/vertical-pod-autoscaler/pkg/target/controller_fetcher"
4646
metrics_recommender "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/utils/metrics/recommender"
4747
)
4848

vertical-pod-autoscaler/pkg/recommender/input/cluster_feeder_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,12 @@ import (
2727
autoscalingv1 "k8s.io/api/autoscaling/v1"
2828
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2929
"k8s.io/apimachinery/pkg/labels"
30+
3031
vpa_types "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/apis/autoscaling.k8s.io/v1"
31-
controllerfetcher "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/recommender/input/controller_fetcher"
3232
"k8s.io/autoscaler/vertical-pod-autoscaler/pkg/recommender/input/history"
3333
"k8s.io/autoscaler/vertical-pod-autoscaler/pkg/recommender/input/spec"
3434
"k8s.io/autoscaler/vertical-pod-autoscaler/pkg/recommender/model"
35+
"k8s.io/autoscaler/vertical-pod-autoscaler/pkg/target/controller_fetcher"
3536
target_mock "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/target/mock"
3637
"k8s.io/autoscaler/vertical-pod-autoscaler/pkg/utils/test"
3738
)

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,10 @@ package main
1919
import (
2020
"context"
2121
"flag"
22-
resourceclient "k8s.io/metrics/pkg/client/clientset/versioned/typed/metrics/v1beta1"
2322
"time"
2423

24+
resourceclient "k8s.io/metrics/pkg/client/clientset/versioned/typed/metrics/v1beta1"
25+
2526
apiv1 "k8s.io/api/core/v1"
2627
"k8s.io/client-go/informers"
2728
kube_client "k8s.io/client-go/kubernetes"
@@ -32,13 +33,13 @@ import (
3233
vpa_clientset "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/client/clientset/versioned"
3334
"k8s.io/autoscaler/vertical-pod-autoscaler/pkg/recommender/checkpoint"
3435
"k8s.io/autoscaler/vertical-pod-autoscaler/pkg/recommender/input"
35-
controllerfetcher "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/recommender/input/controller_fetcher"
3636
"k8s.io/autoscaler/vertical-pod-autoscaler/pkg/recommender/input/history"
3737
input_metrics "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/recommender/input/metrics"
3838
"k8s.io/autoscaler/vertical-pod-autoscaler/pkg/recommender/logic"
3939
"k8s.io/autoscaler/vertical-pod-autoscaler/pkg/recommender/model"
4040
"k8s.io/autoscaler/vertical-pod-autoscaler/pkg/recommender/routines"
4141
"k8s.io/autoscaler/vertical-pod-autoscaler/pkg/target"
42+
"k8s.io/autoscaler/vertical-pod-autoscaler/pkg/target/controller_fetcher"
4243
"k8s.io/autoscaler/vertical-pod-autoscaler/pkg/utils/metrics"
4344
metrics_quality "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/utils/metrics/quality"
4445
metrics_recommender "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/utils/metrics/recommender"

vertical-pod-autoscaler/pkg/recommender/model/cluster.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,11 @@ import (
2222

2323
apiv1 "k8s.io/api/core/v1"
2424
labels "k8s.io/apimachinery/pkg/labels"
25+
"k8s.io/klog/v2"
26+
2527
vpa_types "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/apis/autoscaling.k8s.io/v1"
26-
controllerfetcher "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/recommender/input/controller_fetcher"
28+
"k8s.io/autoscaler/vertical-pod-autoscaler/pkg/target/controller_fetcher"
2729
vpa_utils "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/utils/vpa"
28-
"k8s.io/klog/v2"
2930
)
3031

3132
const (

vertical-pod-autoscaler/pkg/recommender/model/cluster_test.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,11 @@ import (
2626
apiv1 "k8s.io/api/core/v1"
2727
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2828
"k8s.io/apimachinery/pkg/labels"
29+
"k8s.io/klog/v2"
30+
2931
vpa_types "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/apis/autoscaling.k8s.io/v1"
30-
controllerfetcher "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/recommender/input/controller_fetcher"
32+
"k8s.io/autoscaler/vertical-pod-autoscaler/pkg/target/controller_fetcher"
3133
"k8s.io/autoscaler/vertical-pod-autoscaler/pkg/utils/test"
32-
"k8s.io/klog/v2"
3334
)
3435

3536
var (

vertical-pod-autoscaler/pkg/recommender/routines/recommender.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,9 @@ import (
2626
vpa_api "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/client/clientset/versioned/typed/autoscaling.k8s.io/v1"
2727
"k8s.io/autoscaler/vertical-pod-autoscaler/pkg/recommender/checkpoint"
2828
"k8s.io/autoscaler/vertical-pod-autoscaler/pkg/recommender/input"
29-
controllerfetcher "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/recommender/input/controller_fetcher"
3029
"k8s.io/autoscaler/vertical-pod-autoscaler/pkg/recommender/logic"
3130
"k8s.io/autoscaler/vertical-pod-autoscaler/pkg/recommender/model"
31+
"k8s.io/autoscaler/vertical-pod-autoscaler/pkg/target/controller_fetcher"
3232
metrics_recommender "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/utils/metrics/recommender"
3333
vpa_utils "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/utils/vpa"
3434
)
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/*
2+
Copyright 2019 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 controllerfetcher
18+
19+
// FakeControllerFetcher should be used in test only. It returns exactly the same controllerKey
20+
type FakeControllerFetcher struct{}
21+
22+
// FindTopMostWellKnownOrScalable returns the same key for that fake implementation
23+
func (f FakeControllerFetcher) FindTopMostWellKnownOrScalable(controller *ControllerKeyWithAPIVersion) (*ControllerKeyWithAPIVersion, error) {
24+
return controller, nil
25+
}
26+
27+
var _ ControllerFetcher = &FakeControllerFetcher{}

0 commit comments

Comments
 (0)