Add support for averageValue for request-per-second Skipper metric

This adds support for `averageValue` for the `request-per-second` metric
based on Ingress Objects. This is only supported from Kubernetes
`>=v1.14` (https://github.com/kubernetes/kubernetes/pull/72872).

When defining the HPA with `autoscaling/v2beta1` you still need to
define `targetValue` even though it won't be used when `averageValue` is
set. Once we default to `autoscaling/v2beta2` this akward API will be
gone.

Signed-off-by: Mikkel Oscar Lyderik Larsen <mikkel.larsen@zalando.de>
This commit is contained in:
Mikkel Oscar Lyderik Larsen
2019-07-27 10:42:46 +02:00
parent 0de5042d3d
commit 76d2f74743
5 changed files with 125 additions and 39 deletions
+2
View File
@@ -165,6 +165,7 @@ type MetricConfig struct {
ObjectReference custom_metrics.ObjectReference
PerReplica bool
Interval time.Duration
MetricSpec autoscalingv2.MetricSpec
}
// ParseHPAMetrics parses the HPA object into a list of metric configurations.
@@ -206,6 +207,7 @@ func ParseHPAMetrics(hpa *autoscalingv2.HorizontalPodAutoscaler) ([]*MetricConfi
MetricTypeName: typeName,
ObjectReference: ref,
Config: map[string]string{},
MetricSpec: metric,
}
if metric.Type == autoscalingv2.ExternalMetricSourceType &&
+20 -17
View File
@@ -72,8 +72,7 @@ type SkipperCollector struct {
}
// NewSkipperCollector initializes a new SkipperCollector.
func NewSkipperCollector(client kubernetes.Interface, plugin CollectorPlugin, hpa *autoscalingv2.HorizontalPodAutoscaler,
config *MetricConfig, interval time.Duration, backendAnnotations []string, backend string) (*SkipperCollector, error) {
func NewSkipperCollector(client kubernetes.Interface, plugin CollectorPlugin, hpa *autoscalingv2.HorizontalPodAutoscaler, config *MetricConfig, interval time.Duration, backendAnnotations []string, backend string) (*SkipperCollector, error) {
return &SkipperCollector{
client: client,
objectReference: config.ObjectReference,
@@ -178,22 +177,26 @@ func (c *SkipperCollector) GetMetrics() ([]CollectedMetric, error) {
return nil, fmt.Errorf("expected to only get one metric value, got %d", len(values))
}
// get current replicas for the targeted scale object. This is used to
// calculate an average metric instead of total.
// targetAverageValue will be available in Kubernetes v1.12
// https://github.com/kubernetes/kubernetes/pull/64097
replicas, err := targetRefReplicas(c.client, c.hpa)
if err != nil {
return nil, err
}
if replicas < 1 {
return nil, fmt.Errorf("unable to get average value for %d replicas", replicas)
}
value := values[0]
avgValue := float64(value.Custom.Value.MilliValue()) / float64(replicas)
value.Custom.Value = *resource.NewMilliQuantity(int64(avgValue), resource.DecimalSI)
// For Kubernetes <v1.14 we have to fall back to manual average
if c.config.MetricSpec.Object.Target.AverageValue == nil {
// get current replicas for the targeted scale object. This is used to
// calculate an average metric instead of total.
// targetAverageValue will be available in Kubernetes v1.12
// https://github.com/kubernetes/kubernetes/pull/64097
replicas, err := targetRefReplicas(c.client, c.hpa)
if err != nil {
return nil, err
}
if replicas < 1 {
return nil, fmt.Errorf("unable to get average value for %d replicas", replicas)
}
avgValue := float64(value.Custom.Value.MilliValue()) / float64(replicas)
value.Custom.Value = *resource.NewMilliQuantity(int64(avgValue), resource.DecimalSI)
}
return []CollectedMetric{value}, nil
}
+61 -3
View File
@@ -107,6 +107,7 @@ func TestSkipperCollector(t *testing.T) {
ingressName string
collectedMetric int
expectError bool
fakedAverage bool
namespace string
backendWeights map[string]map[string]int
replicas int32
@@ -140,6 +141,7 @@ func TestSkipperCollector(t *testing.T) {
metrics: []int{100, 1500, 700},
ingressName: "dummy-ingress",
collectedMetric: 150,
fakedAverage: true,
namespace: "default",
backend: "backend1",
backendWeights: map[string]map[string]int{testBackendWeightsAnnotation: {"backend2": 50, "backend1": 50}},
@@ -147,6 +149,18 @@ func TestSkipperCollector(t *testing.T) {
readyReplicas: 5,
backendAnnotations: []string{testBackendWeightsAnnotation},
},
{
msg: "test multiple replicas not calculating average internally",
metrics: []int{100, 1500, 700},
ingressName: "dummy-ingress",
collectedMetric: 750, // 50% of 1500
namespace: "default",
backend: "backend1",
backendWeights: map[string]map[string]int{testBackendWeightsAnnotation: {"backend2": 50, "backend1": 50}},
replicas: 5, // this is not taken into account
readyReplicas: 5,
backendAnnotations: []string{testBackendWeightsAnnotation},
},
{
msg: "test zero weight backends",
metrics: []int{100, 1500, 700},
@@ -164,6 +178,22 @@ func TestSkipperCollector(t *testing.T) {
metrics: []int{100, 1500, 700},
ingressName: "dummy-ingress",
collectedMetric: 300,
fakedAverage: true,
namespace: "default",
backend: "backend1",
backendWeights: map[string]map[string]int{
testBackendWeightsAnnotation: {"backend2": 20, "backend1": 80},
testStacksetWeightsAnnotation: {"backend2": 0, "backend1": 100},
},
replicas: 5,
readyReplicas: 5,
backendAnnotations: []string{testBackendWeightsAnnotation, testStacksetWeightsAnnotation},
},
{
msg: "test multiple backend annotation not calculating average internally",
metrics: []int{100, 1500, 700},
ingressName: "dummy-ingress",
collectedMetric: 1500,
namespace: "default",
backend: "backend1",
backendWeights: map[string]map[string]int{
@@ -227,6 +257,22 @@ func TestSkipperCollector(t *testing.T) {
metrics: []int{100, 1500, 700},
ingressName: "dummy-ingress",
collectedMetric: 60,
fakedAverage: true,
namespace: "default",
backend: "backend2",
backendWeights: map[string]map[string]int{
testBackendWeightsAnnotation: {"backend2": 20, "backend1": 80},
testStacksetWeightsAnnotation: {"backend1": 100},
},
replicas: 5,
readyReplicas: 5,
backendAnnotations: []string{testBackendWeightsAnnotation, testStacksetWeightsAnnotation},
},
{
msg: "test partial backend annotations not calculating average internally",
metrics: []int{100, 1500, 700},
ingressName: "dummy-ingress",
collectedMetric: 300,
namespace: "default",
backend: "backend2",
backendWeights: map[string]map[string]int{
@@ -244,7 +290,7 @@ func TestSkipperCollector(t *testing.T) {
require.NoError(t, err)
plugin := makePlugin(tc.metrics)
hpa := makeHPA(tc.ingressName, tc.backend)
config := makeConfig(tc.backend)
config := makeConfig(tc.backend, tc.fakedAverage)
_, err = newDeployment(client, tc.namespace, tc.backend, tc.replicas, tc.readyReplicas)
require.NoError(t, err)
collector, err := NewSkipperCollector(client, plugin, hpa, config, time.Minute, tc.backendAnnotations, tc.backend)
@@ -314,10 +360,22 @@ func makeHPA(ingressName, backend string) *autoscalingv2.HorizontalPodAutoscaler
},
}
}
func makeConfig(backend string) *MetricConfig {
return &MetricConfig{
func makeConfig(backend string, fakedAverage bool) *MetricConfig {
config := &MetricConfig{
MetricTypeName: MetricTypeName{Metric: autoscalingv2.MetricIdentifier{Name: fmt.Sprintf("%s,%s", rpsMetricName, backend)}},
MetricSpec: autoscalingv2.MetricSpec{
Object: &autoscalingv2.ObjectMetricSource{
Target: autoscalingv2.MetricTarget{},
},
},
}
if fakedAverage {
config.MetricSpec.Object.Target.Value = resource.NewQuantity(10, resource.DecimalSI)
} else {
config.MetricSpec.Object.Target.AverageValue = resource.NewQuantity(10, resource.DecimalSI)
}
return config
}
type FakeCollectorPlugin struct {