vpa-admission-controller: Improve finding the matching VPA for Pod
This commit is contained in:
parent
e6150cc55c
commit
c76dffa968
|
|
@ -52,30 +52,46 @@ func NewMatcher(vpaLister vpa_lister.VerticalPodAutoscalerLister,
|
|||
}
|
||||
|
||||
func (m *matcher) GetMatchingVPA(ctx context.Context, pod *core.Pod) *vpa_types.VerticalPodAutoscaler {
|
||||
parentController, err := vpa_api_util.FindParentControllerForPod(ctx, pod, m.controllerFetcher)
|
||||
if err != nil {
|
||||
klog.ErrorS(err, "Failed to get parent controller for pod", "pod", klog.KObj(pod))
|
||||
return nil
|
||||
}
|
||||
if parentController == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
configs, err := m.vpaLister.VerticalPodAutoscalers(pod.Namespace).List(labels.Everything())
|
||||
if err != nil {
|
||||
klog.ErrorS(err, "Failed to get vpa configs")
|
||||
return nil
|
||||
}
|
||||
onConfigs := make([]*vpa_api_util.VpaWithSelector, 0)
|
||||
|
||||
var controllingVpa *vpa_types.VerticalPodAutoscaler
|
||||
for _, vpaConfig := range configs {
|
||||
if vpa_api_util.GetUpdateMode(vpaConfig) == vpa_types.UpdateModeOff {
|
||||
continue
|
||||
}
|
||||
if vpaConfig.Spec.TargetRef == nil {
|
||||
continue
|
||||
}
|
||||
if vpaConfig.Spec.TargetRef.Kind != parentController.Kind ||
|
||||
vpaConfig.Namespace != parentController.Namespace ||
|
||||
vpaConfig.Spec.TargetRef.Name != parentController.Name {
|
||||
continue // This pod is not associated to the right controller
|
||||
}
|
||||
|
||||
selector, err := m.selectorFetcher.Fetch(ctx, vpaConfig)
|
||||
if err != nil {
|
||||
klog.V(3).InfoS("Skipping VPA object because we cannot fetch selector", "vpa", klog.KObj(vpaConfig), "error", err)
|
||||
continue
|
||||
}
|
||||
onConfigs = append(onConfigs, &vpa_api_util.VpaWithSelector{
|
||||
Vpa: vpaConfig,
|
||||
Selector: selector,
|
||||
})
|
||||
|
||||
vpaWithSelector := &vpa_api_util.VpaWithSelector{Vpa: vpaConfig, Selector: selector}
|
||||
if vpa_api_util.PodMatchesVPA(pod, vpaWithSelector) && vpa_api_util.Stronger(vpaConfig, controllingVpa) {
|
||||
controllingVpa = vpaConfig
|
||||
}
|
||||
}
|
||||
klog.V(2).InfoS("Let's choose from", "configs", len(onConfigs), "pod", klog.KObj(pod))
|
||||
result := vpa_api_util.GetControllingVPAForPod(ctx, pod, onConfigs, m.controllerFetcher)
|
||||
if result != nil {
|
||||
return result.Vpa
|
||||
}
|
||||
return nil
|
||||
|
||||
return controllingVpa
|
||||
}
|
||||
|
|
|
|||
|
|
@ -57,10 +57,16 @@ func TestGetMatchingVpa(t *testing.T) {
|
|||
Name: sts.Name,
|
||||
APIVersion: sts.APIVersion,
|
||||
}
|
||||
targetRefWithNoMatches := &v1.CrossVersionObjectReference{
|
||||
Kind: "ReplicaSet",
|
||||
Name: "rs",
|
||||
APIVersion: "apps/v1",
|
||||
}
|
||||
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
|
||||
pod *core.Pod
|
||||
|
|
@ -79,12 +85,25 @@ func TestGetMatchingVpa(t *testing.T) {
|
|||
expectedFound: true,
|
||||
expectedVpaName: "auto-vpa",
|
||||
}, {
|
||||
name: "matching selector but not match ownerRef (orphan pod)",
|
||||
name: "no matching 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: "vpa without targetRef",
|
||||
pod: podBuilder.Get(),
|
||||
vpas: []*vpa_types.VerticalPodAutoscaler{
|
||||
vpaBuilder.WithUpdateMode(vpa_types.UpdateModeAuto).WithName("auto-vpa").Get(),
|
||||
},
|
||||
expectedFound: false,
|
||||
}, {
|
||||
name: "no vpa with matching targetRef",
|
||||
pod: podBuilder.Get(),
|
||||
vpas: []*vpa_types.VerticalPodAutoscaler{
|
||||
vpaBuilder.WithUpdateMode(vpa_types.UpdateModeAuto).WithName("auto-vpa").WithTargetRef(targetRefWithNoMatches).Get(),
|
||||
},
|
||||
expectedFound: false,
|
||||
}, {
|
||||
name: "not matching selector",
|
||||
|
|
@ -100,10 +119,9 @@ func TestGetMatchingVpa(t *testing.T) {
|
|||
vpas: []*vpa_types.VerticalPodAutoscaler{
|
||||
vpaBuilder.WithUpdateMode(vpa_types.UpdateModeOff).WithName("off-vpa").WithTargetRef(targetRef).Get(),
|
||||
},
|
||||
labelSelector: "app = test",
|
||||
expectedFound: false,
|
||||
}, {
|
||||
name: "two vpas one in off mode",
|
||||
name: "two vpas, one in off mode",
|
||||
pod: podBuilder.Get(),
|
||||
vpas: []*vpa_types.VerticalPodAutoscaler{
|
||||
vpaBuilder.WithUpdateMode(vpa_types.UpdateModeOff).WithName("off-vpa").WithTargetRef(targetRef).Get(),
|
||||
|
|
@ -125,10 +143,10 @@ func TestGetMatchingVpa(t *testing.T) {
|
|||
name: "no vpa objects",
|
||||
pod: podBuilder.Get(),
|
||||
vpas: []*vpa_types.VerticalPodAutoscaler{},
|
||||
labelSelector: "app = test",
|
||||
expectedFound: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
|
|
@ -142,7 +160,9 @@ func TestGetMatchingVpa(t *testing.T) {
|
|||
vpaLister := &test.VerticalPodAutoscalerListerMock{}
|
||||
vpaLister.On("VerticalPodAutoscalers", "default").Return(vpaNamespaceLister)
|
||||
|
||||
mockSelectorFetcher.EXPECT().Fetch(gomock.Any()).AnyTimes().Return(parseLabelSelector(tc.labelSelector), nil)
|
||||
if tc.labelSelector != "" {
|
||||
mockSelectorFetcher.EXPECT().Fetch(gomock.Any()).AnyTimes().Return(parseLabelSelector(tc.labelSelector), nil)
|
||||
}
|
||||
// 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.
|
||||
|
|
|
|||
|
|
@ -109,8 +109,8 @@ func PodLabelsMatchVPA(podNamespace string, labels labels.Set, vpaNamespace stri
|
|||
return vpaSelector.Matches(labels)
|
||||
}
|
||||
|
||||
// stronger returns true iff a is before b in the order to control a Pod (that matches both VPAs).
|
||||
func stronger(a, b *vpa_types.VerticalPodAutoscaler) bool {
|
||||
// Stronger returns true iff a is before b in the order to control a Pod (that matches both VPAs).
|
||||
func Stronger(a, b *vpa_types.VerticalPodAutoscaler) bool {
|
||||
// Assume a is not nil and each valid object is before nil object.
|
||||
if b == nil {
|
||||
return true
|
||||
|
|
@ -129,28 +129,9 @@ 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(ctx context.Context, 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(ctx, k)
|
||||
parentController, err := FindParentControllerForPod(ctx, pod, ctrlFetcher)
|
||||
if err != nil {
|
||||
klog.ErrorS(err, "Failed to get pod controller", "pod", klog.KObj(pod))
|
||||
klog.ErrorS(err, "Failed to get parent controller for pod", "pod", klog.KObj(pod))
|
||||
return nil
|
||||
}
|
||||
if parentController == nil {
|
||||
|
|
@ -169,7 +150,7 @@ func GetControllingVPAForPod(ctx context.Context, pod *core.Pod, vpas []*VpaWith
|
|||
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) {
|
||||
if PodMatchesVPA(pod, vpaWithSelector) && Stronger(vpaWithSelector.Vpa, controllingVpa) {
|
||||
controlling = vpaWithSelector
|
||||
controllingVpa = controlling.Vpa
|
||||
}
|
||||
|
|
@ -177,6 +158,30 @@ func GetControllingVPAForPod(ctx context.Context, pod *core.Pod, vpas []*VpaWith
|
|||
return controlling
|
||||
}
|
||||
|
||||
// FindParentControllerForPod returns the parent controller (topmost well-known or scalable controller) for the given Pod.
|
||||
func FindParentControllerForPod(ctx context.Context, pod *core.Pod, ctrlFetcher controllerfetcher.ControllerFetcher) (*controllerfetcher.ControllerKeyWithAPIVersion, error) {
|
||||
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, nil
|
||||
}
|
||||
k := &controllerfetcher.ControllerKeyWithAPIVersion{
|
||||
ControllerKey: controllerfetcher.ControllerKey{
|
||||
Namespace: pod.Namespace,
|
||||
Kind: ownerRefrence.Kind,
|
||||
Name: ownerRefrence.Name,
|
||||
},
|
||||
ApiVersion: ownerRefrence.APIVersion,
|
||||
}
|
||||
return ctrlFetcher.FindTopMostWellKnownOrScalable(ctx, k)
|
||||
}
|
||||
|
||||
// GetUpdateMode returns the updatePolicy.updateMode for a given VPA.
|
||||
// If the mode is not specified it returns the default (UpdateModeAuto).
|
||||
func GetUpdateMode(vpa *vpa_types.VerticalPodAutoscaler) vpa_types.UpdateMode {
|
||||
|
|
|
|||
Loading…
Reference in New Issue