diff --git a/client/client.go b/client/client.go index f920383..89655e7 100644 --- a/client/client.go +++ b/client/client.go @@ -12,6 +12,7 @@ import ( "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" unstructuredv1 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" vpav1 "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/apis/autoscaling.k8s.io/v1" @@ -19,6 +20,7 @@ import ( "k8s.io/client-go/discovery" "k8s.io/client-go/dynamic" corev1client "k8s.io/client-go/kubernetes/typed/core/v1" + "k8s.io/client-go/scale" "k8s.io/client-go/tools/pager" ) @@ -44,6 +46,7 @@ type Interface interface { ListVPAResources(context.Context, ListOptions) ([]*vpav1.VerticalPodAutoscaler, error) GetVPATarget(context.Context, *autoscalingv1.CrossVersionObjectReference, string) (*unstructuredv1.Unstructured, error) ListDependentPods(ctx context.Context, targetMeta metav1.ObjectMeta, labelSelector string) ([]*corev1.Pod, error) + GetLabelSelectorFromResource(groupKind schema.GroupKind, namespace, name string) (labels.Selector, error) } var _ Interface = (*client)(nil) @@ -56,6 +59,7 @@ type client struct { discoveryClient discovery.DiscoveryInterface coreClient *corev1client.CoreV1Client mapper meta.RESTMapper + scaleNamespacer scale.ScalesGetter // lock during lazy init of the client sync.Mutex @@ -222,6 +226,33 @@ func (c *client) ListDependentPods(ctx context.Context, targetMeta metav1.Object return pods, nil } +func (c *client) GetLabelSelectorFromResource(groupKind schema.GroupKind, namespace, name string) (labels.Selector, error) { + mappings, err := c.mapper.RESTMappings(groupKind) + if err != nil { + return nil, err + } + + var lastError error + for _, mapping := range mappings { + groupResource := mapping.Resource.GroupResource() + scale, err := c.scaleNamespacer.Scales(namespace).Get(context.TODO(), groupResource, name, metav1.GetOptions{}) + if err == nil { + if scale.Status.Selector == "" { + return nil, fmt.Errorf("Resource %s/%s has an empty selector for scale sub-resource", namespace, name) + } + selector, err := labels.Parse(scale.Status.Selector) + if err != nil { + return nil, err + } + return selector, nil + } + lastError = err + } + + // nothing found, apparently the resource does not support scale (or we lack RBAC) + return nil, lastError +} + // referenceMatchController returns whether the given // OwnerReference matches a target controller. func referenceMatchController(ref metav1.OwnerReference, targetMeta metav1.ObjectMeta) bool { diff --git a/client/flags.go b/client/flags.go index 1a8ef51..ec5590a 100644 --- a/client/flags.go +++ b/client/flags.go @@ -7,7 +7,9 @@ import ( "github.com/spf13/pflag" "k8s.io/cli-runtime/pkg/genericclioptions" "k8s.io/client-go/dynamic" + kube_client "k8s.io/client-go/kubernetes" corev1client "k8s.io/client-go/kubernetes/typed/core/v1" + "k8s.io/client-go/scale" "k8s.io/kubectl/pkg/cmd/get" cmdutil "k8s.io/kubectl/pkg/cmd/util" "k8s.io/kubectl/pkg/util" @@ -107,12 +109,19 @@ func (f *Flags) NewClient() (Interface, error) { if err != nil { return nil, err } + r := scale.NewDiscoveryScaleKindResolver(dis) + + kubeClient := kube_client.NewForConfigOrDie(config) + restClient := kubeClient.CoreV1().RESTClient() + scaleNamespacer := scale.New(restClient, m, dynamic.LegacyAPIPathResolverFunc, r) + c := &client{ flags: f, dynamicClient: dyn, discoveryClient: dis, coreClient: pc, mapper: m, + scaleNamespacer: scaleNamespacer, } return c, nil } diff --git a/vpa/target.go b/vpa/target.go index d9b2925..4c65bc4 100644 --- a/vpa/target.go +++ b/vpa/target.go @@ -11,6 +11,7 @@ import ( "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" unstructuredv1 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" @@ -53,35 +54,60 @@ func NewTargetController(c client.Interface, ref *autoscalingv1.CrossVersionObje } kind := obj.GetKind() + isWellKnown := false + switch wellKnownControllerKind(kind) { case cj, ds, deploy, job, rs, rc, sts, ro: + isWellKnown = true case node: // Some pods specify nodes as their owners, // but they aren't valid controllers that // the VPA supports, so we just skip them. return nil, fmt.Errorf("node is not a valid target") default: - return nil, fmt.Errorf("unsupported target kind: %s", kind) - } - tc := &TargetController{ - Name: obj.GetName(), - Namespace: obj.GetNamespace(), - GroupVersionKind: obj.GetObjectKind().GroupVersionKind(), - controllerKind: wellKnownControllerKind(kind), - controllerObj: obj, - } - tc.podSpec, err = resolvePodSpec(obj) - if err != nil { - return nil, err + isWellKnown = false } - labelSelector, err := resolveLabelSelector(obj) - if err != nil { - return nil, err - } - selector, err := metav1.LabelSelectorAsSelector(labelSelector) - if err != nil { - return nil, err + var tc *TargetController + var selector labels.Selector + + if isWellKnown { + tc = &TargetController{ + Name: obj.GetName(), + Namespace: obj.GetNamespace(), + GroupVersionKind: obj.GetObjectKind().GroupVersionKind(), + controllerKind: wellKnownControllerKind(kind), + controllerObj: obj, + } + tc.podSpec, err = resolvePodSpec(obj) + if err != nil { + return nil, err + } + + labelSelector, err := resolveLabelSelector(obj) + if err != nil { + return nil, err + } + + selector, err = metav1.LabelSelectorAsSelector(labelSelector) + if err != nil { + return nil, err + } + + } else { + tc = &TargetController{ + Name: obj.GetName(), + Namespace: obj.GetNamespace(), + GroupVersionKind: obj.GetObjectKind().GroupVersionKind(), + controllerKind: wellKnownControllerKind(kind), + controllerObj: obj, + } + selector, err = c.GetLabelSelectorFromResource(tc.GroupVersionKind.GroupKind(), tc.Namespace, tc.Name) + + if err != nil { + return nil, err + } } + // The PodSpec template defined by a controller might not represent // the final spec of the pods. For example, a LimitRanger controller // could change the spec of pods to set default resource requests and @@ -104,7 +130,7 @@ func NewTargetController(c client.Interface, ref *autoscalingv1.CrossVersionObje } if len(pods) != 0 { p := pods[0] - if !reflect.DeepEqual(p.Spec.Containers, tc.podSpec.Containers) { + if tc.podSpec == nil || !reflect.DeepEqual(p.Spec.Containers, tc.podSpec.Containers) { tc.podSpec = &p.Spec } }