Skip to content

Commit 61accfd

Browse files
authored
Add TargetMemoryUtilization metric for AutoScaling (#1223)
* Add TargetMemoryUtilization metric for AutoScaling Signed-off-by: Kevin Earls <[email protected]> * Add changes to v2beta2 as there is no way to un e2e tests just for one version Signed-off-by: Kevin Earls <[email protected]> * See if we just have a race condition Signed-off-by: Kevin Earls <[email protected]> * Reset kuttl timeout Signed-off-by: Kevin Earls <[email protected]> * Add some debugging code to help analyze failures on github Signed-off-by: Kevin Earls <[email protected]> * Try to appease the linter Signed-off-by: Kevin Earls <[email protected]> * Restore autoscale tests Signed-off-by: Kevin Earls <[email protected]> * Cleanup Signed-off-by: Kevin Earls <[email protected]> * More cleanup Signed-off-by: Kevin Earls <[email protected]> * Respond to comments Signed-off-by: Kevin Earls <[email protected]> * Cleanup whitespace so linter will rerun Signed-off-by: Kevin Earls <[email protected]> * Don't set TargetCPUUtilization to default if another metric is set Signed-off-by: Kevin Earls <[email protected]> Signed-off-by: Kevin Earls <[email protected]>
1 parent 6f9d7df commit 61accfd

12 files changed

+116
-27
lines changed

apis/v1alpha1/opentelemetrycollector_types.go

+3
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,9 @@ type AutoscalerSpec struct {
277277
// If average CPU exceeds this value, the HPA will scale up. Defaults to 90 percent.
278278
// +optional
279279
TargetCPUUtilization *int32 `json:"targetCPUUtilization,omitempty"`
280+
// +optional
281+
// TargetMemoryUtilization sets the target average memory utilization across all replicas
282+
TargetMemoryUtilization *int32 `json:"targetMemoryUtilization,omitempty"`
280283
}
281284

282285
func init() {

apis/v1alpha1/opentelemetrycollector_webhook.go

+9-5
Original file line numberDiff line numberDiff line change
@@ -67,13 +67,15 @@ func (r *OpenTelemetryCollector) Default() {
6767
r.Spec.TargetAllocator.Replicas = &one
6868
}
6969

70-
// Set default targetCPUUtilization for autoscaler
71-
if r.Spec.MaxReplicas != nil && (r.Spec.Autoscaler == nil || r.Spec.Autoscaler.TargetCPUUtilization == nil) {
72-
defaultCPUTarget := int32(90)
70+
if r.Spec.MaxReplicas != nil {
7371
if r.Spec.Autoscaler == nil {
7472
r.Spec.Autoscaler = &AutoscalerSpec{}
7573
}
76-
r.Spec.Autoscaler.TargetCPUUtilization = &defaultCPUTarget
74+
75+
if r.Spec.Autoscaler.TargetMemoryUtilization == nil && r.Spec.Autoscaler.TargetCPUUtilization == nil {
76+
defaultCPUTarget := int32(90)
77+
r.Spec.Autoscaler.TargetCPUUtilization = &defaultCPUTarget
78+
}
7779
}
7880
}
7981

@@ -176,7 +178,9 @@ func (r *OpenTelemetryCollector) validateCRDSpec() error {
176178
if r.Spec.Autoscaler != nil && r.Spec.Autoscaler.TargetCPUUtilization != nil && (*r.Spec.Autoscaler.TargetCPUUtilization < int32(1) || *r.Spec.Autoscaler.TargetCPUUtilization > int32(99)) {
177179
return fmt.Errorf("the OpenTelemetry Spec autoscale configuration is incorrect, targetCPUUtilization should be greater than 0 and less than 100")
178180
}
179-
181+
if r.Spec.Autoscaler != nil && r.Spec.Autoscaler.TargetMemoryUtilization != nil && (*r.Spec.Autoscaler.TargetMemoryUtilization < int32(1) || *r.Spec.Autoscaler.TargetMemoryUtilization > int32(99)) {
182+
return fmt.Errorf("the OpenTelemetry Spec autoscale configuration is incorrect, targetMemoryUtilization should be greater than 0 and less than 100")
183+
}
180184
}
181185

182186
if r.Spec.Ingress.Type == IngressTypeNginx && r.Spec.Mode == ModeSidecar {

apis/v1alpha1/zz_generated.deepcopy.go

+5
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

bundle/manifests/opentelemetry.io_opentelemetrycollectors.yaml

+5
Original file line numberDiff line numberDiff line change
@@ -1016,6 +1016,11 @@ spec:
10161016
the HPA will scale up. Defaults to 90 percent.
10171017
format: int32
10181018
type: integer
1019+
targetMemoryUtilization:
1020+
description: TargetMemoryUtilization sets the target average memory
1021+
utilization across all replicas
1022+
format: int32
1023+
type: integer
10191024
type: object
10201025
config:
10211026
description: Config is the raw JSON to be used as the collector's

config/crd/bases/opentelemetry.io_opentelemetrycollectors.yaml

+5
Original file line numberDiff line numberDiff line change
@@ -1014,6 +1014,11 @@ spec:
10141014
the HPA will scale up. Defaults to 90 percent.
10151015
format: int32
10161016
type: integer
1017+
targetMemoryUtilization:
1018+
description: TargetMemoryUtilization sets the target average memory
1019+
utilization across all replicas
1020+
format: int32
1021+
type: integer
10171022
type: object
10181023
config:
10191024
description: Config is the raw JSON to be used as the collector's

docs/api.md

+9
Original file line numberDiff line numberDiff line change
@@ -3228,6 +3228,15 @@ Autoscaler specifies the pod autoscaling configuration to use for the OpenTeleme
32283228
<i>Format</i>: int32<br/>
32293229
</td>
32303230
<td>false</td>
3231+
</tr><tr>
3232+
<td><b>targetMemoryUtilization</b></td>
3233+
<td>integer</td>
3234+
<td>
3235+
TargetMemoryUtilization sets the target average memory utilization across all replicas<br/>
3236+
<br/>
3237+
<i>Format</i>: int32<br/>
3238+
</td>
3239+
<td>false</td>
32313240
</tr></tbody>
32323241
</table>
32333242

pkg/collector/horizontalpodautoscaler.go

+45-13
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,7 @@ func HorizontalPodAutoscaler(cfg config.Config, logger logr.Logger, otelcol v1al
3333

3434
labels := Labels(otelcol, cfg.LabelsFilter())
3535
labels["app.kubernetes.io/name"] = naming.Collector(otelcol)
36-
3736
annotations := Annotations(otelcol)
38-
3937
var result client.Object
4038

4139
objectMeta := metav1.ObjectMeta{
@@ -46,6 +44,22 @@ func HorizontalPodAutoscaler(cfg config.Config, logger logr.Logger, otelcol v1al
4644
}
4745

4846
if autoscalingVersion == autodetect.AutoscalingVersionV2Beta2 {
47+
metrics := []autoscalingv2beta2.MetricSpec{}
48+
49+
if otelcol.Spec.Autoscaler.TargetMemoryUtilization != nil {
50+
utilizationTarget := autoscalingv2beta2.MetricSpec{
51+
Type: autoscalingv2beta2.ResourceMetricSourceType,
52+
Resource: &autoscalingv2beta2.ResourceMetricSource{
53+
Name: corev1.ResourceMemory,
54+
Target: autoscalingv2beta2.MetricTarget{
55+
Type: autoscalingv2beta2.UtilizationMetricType,
56+
AverageUtilization: otelcol.Spec.Autoscaler.TargetMemoryUtilization,
57+
},
58+
},
59+
}
60+
metrics = append(metrics, utilizationTarget)
61+
}
62+
4963
targetCPUUtilization := autoscalingv2beta2.MetricSpec{
5064
Type: autoscalingv2beta2.ResourceMetricSourceType,
5165
Resource: &autoscalingv2beta2.ResourceMetricSource{
@@ -56,7 +70,7 @@ func HorizontalPodAutoscaler(cfg config.Config, logger logr.Logger, otelcol v1al
5670
},
5771
},
5872
}
59-
metrics := []autoscalingv2beta2.MetricSpec{targetCPUUtilization}
73+
metrics = append(metrics, targetCPUUtilization)
6074

6175
autoscaler := autoscalingv2beta2.HorizontalPodAutoscaler{
6276
ObjectMeta: objectMeta,
@@ -79,17 +93,35 @@ func HorizontalPodAutoscaler(cfg config.Config, logger logr.Logger, otelcol v1al
7993

8094
result = &autoscaler
8195
} else {
82-
targetCPUUtilization := autoscalingv2.MetricSpec{
83-
Type: autoscalingv2.ResourceMetricSourceType,
84-
Resource: &autoscalingv2.ResourceMetricSource{
85-
Name: corev1.ResourceCPU,
86-
Target: autoscalingv2.MetricTarget{
87-
Type: autoscalingv2.UtilizationMetricType,
88-
AverageUtilization: otelcol.Spec.Autoscaler.TargetCPUUtilization,
96+
metrics := []autoscalingv2.MetricSpec{}
97+
98+
if otelcol.Spec.Autoscaler.TargetMemoryUtilization != nil {
99+
utilizationTarget := autoscalingv2.MetricSpec{
100+
Type: autoscalingv2.ResourceMetricSourceType,
101+
Resource: &autoscalingv2.ResourceMetricSource{
102+
Name: corev1.ResourceMemory,
103+
Target: autoscalingv2.MetricTarget{
104+
Type: autoscalingv2.UtilizationMetricType,
105+
AverageUtilization: otelcol.Spec.Autoscaler.TargetMemoryUtilization,
106+
},
89107
},
90-
},
108+
}
109+
metrics = append(metrics, utilizationTarget)
110+
}
111+
112+
if otelcol.Spec.Autoscaler.TargetCPUUtilization != nil {
113+
targetCPUUtilization := autoscalingv2.MetricSpec{
114+
Type: autoscalingv2.ResourceMetricSourceType,
115+
Resource: &autoscalingv2.ResourceMetricSource{
116+
Name: corev1.ResourceCPU,
117+
Target: autoscalingv2.MetricTarget{
118+
Type: autoscalingv2.UtilizationMetricType,
119+
AverageUtilization: otelcol.Spec.Autoscaler.TargetCPUUtilization,
120+
},
121+
},
122+
}
123+
metrics = append(metrics, targetCPUUtilization)
91124
}
92-
metrics := []autoscalingv2.MetricSpec{targetCPUUtilization}
93125

94126
autoscaler := autoscalingv2.HorizontalPodAutoscaler{
95127
ObjectMeta: objectMeta,
@@ -104,7 +136,7 @@ func HorizontalPodAutoscaler(cfg config.Config, logger logr.Logger, otelcol v1al
104136
Metrics: metrics,
105137
},
106138
}
107-
if otelcol.Spec.Autoscaler != nil && otelcol.Spec.Autoscaler.Behavior != nil {
139+
if otelcol.Spec.Autoscaler.Behavior != nil {
108140
autoscaler.Spec.Behavior = otelcol.Spec.Autoscaler.Behavior
109141
}
110142
result = &autoscaler

pkg/collector/horizontalpodautoscaler_test.go

+20-8
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,8 @@ func TestHPA(t *testing.T) {
4141

4242
var minReplicas int32 = 3
4343
var maxReplicas int32 = 5
44-
var cpuUtilization int32 = 90
44+
var cpuUtilization int32 = 66
45+
var memoryUtilization int32 = 77
4546

4647
otelcol := v1alpha1.OpenTelemetryCollector{
4748
ObjectMeta: metav1.ObjectMeta{
@@ -51,7 +52,8 @@ func TestHPA(t *testing.T) {
5152
Replicas: &minReplicas,
5253
MaxReplicas: &maxReplicas,
5354
Autoscaler: &v1alpha1.AutoscalerSpec{
54-
TargetCPUUtilization: &cpuUtilization,
55+
TargetCPUUtilization: &cpuUtilization,
56+
TargetMemoryUtilization: &memoryUtilization,
5557
},
5658
},
5759
}
@@ -76,9 +78,13 @@ func TestHPA(t *testing.T) {
7678
assert.Equal(t, "my-instance-collector", hpa.Labels["app.kubernetes.io/name"])
7779
assert.Equal(t, int32(3), *hpa.Spec.MinReplicas)
7880
assert.Equal(t, int32(5), hpa.Spec.MaxReplicas)
79-
assert.Equal(t, 1, len(hpa.Spec.Metrics))
80-
assert.Equal(t, corev1.ResourceCPU, hpa.Spec.Metrics[0].Resource.Name)
81-
assert.Equal(t, int32(90), *hpa.Spec.Metrics[0].Resource.Target.AverageUtilization)
81+
for _, metric := range hpa.Spec.Metrics {
82+
if metric.Resource.Name == corev1.ResourceCPU {
83+
assert.Equal(t, cpuUtilization, *metric.Resource.Target.AverageUtilization)
84+
} else if metric.Resource.Name == corev1.ResourceMemory {
85+
assert.Equal(t, memoryUtilization, *metric.Resource.Target.AverageUtilization)
86+
}
87+
}
8288
} else {
8389
hpa := raw.(*autoscalingv2.HorizontalPodAutoscaler)
8490

@@ -87,9 +93,15 @@ func TestHPA(t *testing.T) {
8793
assert.Equal(t, "my-instance-collector", hpa.Labels["app.kubernetes.io/name"])
8894
assert.Equal(t, int32(3), *hpa.Spec.MinReplicas)
8995
assert.Equal(t, int32(5), hpa.Spec.MaxReplicas)
90-
assert.Equal(t, 1, len(hpa.Spec.Metrics))
91-
assert.Equal(t, corev1.ResourceCPU, hpa.Spec.Metrics[0].Resource.Name)
92-
assert.Equal(t, int32(90), *hpa.Spec.Metrics[0].Resource.Target.AverageUtilization)
96+
assert.Equal(t, 2, len(hpa.Spec.Metrics))
97+
98+
for _, metric := range hpa.Spec.Metrics {
99+
if metric.Resource.Name == corev1.ResourceCPU {
100+
assert.Equal(t, cpuUtilization, *metric.Resource.Target.AverageUtilization)
101+
} else if metric.Resource.Name == corev1.ResourceMemory {
102+
assert.Equal(t, memoryUtilization, *metric.Resource.Target.AverageUtilization)
103+
}
104+
}
93105
}
94106
})
95107
}

pkg/collector/reconcile/horizontalpodautoscaler.go

+10-1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020

2121
autoscalingv2 "k8s.io/api/autoscaling/v2"
2222
autoscalingv2beta2 "k8s.io/api/autoscaling/v2beta2"
23+
corev1 "k8s.io/api/core/v1"
2324
k8serrors "k8s.io/apimachinery/pkg/api/errors"
2425
"k8s.io/apimachinery/pkg/api/meta"
2526
"k8s.io/apimachinery/pkg/types"
@@ -127,7 +128,15 @@ func setAutoscalerSpec(params Params, autoscalingVersion autodetect.AutoscalingV
127128
} else {
128129
updated.(*autoscalingv2.HorizontalPodAutoscaler).Spec.MinReplicas = &one
129130
}
130-
updated.(*autoscalingv2.HorizontalPodAutoscaler).Spec.Metrics[0].Resource.Target.AverageUtilization = params.Instance.Spec.Autoscaler.TargetCPUUtilization
131+
132+
// This will update memory and CPU usage for now, and can be used to update other metrics in the future
133+
for _, metric := range updated.(*autoscalingv2.HorizontalPodAutoscaler).Spec.Metrics {
134+
if metric.Resource.Name == corev1.ResourceCPU {
135+
metric.Resource.Target.AverageUtilization = params.Instance.Spec.Autoscaler.TargetCPUUtilization
136+
} else if metric.Resource.Name == corev1.ResourceMemory {
137+
metric.Resource.Target.AverageUtilization = params.Instance.Spec.Autoscaler.TargetMemoryUtilization
138+
}
139+
}
131140
}
132141
}
133142
}

tests/e2e/autoscale/00-install.yaml

+3
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
# This creates two different deployments. The first one will be used to see if we scale properly. (Note that we are
2+
# only scaling up to 2 because of limitations of KUTTL). The second is to check the targetCPUUtilization option.
3+
#
14
apiVersion: opentelemetry.io/v1alpha1
25
kind: OpenTelemetryCollector
36
metadata:

tests/e2e/autoscale/01-assert.yaml

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
# Wait until tracegen has completed and the simplest deployment has scaled up to 2
12
apiVersion: batch/v1
23
kind: Job
34
metadata:

tests/e2e/autoscale/02-assert.yaml

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
# Wait for the collector to scale back down to 1
12
apiVersion: opentelemetry.io/v1alpha1
23
kind: OpenTelemetryCollector
34

0 commit comments

Comments
 (0)