From 39177e80244739799ec6eebd0d82d684113585ce Mon Sep 17 00:00:00 2001 From: weilerN Date: Tue, 6 Jan 2026 10:56:14 +0200 Subject: [PATCH 1/8] Add Metrics client interface --- pkg/scalertypes/types.go | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/pkg/scalertypes/types.go b/pkg/scalertypes/types.go index e92a5e19..db16d739 100644 --- a/pkg/scalertypes/types.go +++ b/pkg/scalertypes/types.go @@ -33,10 +33,23 @@ import ( "k8s.io/client-go/kubernetes" ) +type Kind string + +const ( + KindCustomMetrics = "customMetrics" +) + +type MetricClientOptions struct { + Kind Kind + URL string + ServicesMetricNames []string +} + type AutoScalerOptions struct { - Namespace string - ScaleInterval Duration - GroupKind schema.GroupKind + Namespace string + ScaleInterval Duration + GroupKind schema.GroupKind + MetricClientOptions MetricClientOptions } type ResourceScalerConfig struct { @@ -199,3 +212,7 @@ func shortDurationString(d Duration) string { } return s } + +type MetricsClient interface { + GetResourceMetrics(metricNames []string) (map[string]map[string]int, error) +} From 1a8130189ac0bfd35e29fe95555aff40bacf883f Mon Sep 17 00:00:00 2001 From: weilerN Date: Tue, 6 Jan 2026 10:57:09 +0200 Subject: [PATCH 2/8] Refactor custom metrics client --- .../metricsclients/custommetricswrapper.go | 109 ++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 pkg/autoscaler/metricsclients/custommetricswrapper.go diff --git a/pkg/autoscaler/metricsclients/custommetricswrapper.go b/pkg/autoscaler/metricsclients/custommetricswrapper.go new file mode 100644 index 00000000..e7dfb6af --- /dev/null +++ b/pkg/autoscaler/metricsclients/custommetricswrapper.go @@ -0,0 +1,109 @@ +/* +Copyright 2026 Iguazio Systems Ltd. + +Licensed under the Apache License, Version 2.0 (the "License") with +an addition restriction as set forth herein. 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. + +In addition, you may not use the software for any purposes that are +illegal under applicable law, and the grant of the foregoing license +under the Apache 2.0 license is conditioned upon your compliance with +such restriction. +*/ + +package metricsclients + +import ( + "github.com/nuclio/errors" + "github.com/nuclio/logger" + k8serrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/client-go/discovery" + "k8s.io/client-go/discovery/cached/memory" + "k8s.io/client-go/rest" + "k8s.io/client-go/restmapper" + "k8s.io/metrics/pkg/client/custom_metrics" +) + +type CustomMetricsWrapper struct { + custom_metrics.CustomMetricsClient + namespace string + groupKind schema.GroupKind + logger logger.Logger +} + +// NewCustomMetricsClientFromConfig creates a custom_metrics.CustomMetricsClient from rest.Config +func NewCustomMetricsClientFromConfig(restConfig *rest.Config) (custom_metrics.CustomMetricsClient, error) { + discoveryClient, err := discovery.NewDiscoveryClientForConfig(restConfig) + if err != nil { + return nil, errors.Wrap(err, "Failed to create discovery client") + } + availableAPIsGetter := custom_metrics.NewAvailableAPIsGetter(discoveryClient) + restMapper := restmapper.NewDeferredDiscoveryRESTMapper(memory.NewMemCacheClient(discoveryClient)) + customMetricsClient := custom_metrics.NewForConfig(restConfig, restMapper, availableAPIsGetter) + return customMetricsClient, nil +} + +func NewCustomMetricsWrapper( + logger logger.Logger, + customMetricsClient custom_metrics.CustomMetricsClient, + namespace string, + groupKind schema.GroupKind) *CustomMetricsWrapper { + return &CustomMetricsWrapper{ + logger: logger, + CustomMetricsClient: customMetricsClient, + namespace: namespace, + groupKind: groupKind, + } +} + +func (cmw *CustomMetricsWrapper) GetResourceMetrics(metricNames []string) (map[string]map[string]int, error) { + resourcesMetricsMap := make(map[string]map[string]int) + resourceLabels := labels.Everything() + metricSelectorLabels := labels.Everything() + metricsClient := cmw.NamespacedMetrics(cmw.namespace) + + for _, metricName := range metricNames { + // getting the metric values for all object of schema group kind (e.g. deployment) + metrics, err := metricsClient.GetForObjects(cmw.groupKind, resourceLabels, metricName, metricSelectorLabels) + if err != nil { + // No data points have been submitted yet; this is expected—proceed to the next metric. + if k8serrors.IsNotFound(err) { + continue + } + return nil, errors.Wrap(err, "Failed to get custom metrics") + } + + // fill the resourcesMetricsMap with the metrics data we got + for _, item := range metrics.Items { + resourceName := item.DescribedObject.Name + value := int(item.Value.MilliValue()) + + cmw.logger.DebugWith("Got metric entry", + "resourceName", resourceName, + "metricName", metricName, + "value", value) + + if _, found := resourcesMetricsMap[resourceName]; !found { + resourcesMetricsMap[resourceName] = make(map[string]int) + } + + // sanity + if _, found := resourcesMetricsMap[resourceName][metricName]; found { + return nil, errors.New("Can not have more than one metric value per resource") + } + + resourcesMetricsMap[resourceName][metricName] = value + } + } + + return resourcesMetricsMap, nil +} From c8e45f0c7a87fb7702db1a00dda7147fc1aba672 Mon Sep 17 00:00:00 2001 From: weilerN Date: Tue, 6 Jan 2026 10:58:08 +0200 Subject: [PATCH 3/8] Refactor metrics client param in Autoscaler struct --- pkg/autoscaler/autoscaler.go | 57 +++--------------------------------- 1 file changed, 4 insertions(+), 53 deletions(-) diff --git a/pkg/autoscaler/autoscaler.go b/pkg/autoscaler/autoscaler.go index 77288563..30a30ddb 100644 --- a/pkg/autoscaler/autoscaler.go +++ b/pkg/autoscaler/autoscaler.go @@ -28,10 +28,7 @@ import ( "github.com/nuclio/errors" "github.com/nuclio/logger" - k8serrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/metrics/pkg/client/custom_metrics" ) type Autoscaler struct { @@ -41,13 +38,13 @@ type Autoscaler struct { scaleInterval scalertypes.Duration inScaleToZeroProcessMap map[string]bool groupKind schema.GroupKind - customMetricsClientSet custom_metrics.CustomMetricsClient + metricsClient scalertypes.MetricsClient ticker *time.Ticker } func NewAutoScaler(parentLogger logger.Logger, resourceScaler scalertypes.ResourceScaler, - customMetricsClientSet custom_metrics.CustomMetricsClient, + metricsClient scalertypes.MetricsClient, options scalertypes.AutoScalerOptions) (*Autoscaler, error) { childLogger := parentLogger.GetChild("autoscaler") childLogger.InfoWith("Creating Autoscaler", @@ -59,7 +56,7 @@ func NewAutoScaler(parentLogger logger.Logger, resourceScaler: resourceScaler, scaleInterval: options.ScaleInterval, groupKind: options.GroupKind, - customMetricsClientSet: customMetricsClientSet, + metricsClient: metricsClient, inScaleToZeroProcessMap: make(map[string]bool), }, nil } @@ -99,52 +96,6 @@ func (as *Autoscaler) getMetricNames(resources []scalertypes.Resource) []string return metricNames } -func (as *Autoscaler) getResourceMetrics(metricNames []string) (map[string]map[string]int, error) { - resourcesMetricsMap := make(map[string]map[string]int) - resourceLabels := labels.Everything() - metricSelectorLabels := labels.Everything() - metricsClient := as.customMetricsClientSet.NamespacedMetrics(as.namespace) - - for _, metricName := range metricNames { - - // getting the metric values for all object of schema group kind (e.g. deployment) - metrics, err := metricsClient.GetForObjects(as.groupKind, resourceLabels, metricName, metricSelectorLabels) - if err != nil { - - // if no data points submitted yet it's ok, continue to the next metric - if k8serrors.IsNotFound(err) { - continue - } - return nil, errors.Wrap(err, "Failed to get custom metrics") - } - - // fill the resourcesMetricsMap with the metrics data we got - for _, item := range metrics.Items { - - resourceName := item.DescribedObject.Name - value := int(item.Value.MilliValue()) - - as.logger.DebugWith("Got metric entry", - "resourceName", resourceName, - "metricName", metricName, - "value", value) - - if _, found := resourcesMetricsMap[resourceName]; !found { - resourcesMetricsMap[resourceName] = make(map[string]int) - } - - // sanity - if _, found := resourcesMetricsMap[resourceName][metricName]; found { - return nil, errors.New("Can not have more than one metric value per resource") - } - - resourcesMetricsMap[resourceName][metricName] = value - } - } - - return resourcesMetricsMap, nil -} - func (as *Autoscaler) checkResourceToScale(resource scalertypes.Resource, resourcesMetricsMap map[string]map[string]int) bool { if _, found := resourcesMetricsMap[resource.Name]; !found { as.logger.DebugWith("Resource does not have metrics data yet, keeping up", "resourceName", resource.Name) @@ -198,7 +149,7 @@ func (as *Autoscaler) checkResourcesToScale() error { } metricNames := as.getMetricNames(activeResources) as.logger.DebugWith("Got metric names", "metricNames", metricNames) - resourceMetricsMap, err := as.getResourceMetrics(metricNames) + resourceMetricsMap, err := as.metricsClient.GetResourceMetrics(metricNames) if err != nil { return errors.Wrap(err, "Failed to get resources metrics") } From 150c33b02dc539344e82586a627b867db771d4b4 Mon Sep 17 00:00:00 2001 From: weilerN Date: Tue, 6 Jan 2026 10:58:48 +0200 Subject: [PATCH 4/8] Use factory to create MetricsClient instance --- cmd/autoscaler/app/autoscaler.go | 18 ++++++------ pkg/autoscaler/factory/factory.go | 47 +++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 10 deletions(-) create mode 100644 pkg/autoscaler/factory/factory.go diff --git a/cmd/autoscaler/app/autoscaler.go b/cmd/autoscaler/app/autoscaler.go index 3da1990b..457e8511 100644 --- a/cmd/autoscaler/app/autoscaler.go +++ b/cmd/autoscaler/app/autoscaler.go @@ -25,6 +25,7 @@ import ( "time" "github.com/v3io/scaler/pkg/autoscaler" + "github.com/v3io/scaler/pkg/autoscaler/factory" "github.com/v3io/scaler/pkg/common" "github.com/v3io/scaler/pkg/pluginloader" "github.com/v3io/scaler/pkg/scalertypes" @@ -32,11 +33,7 @@ import ( "github.com/nuclio/errors" "github.com/nuclio/zap" "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/client-go/discovery" - "k8s.io/client-go/discovery/cached/memory" "k8s.io/client-go/rest" - "k8s.io/client-go/restmapper" - "k8s.io/metrics/pkg/client/custom_metrics" ) func Run(kubeconfigPath string, @@ -51,6 +48,9 @@ func Run(kubeconfigPath string, Kind: metricsResourceKind, Group: metricsResourceGroup, }, + MetricClientOptions: scalertypes.MetricClientOptions{ + Kind: scalertypes.KindCustomMetrics, + }, } pluginLoader, err := pluginloader.New() @@ -103,16 +103,14 @@ func createAutoScaler(restConfig *rest.Config, return nil, errors.Wrap(err, "Failed to initialize root logger") } - discoveryClient, err := discovery.NewDiscoveryClientForConfig(restConfig) + // create metrics client using factory + metricsClient, err := factory.NewMetricsClient(rootLogger, restConfig, options) if err != nil { - return nil, errors.Wrap(err, "Failed to create discovery client") + return nil, errors.Wrap(err, "Failed to create metrics client") } - availableAPIsGetter := custom_metrics.NewAvailableAPIsGetter(discoveryClient) - restMapper := restmapper.NewDeferredDiscoveryRESTMapper(memory.NewMemCacheClient(discoveryClient)) - customMetricsClient := custom_metrics.NewForConfig(restConfig, restMapper, availableAPIsGetter) // create auto scaler - newScaler, err := autoscaler.NewAutoScaler(rootLogger, resourceScaler, customMetricsClient, options) + newScaler, err := autoscaler.NewAutoScaler(rootLogger, resourceScaler, metricsClient, options) if err != nil { return nil, errors.Wrap(err, "Failed to create auto scaler") } diff --git a/pkg/autoscaler/factory/factory.go b/pkg/autoscaler/factory/factory.go new file mode 100644 index 00000000..1604f815 --- /dev/null +++ b/pkg/autoscaler/factory/factory.go @@ -0,0 +1,47 @@ +/* +Copyright 2026 Iguazio Systems Ltd. + +Licensed under the Apache License, Version 2.0 (the "License") with +an addition restriction as set forth herein. 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. + +In addition, you may not use the software for any purposes that are +illegal under applicable law, and the grant of the foregoing license +under the Apache 2.0 license is conditioned upon your compliance with +such restriction. +*/ + +package factory + +import ( + "github.com/v3io/scaler/pkg/autoscaler/metricsclients" + "github.com/v3io/scaler/pkg/scalertypes" + + "github.com/nuclio/errors" + "github.com/nuclio/logger" + "k8s.io/client-go/rest" +) + +func NewMetricsClient(logger logger.Logger, restConfig *rest.Config, autoScalerConf scalertypes.AutoScalerOptions) (scalertypes.MetricsClient, error) { + switch autoScalerConf.MetricClientOptions.Kind { + case scalertypes.KindCustomMetrics: + customMetricsClient, err := metricsclients.NewCustomMetricsClientFromConfig(restConfig) + if err != nil { + return nil, errors.Wrap(err, "Failed to create custom metrics client") + } + return metricsclients.NewCustomMetricsWrapper( + logger.GetChild("customMetricsClient"), + customMetricsClient, + autoScalerConf.Namespace, + autoScalerConf.GroupKind), nil + default: + return nil, errors.New("unsupported metrics client kind") + } +} From 2db6008f0a77e6dd6c4dacee124184a663a9c262 Mon Sep 17 00:00:00 2001 From: weilerN Date: Wed, 7 Jan 2026 11:39:20 +0200 Subject: [PATCH 5/8] Update metrics client options --- pkg/autoscaler/factory/factory.go | 4 +++- pkg/scalertypes/types.go | 6 +++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/pkg/autoscaler/factory/factory.go b/pkg/autoscaler/factory/factory.go index 1604f815..b60d38a5 100644 --- a/pkg/autoscaler/factory/factory.go +++ b/pkg/autoscaler/factory/factory.go @@ -21,6 +21,8 @@ such restriction. package factory import ( + "fmt" + "github.com/v3io/scaler/pkg/autoscaler/metricsclients" "github.com/v3io/scaler/pkg/scalertypes" @@ -42,6 +44,6 @@ func NewMetricsClient(logger logger.Logger, restConfig *rest.Config, autoScalerC autoScalerConf.Namespace, autoScalerConf.GroupKind), nil default: - return nil, errors.New("unsupported metrics client kind") + return nil, fmt.Errorf("unsupported metrics client kind: %s", autoScalerConf.MetricClientOptions.Kind) } } diff --git a/pkg/scalertypes/types.go b/pkg/scalertypes/types.go index db16d739..d6d119e3 100644 --- a/pkg/scalertypes/types.go +++ b/pkg/scalertypes/types.go @@ -40,9 +40,9 @@ const ( ) type MetricClientOptions struct { - Kind Kind - URL string - ServicesMetricNames []string + Kind Kind + URL string + Template string } type AutoScalerOptions struct { From 44b998c58ffe668826c63024860eb00f136da375 Mon Sep 17 00:00:00 2001 From: weilerN Date: Wed, 7 Jan 2026 11:56:54 +0200 Subject: [PATCH 6/8] Renamings --- cmd/autoscaler/app/autoscaler.go | 6 ++--- .../custommetrics.go} | 24 +++++++++---------- .../{factory => metricsclient}/factory.go | 15 ++++++------ pkg/scalertypes/types.go | 10 ++++---- 4 files changed, 28 insertions(+), 27 deletions(-) rename pkg/autoscaler/{metricsclients/custommetricswrapper.go => metricsclient/custommetrics.go} (83%) rename pkg/autoscaler/{factory => metricsclient}/factory.go (74%) diff --git a/cmd/autoscaler/app/autoscaler.go b/cmd/autoscaler/app/autoscaler.go index 457e8511..da8587d2 100644 --- a/cmd/autoscaler/app/autoscaler.go +++ b/cmd/autoscaler/app/autoscaler.go @@ -25,7 +25,7 @@ import ( "time" "github.com/v3io/scaler/pkg/autoscaler" - "github.com/v3io/scaler/pkg/autoscaler/factory" + "github.com/v3io/scaler/pkg/autoscaler/metricsclient" "github.com/v3io/scaler/pkg/common" "github.com/v3io/scaler/pkg/pluginloader" "github.com/v3io/scaler/pkg/scalertypes" @@ -48,7 +48,7 @@ func Run(kubeconfigPath string, Kind: metricsResourceKind, Group: metricsResourceGroup, }, - MetricClientOptions: scalertypes.MetricClientOptions{ + MetricsClientOptions: scalertypes.MetricsClientOptions{ Kind: scalertypes.KindCustomMetrics, }, } @@ -104,7 +104,7 @@ func createAutoScaler(restConfig *rest.Config, } // create metrics client using factory - metricsClient, err := factory.NewMetricsClient(rootLogger, restConfig, options) + metricsClient, err := metricsclient.NewMetricsClient(rootLogger, restConfig, options) if err != nil { return nil, errors.Wrap(err, "Failed to create metrics client") } diff --git a/pkg/autoscaler/metricsclients/custommetricswrapper.go b/pkg/autoscaler/metricsclient/custommetrics.go similarity index 83% rename from pkg/autoscaler/metricsclients/custommetricswrapper.go rename to pkg/autoscaler/metricsclient/custommetrics.go index e7dfb6af..f370ce86 100644 --- a/pkg/autoscaler/metricsclients/custommetricswrapper.go +++ b/pkg/autoscaler/metricsclient/custommetrics.go @@ -18,7 +18,7 @@ under the Apache 2.0 license is conditioned upon your compliance with such restriction. */ -package metricsclients +package metricsclient import ( "github.com/nuclio/errors" @@ -30,34 +30,34 @@ import ( "k8s.io/client-go/discovery/cached/memory" "k8s.io/client-go/rest" "k8s.io/client-go/restmapper" - "k8s.io/metrics/pkg/client/custom_metrics" + k8scustommetrics "k8s.io/metrics/pkg/client/custom_metrics" ) -type CustomMetricsWrapper struct { - custom_metrics.CustomMetricsClient +type CustomMetricsClient struct { + k8scustommetrics.CustomMetricsClient namespace string groupKind schema.GroupKind logger logger.Logger } // NewCustomMetricsClientFromConfig creates a custom_metrics.CustomMetricsClient from rest.Config -func NewCustomMetricsClientFromConfig(restConfig *rest.Config) (custom_metrics.CustomMetricsClient, error) { +func NewCustomMetricsClientFromConfig(restConfig *rest.Config) (k8scustommetrics.CustomMetricsClient, error) { discoveryClient, err := discovery.NewDiscoveryClientForConfig(restConfig) if err != nil { return nil, errors.Wrap(err, "Failed to create discovery client") } - availableAPIsGetter := custom_metrics.NewAvailableAPIsGetter(discoveryClient) + availableAPIsGetter := k8scustommetrics.NewAvailableAPIsGetter(discoveryClient) restMapper := restmapper.NewDeferredDiscoveryRESTMapper(memory.NewMemCacheClient(discoveryClient)) - customMetricsClient := custom_metrics.NewForConfig(restConfig, restMapper, availableAPIsGetter) + customMetricsClient := k8scustommetrics.NewForConfig(restConfig, restMapper, availableAPIsGetter) return customMetricsClient, nil } -func NewCustomMetricsWrapper( +func NewCustomMetricsClient( logger logger.Logger, - customMetricsClient custom_metrics.CustomMetricsClient, + customMetricsClient k8scustommetrics.CustomMetricsClient, namespace string, - groupKind schema.GroupKind) *CustomMetricsWrapper { - return &CustomMetricsWrapper{ + groupKind schema.GroupKind) *CustomMetricsClient { + return &CustomMetricsClient{ logger: logger, CustomMetricsClient: customMetricsClient, namespace: namespace, @@ -65,7 +65,7 @@ func NewCustomMetricsWrapper( } } -func (cmw *CustomMetricsWrapper) GetResourceMetrics(metricNames []string) (map[string]map[string]int, error) { +func (cmw *CustomMetricsClient) GetResourceMetrics(metricNames []string) (map[string]map[string]int, error) { resourcesMetricsMap := make(map[string]map[string]int) resourceLabels := labels.Everything() metricSelectorLabels := labels.Everything() diff --git a/pkg/autoscaler/factory/factory.go b/pkg/autoscaler/metricsclient/factory.go similarity index 74% rename from pkg/autoscaler/factory/factory.go rename to pkg/autoscaler/metricsclient/factory.go index b60d38a5..a08ef113 100644 --- a/pkg/autoscaler/factory/factory.go +++ b/pkg/autoscaler/metricsclient/factory.go @@ -18,12 +18,11 @@ under the Apache 2.0 license is conditioned upon your compliance with such restriction. */ -package factory +package metricsclient import ( "fmt" - "github.com/v3io/scaler/pkg/autoscaler/metricsclients" "github.com/v3io/scaler/pkg/scalertypes" "github.com/nuclio/errors" @@ -31,19 +30,21 @@ import ( "k8s.io/client-go/rest" ) -func NewMetricsClient(logger logger.Logger, restConfig *rest.Config, autoScalerConf scalertypes.AutoScalerOptions) (scalertypes.MetricsClient, error) { - switch autoScalerConf.MetricClientOptions.Kind { +func NewMetricsClient(logger logger.Logger, + restConfig *rest.Config, + autoScalerConf scalertypes.AutoScalerOptions) (scalertypes.MetricsClient, error) { + switch autoScalerConf.MetricsClientOptions.Kind { case scalertypes.KindCustomMetrics: - customMetricsClient, err := metricsclients.NewCustomMetricsClientFromConfig(restConfig) + customMetricsClient, err := NewCustomMetricsClientFromConfig(restConfig) if err != nil { return nil, errors.Wrap(err, "Failed to create custom metrics client") } - return metricsclients.NewCustomMetricsWrapper( + return NewCustomMetricsClient( logger.GetChild("customMetricsClient"), customMetricsClient, autoScalerConf.Namespace, autoScalerConf.GroupKind), nil default: - return nil, fmt.Errorf("unsupported metrics client kind: %s", autoScalerConf.MetricClientOptions.Kind) + return nil, fmt.Errorf("unsupported metrics client kind: %s", autoScalerConf.MetricsClientOptions.Kind) } } diff --git a/pkg/scalertypes/types.go b/pkg/scalertypes/types.go index d6d119e3..618d31a8 100644 --- a/pkg/scalertypes/types.go +++ b/pkg/scalertypes/types.go @@ -39,17 +39,17 @@ const ( KindCustomMetrics = "customMetrics" ) -type MetricClientOptions struct { +type MetricsClientOptions struct { Kind Kind URL string Template string } type AutoScalerOptions struct { - Namespace string - ScaleInterval Duration - GroupKind schema.GroupKind - MetricClientOptions MetricClientOptions + Namespace string + ScaleInterval Duration + GroupKind schema.GroupKind + MetricsClientOptions MetricsClientOptions } type ResourceScalerConfig struct { From ce5c46bc5d9146543835a8be8714275c22c9ccaf Mon Sep 17 00:00:00 2001 From: weilerN Date: Thu, 8 Jan 2026 15:14:56 +0200 Subject: [PATCH 7/8] CR1 - renamings and docstring --- cmd/autoscaler/app/autoscaler.go | 4 ++- pkg/autoscaler/metricsclient/factory.go | 21 ++++++++------ .../{custommetrics.go => k8s.go} | 28 ++++--------------- pkg/scalertypes/types.go | 27 ++++++++++++++---- 4 files changed, 44 insertions(+), 36 deletions(-) rename pkg/autoscaler/metricsclient/{custommetrics.go => k8s.go} (72%) diff --git a/cmd/autoscaler/app/autoscaler.go b/cmd/autoscaler/app/autoscaler.go index da8587d2..5c34c29e 100644 --- a/cmd/autoscaler/app/autoscaler.go +++ b/cmd/autoscaler/app/autoscaler.go @@ -41,6 +41,7 @@ func Run(kubeconfigPath string, scaleInterval time.Duration, metricsResourceKind string, metricsResourceGroup string) error { + // define default auto scaler options autoScalerOptions := scalertypes.AutoScalerOptions{ Namespace: namespace, ScaleInterval: scalertypes.Duration{Duration: scaleInterval}, @@ -49,7 +50,8 @@ func Run(kubeconfigPath string, Group: metricsResourceGroup, }, MetricsClientOptions: scalertypes.MetricsClientOptions{ - Kind: scalertypes.KindCustomMetrics, + // default to k8s metrics client for the sake of backwards compatibility + MetricsClientKind: scalertypes.KindK8sMetricsClient, }, } diff --git a/pkg/autoscaler/metricsclient/factory.go b/pkg/autoscaler/metricsclient/factory.go index a08ef113..ceecd58d 100644 --- a/pkg/autoscaler/metricsclient/factory.go +++ b/pkg/autoscaler/metricsclient/factory.go @@ -21,30 +21,35 @@ such restriction. package metricsclient import ( - "fmt" - "github.com/v3io/scaler/pkg/scalertypes" "github.com/nuclio/errors" "github.com/nuclio/logger" + "k8s.io/client-go/discovery" + "k8s.io/client-go/discovery/cached/memory" "k8s.io/client-go/rest" + "k8s.io/client-go/restmapper" + k8scustommetrics "k8s.io/metrics/pkg/client/custom_metrics" ) func NewMetricsClient(logger logger.Logger, restConfig *rest.Config, autoScalerConf scalertypes.AutoScalerOptions) (scalertypes.MetricsClient, error) { - switch autoScalerConf.MetricsClientOptions.Kind { - case scalertypes.KindCustomMetrics: - customMetricsClient, err := NewCustomMetricsClientFromConfig(restConfig) + switch autoScalerConf.MetricsClientOptions.MetricsClientKind { + case scalertypes.KindK8sMetricsClient: + discoveryClient, err := discovery.NewDiscoveryClientForConfig(restConfig) if err != nil { - return nil, errors.Wrap(err, "Failed to create custom metrics client") + return nil, errors.Wrap(err, "Failed to create k8s metrics client") } + availableAPIsGetter := k8scustommetrics.NewAvailableAPIsGetter(discoveryClient) + restMapper := restmapper.NewDeferredDiscoveryRESTMapper(memory.NewMemCacheClient(discoveryClient)) + customMetricsClient := k8scustommetrics.NewForConfig(restConfig, restMapper, availableAPIsGetter) return NewCustomMetricsClient( - logger.GetChild("customMetricsClient"), + logger, customMetricsClient, autoScalerConf.Namespace, autoScalerConf.GroupKind), nil default: - return nil, fmt.Errorf("unsupported metrics client kind: %s", autoScalerConf.MetricsClientOptions.Kind) + return nil, errors.Errorf("unsupported metrics client kind: %s", autoScalerConf.MetricsClientOptions.MetricsClientKind) } } diff --git a/pkg/autoscaler/metricsclient/custommetrics.go b/pkg/autoscaler/metricsclient/k8s.go similarity index 72% rename from pkg/autoscaler/metricsclient/custommetrics.go rename to pkg/autoscaler/metricsclient/k8s.go index f370ce86..e71d1463 100644 --- a/pkg/autoscaler/metricsclient/custommetrics.go +++ b/pkg/autoscaler/metricsclient/k8s.go @@ -26,46 +26,30 @@ import ( k8serrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/client-go/discovery" - "k8s.io/client-go/discovery/cached/memory" - "k8s.io/client-go/rest" - "k8s.io/client-go/restmapper" k8scustommetrics "k8s.io/metrics/pkg/client/custom_metrics" ) -type CustomMetricsClient struct { +type K8sCustomMetricsClient struct { k8scustommetrics.CustomMetricsClient namespace string groupKind schema.GroupKind logger logger.Logger } -// NewCustomMetricsClientFromConfig creates a custom_metrics.CustomMetricsClient from rest.Config -func NewCustomMetricsClientFromConfig(restConfig *rest.Config) (k8scustommetrics.CustomMetricsClient, error) { - discoveryClient, err := discovery.NewDiscoveryClientForConfig(restConfig) - if err != nil { - return nil, errors.Wrap(err, "Failed to create discovery client") - } - availableAPIsGetter := k8scustommetrics.NewAvailableAPIsGetter(discoveryClient) - restMapper := restmapper.NewDeferredDiscoveryRESTMapper(memory.NewMemCacheClient(discoveryClient)) - customMetricsClient := k8scustommetrics.NewForConfig(restConfig, restMapper, availableAPIsGetter) - return customMetricsClient, nil -} - func NewCustomMetricsClient( - logger logger.Logger, + parentLogger logger.Logger, customMetricsClient k8scustommetrics.CustomMetricsClient, namespace string, - groupKind schema.GroupKind) *CustomMetricsClient { - return &CustomMetricsClient{ - logger: logger, + groupKind schema.GroupKind) *K8sCustomMetricsClient { + return &K8sCustomMetricsClient{ + logger: parentLogger.GetChild("custom-metrics"), CustomMetricsClient: customMetricsClient, namespace: namespace, groupKind: groupKind, } } -func (cmw *CustomMetricsClient) GetResourceMetrics(metricNames []string) (map[string]map[string]int, error) { +func (cmw *K8sCustomMetricsClient) GetResourceMetrics(metricNames []string) (map[string]map[string]int, error) { resourcesMetricsMap := make(map[string]map[string]int) resourceLabels := labels.Everything() metricSelectorLabels := labels.Everything() diff --git a/pkg/scalertypes/types.go b/pkg/scalertypes/types.go index 618d31a8..7103a073 100644 --- a/pkg/scalertypes/types.go +++ b/pkg/scalertypes/types.go @@ -33,16 +33,16 @@ import ( "k8s.io/client-go/kubernetes" ) -type Kind string +type MetricsClientKind string const ( - KindCustomMetrics = "customMetrics" + KindK8sMetricsClient = "k8sMetricsClient" ) type MetricsClientOptions struct { - Kind Kind - URL string - Template string + MetricsClientKind MetricsClientKind + URL string + Template string } type AutoScalerOptions struct { @@ -213,6 +213,23 @@ func shortDurationString(d Duration) string { return s } +// MetricsClient defines an interface for retrieving resource metrics used by the autoscaler. type MetricsClient interface { + // GetResourceMetrics retrieves metrics for multiple resources and metric names. + // + // Parameters: + // - metricNames: A slice of metric names to retrieve (e.g., "requests_per_minute", "cpu_usage_per_hour") + // + // Returns: + // - map[string]map[string]int: A nested map structure where: + // * The outer map key is the resource name (e.g., deployment name) + // * The inner map key is the metric name + // * The inner map value is the metric value as an integer + // Example: map["my-deployment"]["requests_per_minute"] = 42 + // - error: An error if metric retrieval fails + // + // The dual map structure allows efficient lookup of metric values by resource name + // and then by metric name, enabling the autoscaler to check multiple metrics + // per resource when making scaling decisions. GetResourceMetrics(metricNames []string) (map[string]map[string]int, error) } From 22b5f892509b8cdbfe64f43feff461614f687e02 Mon Sep 17 00:00:00 2001 From: weilerN Date: Thu, 8 Jan 2026 16:28:03 +0200 Subject: [PATCH 8/8] CR2 - init custom metrics client in the proper location --- pkg/autoscaler/metricsclient/factory.go | 15 ++------------- pkg/autoscaler/metricsclient/k8s.go | 17 ++++++++++++++--- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/pkg/autoscaler/metricsclient/factory.go b/pkg/autoscaler/metricsclient/factory.go index ceecd58d..b2f610a4 100644 --- a/pkg/autoscaler/metricsclient/factory.go +++ b/pkg/autoscaler/metricsclient/factory.go @@ -25,11 +25,7 @@ import ( "github.com/nuclio/errors" "github.com/nuclio/logger" - "k8s.io/client-go/discovery" - "k8s.io/client-go/discovery/cached/memory" "k8s.io/client-go/rest" - "k8s.io/client-go/restmapper" - k8scustommetrics "k8s.io/metrics/pkg/client/custom_metrics" ) func NewMetricsClient(logger logger.Logger, @@ -37,18 +33,11 @@ func NewMetricsClient(logger logger.Logger, autoScalerConf scalertypes.AutoScalerOptions) (scalertypes.MetricsClient, error) { switch autoScalerConf.MetricsClientOptions.MetricsClientKind { case scalertypes.KindK8sMetricsClient: - discoveryClient, err := discovery.NewDiscoveryClientForConfig(restConfig) - if err != nil { - return nil, errors.Wrap(err, "Failed to create k8s metrics client") - } - availableAPIsGetter := k8scustommetrics.NewAvailableAPIsGetter(discoveryClient) - restMapper := restmapper.NewDeferredDiscoveryRESTMapper(memory.NewMemCacheClient(discoveryClient)) - customMetricsClient := k8scustommetrics.NewForConfig(restConfig, restMapper, availableAPIsGetter) return NewCustomMetricsClient( logger, - customMetricsClient, + restConfig, autoScalerConf.Namespace, - autoScalerConf.GroupKind), nil + autoScalerConf.GroupKind) default: return nil, errors.Errorf("unsupported metrics client kind: %s", autoScalerConf.MetricsClientOptions.MetricsClientKind) } diff --git a/pkg/autoscaler/metricsclient/k8s.go b/pkg/autoscaler/metricsclient/k8s.go index e71d1463..7aa68396 100644 --- a/pkg/autoscaler/metricsclient/k8s.go +++ b/pkg/autoscaler/metricsclient/k8s.go @@ -26,6 +26,10 @@ import ( k8serrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/client-go/discovery" + "k8s.io/client-go/discovery/cached/memory" + "k8s.io/client-go/rest" + "k8s.io/client-go/restmapper" k8scustommetrics "k8s.io/metrics/pkg/client/custom_metrics" ) @@ -38,15 +42,22 @@ type K8sCustomMetricsClient struct { func NewCustomMetricsClient( parentLogger logger.Logger, - customMetricsClient k8scustommetrics.CustomMetricsClient, + restConfig *rest.Config, namespace string, - groupKind schema.GroupKind) *K8sCustomMetricsClient { + groupKind schema.GroupKind) (*K8sCustomMetricsClient, error) { + discoveryClient, err := discovery.NewDiscoveryClientForConfig(restConfig) + if err != nil { + return nil, errors.Wrap(err, "Failed to create k8s metrics client") + } + availableAPIsGetter := k8scustommetrics.NewAvailableAPIsGetter(discoveryClient) + restMapper := restmapper.NewDeferredDiscoveryRESTMapper(memory.NewMemCacheClient(discoveryClient)) + customMetricsClient := k8scustommetrics.NewForConfig(restConfig, restMapper, availableAPIsGetter) return &K8sCustomMetricsClient{ logger: parentLogger.GetChild("custom-metrics"), CustomMetricsClient: customMetricsClient, namespace: namespace, groupKind: groupKind, - } + }, nil } func (cmw *K8sCustomMetricsClient) GetResourceMetrics(metricNames []string) (map[string]map[string]int, error) {