Skip to content
This repository was archived by the owner on Sep 17, 2025. It is now read-only.

Commit edbc6c0

Browse files
authored
Add support for k8s Resource metrics as source of scaling (#7)
2 parents c06dd0a + 34f2cea commit edbc6c0

15 files changed

+565
-24
lines changed

api/v1alpha1/kratos_types.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,9 @@ type ResourceMetricSource struct {
134134
Name v1.ResourceName `json:"name" protobuf:"bytes,1,name=name"`
135135
// target specifies the target value for the given metric
136136
Target MetricTarget `json:"target" protobuf:"bytes,2,name=target"`
137+
// container is the name of the container in the pods of the scaling target.
138+
// +optional
139+
Container string `json:"container" protobuf:"bytes,3,opt,name=container"`
137140
}
138141

139142
// PodsMetricSource indicates how to scale on a metric describing each pod in

config/crd/scaling.core.adobe.com_kratos.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,9 @@ spec:
397397
resource:
398398
description: resource refers to a resource metric (such as those specified in requests and limits) known to Kubernetes describing each pod in the current scale target (e.g. CPU or memory). Such metrics are built in to Kubernetes, and have special scaling options on top of those available to normal per-pod metrics using the "pods" source.
399399
properties:
400+
container:
401+
description: container is the name of the container in the pods of the scaling target.
402+
type: string
400403
name:
401404
description: name is the name of the resource in question.
402405
type: string
File renamed without changes.

examples/resource-scaler.yaml

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
---
2+
apiVersion: v1
3+
kind: ConfigMap
4+
metadata:
5+
name: kratos-resource-example
6+
data:
7+
kratosSpec: |-
8+
algorithm:
9+
type: hpa
10+
minReplicas: 1
11+
maxReplicas: 4
12+
stabilizationWindowSeconds: 30
13+
target:
14+
apiVersion: apps/v1
15+
kind: Deployment
16+
name: nginx
17+
metrics:
18+
- type: Resource
19+
resource:
20+
name: cpu
21+
target:
22+
type: AverageValue # AverageValue/Utilization/Value
23+
averageValue: 10
24+
- type: Resource
25+
resource:
26+
name: memory
27+
container: nginx
28+
target:
29+
type: AverageValue # AverageValue/Utilization/Value
30+
averageValue: 100
31+
# averageUtilization
32+
# value

go.mod

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ require (
1717
k8s.io/api v0.18.6
1818
k8s.io/apimachinery v0.18.6
1919
k8s.io/client-go v0.18.6
20+
k8s.io/kubernetes v1.13.0
21+
k8s.io/metrics v0.18.4
2022
sigs.k8s.io/controller-runtime v0.6.2
2123
sigs.k8s.io/controller-tools v0.4.0 // indirect
2224
sigs.k8s.io/yaml v1.2.0

go.sum

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -929,6 +929,8 @@ k8s.io/client-go v0.18.2/go.mod h1:Xcm5wVGXX9HAA2JJ2sSBUn3tCJ+4SVlCbl2MNNv+CIU=
929929
k8s.io/client-go v0.18.4/go.mod h1:f5sXwL4yAZRkAtzOxRWUhA/N8XzGCb+nPZI8PfobZ9g=
930930
k8s.io/client-go v0.18.6 h1:I+oWqJbibLSGsZj8Xs8F0aWVXJVIoUHWaaJV3kUN/Zw=
931931
k8s.io/client-go v0.18.6/go.mod h1:/fwtGLjYMS1MaM5oi+eXhKwG+1UHidUEXRh6cNsdO0Q=
932+
k8s.io/client-go v1.5.1 h1:XaX/lo2/u3/pmFau8HN+sB5C/b4dc4Dmm2eXjBH4p1E=
933+
k8s.io/client-go v11.0.0+incompatible h1:LBbX2+lOwY9flffWlJM7f1Ct8V2SRNiMRDFeiwnJo9o=
932934
k8s.io/code-generator v0.18.2/go.mod h1:+UHX5rSbxmR8kzS+FAv7um6dtYrZokQvjHpDSYRVkTc=
933935
k8s.io/code-generator v0.18.4/go.mod h1:TgNEVx9hCyPGpdtCWA34olQYLkh3ok9ar7XfSsr8b6c=
934936
k8s.io/code-generator v0.18.6/go.mod h1:TgNEVx9hCyPGpdtCWA34olQYLkh3ok9ar7XfSsr8b6c=
@@ -949,7 +951,9 @@ k8s.io/kube-openapi v0.0.0-20200410145947-61e04a5be9a6 h1:Oh3Mzx5pJ+yIumsAD0MOEC
949951
k8s.io/kube-openapi v0.0.0-20200410145947-61e04a5be9a6/go.mod h1:GRQhZsXIAJ1xR0C9bd8UpWHZ5plfAS9fzPjJuQ6JL3E=
950952
k8s.io/kubectl v0.18.4 h1:l9DUYPTEMs1+qNtoqPpTyaJOosvj7l7tQqphCO1K52s=
951953
k8s.io/kubectl v0.18.4/go.mod h1:EzB+nfeUWk6fm6giXQ8P4Fayw3dsN+M7Wjy23mTRtB0=
954+
k8s.io/kubernetes v1.13.0 h1:qTfB+u5M92k2fCCCVP2iuhgwwSOv1EkAkvQY1tQODD8=
952955
k8s.io/kubernetes v1.13.0/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk=
956+
k8s.io/metrics v0.18.4 h1:iP0U2VhD1BHXIv98OrkFKb19ItRQZLhy834jySbltzI=
953957
k8s.io/metrics v0.18.4/go.mod h1:luze4fyI9JG4eLDZy0kFdYEebqNfi0QrG4xNEbPkHOs=
954958
k8s.io/utils v0.0.0-20200324210504-a9aa75ae1b89/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew=
955959
k8s.io/utils v0.0.0-20200603063816-c1c6865ac451 h1:v8ud2Up6QK1lNOKFgiIVrZdMg7MpmSnvtrOieolJKoE=

metrics/metrics_fetcher.go

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,32 +16,50 @@ package metrics
1616
import (
1717
"errors"
1818
"fmt"
19+
"time"
1920

21+
"k8s.io/apimachinery/pkg/labels"
22+
23+
"github.com/adobe/kratos/api/common"
2024
"github.com/adobe/kratos/api/v1alpha1"
25+
metrics "k8s.io/metrics/pkg/client/clientset/versioned"
26+
)
27+
28+
const (
29+
defaultCacheTtl = time.Minute * 5
30+
defaultCallTimeout = time.Second * 10
2131
)
2232

2333
type MetricValue struct {
2434
Value int64
2535
}
2636

2737
type MetricsFetcher interface {
28-
Fetch(scaleMetric *v1alpha1.ScaleMetric) ([]MetricValue, error)
38+
Fetch(scaleMetric *v1alpha1.ScaleMetric, namespace string, selector labels.Selector) ([]MetricValue, error)
2939
}
3040

3141
type MetricsFactory struct {
3242
prometheusFetcher MetricsFetcher
43+
resourceFetcher MetricsFetcher
3344
}
3445

35-
func NewMetricsFactory(defaultPrometheusUrl string) *MetricsFactory {
46+
func NewMetricsFactory(params *common.KratosParameters) *MetricsFactory {
47+
mc, err := metrics.NewForConfig(params.ClientConfig)
48+
if err != nil {
49+
panic(err.Error())
50+
}
3651
return &MetricsFactory{
37-
prometheusFetcher: newPrometheusMetricsFetcher(defaultPrometheusUrl),
52+
prometheusFetcher: newPrometheusMetricsFetcher(params.DefaultPrometheusUrl),
53+
resourceFetcher: newResourceMetricsFetcher(mc),
3854
}
3955
}
4056

4157
func (facade *MetricsFactory) GetMetricsFetcher(scaleMetric *v1alpha1.ScaleMetric) (MetricsFetcher, error) {
4258
switch scaleMetric.Type {
4359
case v1alpha1.PrometheusScaleMetricType:
4460
return facade.prometheusFetcher, nil
61+
case v1alpha1.ResourceScaleMetricType:
62+
return facade.resourceFetcher, nil
4563
default:
4664
return nil, errors.New(fmt.Sprintf("Unknown metric type %s \n", scaleMetric.Type))
4765
}

metrics/metrics_fetcher_test.go

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,10 @@ import (
2020
)
2121

2222
var _ = Describe("MetricsFactory", func() {
23+
2324
It("Unknown fetcher type", func() {
24-
metricsFactory := NewMetricsFactory("testUrl")
25+
26+
metricsFactory := NewMetricsFactory(fakeKratosSpec)
2527
scaleMetric := &v1alpha1.ScaleMetric{
2628
Type: "testType",
2729
}
@@ -31,7 +33,7 @@ var _ = Describe("MetricsFactory", func() {
3133
})
3234

3335
It("Prometheus fetcher type", func() {
34-
metricsFactory := NewMetricsFactory("testUrl")
36+
metricsFactory := NewMetricsFactory(fakeKratosSpec)
3537
scaleMetric := &v1alpha1.ScaleMetric{
3638
Type: v1alpha1.PrometheusScaleMetricType,
3739
}
@@ -40,4 +42,15 @@ var _ = Describe("MetricsFactory", func() {
4042
Expect(err).To(BeNil(), "no error for supported metrics fetcher type")
4143
Expect(fetcher).NotTo(BeNil())
4244
})
45+
46+
It("Resource fetcher type", func() {
47+
metricsFactory := NewMetricsFactory(fakeKratosSpec)
48+
scaleMetric := &v1alpha1.ScaleMetric{
49+
Type: v1alpha1.ResourceScaleMetricType,
50+
}
51+
fetcher, err := metricsFactory.GetMetricsFetcher(scaleMetric)
52+
53+
Expect(err).To(BeNil(), "no error for supported metrics fetcher type")
54+
Expect(fetcher).NotTo(BeNil())
55+
})
4356
})

metrics/metrics_suite_test.go

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,32 @@ written permission of Adobe.
1414
package metrics
1515

1616
import (
17+
"path/filepath"
18+
"testing"
19+
1720
. "github.com/onsi/ginkgo"
1821
. "github.com/onsi/gomega"
22+
"k8s.io/client-go/kubernetes/scheme"
23+
"k8s.io/client-go/rest"
24+
"sigs.k8s.io/controller-runtime/pkg/client"
25+
"sigs.k8s.io/controller-runtime/pkg/envtest"
1926
"sigs.k8s.io/controller-runtime/pkg/envtest/printer"
20-
"testing"
27+
logf "sigs.k8s.io/controller-runtime/pkg/log"
28+
"sigs.k8s.io/controller-runtime/pkg/log/zap"
29+
30+
"github.com/adobe/kratos/api/common"
31+
scalingv1alpha1 "github.com/adobe/kratos/api/v1alpha1"
32+
// +kubebuilder:scaffold:imports
2133
)
2234

35+
// These tests use Ginkgo (BDD-style Go testing framework). Refer to
36+
// http://onsi.github.io/ginkgo/ to learn more about Ginkgo.
37+
38+
var cfg *rest.Config
39+
var k8sClient client.Client
40+
var testEnv *envtest.Environment
41+
var fakeKratosSpec *common.KratosParameters
42+
2343
func TestMetrics(t *testing.T) {
2444
RegisterFailHandler(Fail)
2545

@@ -29,9 +49,35 @@ func TestMetrics(t *testing.T) {
2949
}
3050

3151
var _ = BeforeSuite(func(done Done) {
52+
logf.SetLogger(zap.LoggerTo(GinkgoWriter, true))
53+
54+
By("bootstrapping test environment")
55+
testEnv = &envtest.Environment{
56+
CRDDirectoryPaths: []string{filepath.Join("..", "config", "crd", "bases")},
57+
}
58+
59+
var err error
60+
cfg, err = testEnv.Start()
61+
Expect(err).ToNot(HaveOccurred())
62+
Expect(cfg).ToNot(BeNil())
63+
64+
err = scalingv1alpha1.AddToScheme(scheme.Scheme)
65+
Expect(err).NotTo(HaveOccurred())
66+
67+
// +kubebuilder:scaffold:scheme
68+
69+
k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme})
70+
Expect(err).ToNot(HaveOccurred())
71+
Expect(k8sClient).ToNot(BeNil())
72+
fakeKratosSpec = &common.KratosParameters{
73+
ClientConfig: cfg,
74+
Client: k8sClient,
75+
}
3276
close(done)
3377
}, 60)
3478

3579
var _ = AfterSuite(func() {
36-
80+
By("tearing down the test environment")
81+
err := testEnv.Stop()
82+
Expect(err).ToNot(HaveOccurred())
3783
})

metrics/prometheus_fetcher.go

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,16 +24,12 @@ import (
2424
"github.com/adobe/kratos/cache"
2525
"github.com/go-logr/logr"
2626
"github.com/prometheus/client_golang/api"
27-
"github.com/prometheus/client_golang/api/prometheus/v1"
27+
v1 "github.com/prometheus/client_golang/api/prometheus/v1"
2828
"github.com/prometheus/common/model"
29+
"k8s.io/apimachinery/pkg/labels"
2930
"sigs.k8s.io/controller-runtime/pkg/log"
3031
)
3132

32-
const (
33-
defaultCacheTtl = time.Minute * 5
34-
defaultCallTimeout = time.Second * 10
35-
)
36-
3733
type prometheusMetricsFetcher struct {
3834
defaultUrl string
3935
prometheusClientsCache *cache.TTLCache
@@ -58,7 +54,7 @@ func newPrometheusMetricsFetcher(defaultUrl string) *prometheusMetricsFetcher {
5854
return fetcher
5955
}
6056

61-
func (p *prometheusMetricsFetcher) Fetch(scaleMetric *v1alpha1.ScaleMetric) ([]MetricValue, error) {
57+
func (p *prometheusMetricsFetcher) Fetch(scaleMetric *v1alpha1.ScaleMetric, namespace string, selector labels.Selector) ([]MetricValue, error) {
6258
client, err := p.getOrCreateClient(scaleMetric.Prometheus.PrometheusEndpoint)
6359

6460
if err != nil {

0 commit comments

Comments
 (0)