Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add autoscale option to enable support for Horizontal Pod Autoscaling #746

Merged
merged 10 commits into from
Mar 8, 2022
12 changes: 12 additions & 0 deletions apis/v1alpha1/opentelemetrycollector_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,22 @@ type OpenTelemetryCollectorSpec struct {
// +optional
Args map[string]string `json:"args,omitempty"`

// Autoscale turns on/off the autoscale feature. By default, it's enabled if the Replicas field is not set.
// +optional
Autoscale *bool `json:"autoscale,omitempty"`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be set to true in the defaulting webhook if replicas is nil?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure, right now the default is one replica, if you don't specify replicas, I tried to not change default behaviour


// Replicas is the number of pod instances for the underlying OpenTelemetry Collector
// +optional
Replicas *int32 `json:"replicas,omitempty"`

// MinReplicas sets a lower bound to the autoscaling feature.
// +optional
MinReplicas *int32 `json:"minReplicas,omitempty"`

// MaxReplicas sets an upper bound to the autoscaling feature. When autoscaling is enabled and no value is provided, a default value is used.
// +optional
MaxReplicas *int32 `json:"maxReplicas,omitempty"`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have a proposal to make the config/CR simpler.

Could we add only MaxReplicas field in this PR to support autostacling?

If the max replicas is set the operator would create HPA. In the validating webhook it would check if maxReplicas is higher than replicas.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems that MaxReplicas can be nil (e.g. for infinite scaling) in this case we need Autoscale flag to enable the HPA.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably we can do It (remove autoscale), https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/horizontal-pod-autoscaler-v1/#HorizontalPodAutoscalerSpec
because maxReplicas is required:

maxReplicas (int32), required
upper limit for the number of pods that can be set by the autoscaler; cannot be smaller than MinReplicas.


// ImagePullPolicy indicates the pull policy to be used for retrieving the container image (Always, Never, IfNotPresent)
// +optional
ImagePullPolicy v1.PullPolicy `json:"imagePullPolicy,omitempty"`
Expand Down
15 changes: 15 additions & 0 deletions apis/v1alpha1/opentelemetrycollector_webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,5 +109,20 @@ func (r *OpenTelemetryCollector) validateCRDSpec() error {
}
}

// validate autoscale with horizontal pod autoscaler
if r.Spec.Autoscale != nil && *r.Spec.Autoscale {
if r.Spec.Replicas != nil {
return fmt.Errorf("the OpenTelemetry Spec autoscale configuration is incorrect, replicas should be nil")
}

if r.Spec.MaxReplicas == nil || *r.Spec.MaxReplicas < int32(1) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

r.Spec.MaxReplicas == nil can be removed

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated

return fmt.Errorf("the OpenTelemetry Spec autoscale configuration is incorrect, maaxReplicas should be defined and more than one")
}

if r.Spec.MinReplicas != nil && *r.Spec.MinReplicas > *r.Spec.MaxReplicas {
return fmt.Errorf("the OpenTelemetry Spec autoscale configuration is incorrect, minReplicas must not be greater than maxReplicas")
}
}

return nil
}
15 changes: 15 additions & 0 deletions apis/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,18 @@ spec:
- patch
- update
- watch
- apiGroups:
- autoscaling
resources:
- horizontalpodautoscalers
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
- apiGroups:
- coordination.k8s.io
resources:
Expand Down
14 changes: 14 additions & 0 deletions bundle/manifests/opentelemetry.io_opentelemetrycollectors.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,10 @@ spec:
description: Args is the set of arguments to pass to the OpenTelemetry
Collector binary
type: object
autoscale:
description: Autoscale turns on/off the autoscale feature. By default,
it's enabled if the Replicas field is not set.
type: boolean
config:
description: Config is the raw JSON to be used as the collector's
configuration. Refer to the OpenTelemetry Collector documentation
Expand Down Expand Up @@ -218,6 +222,16 @@ spec:
description: ImagePullPolicy indicates the pull policy to be used
for retrieving the container image (Always, Never, IfNotPresent)
type: string
maxReplicas:
description: MaxReplicas sets an upper bound to the autoscaling feature.
When autoscaling is enabled and no value is provided, a default
value is used.
format: int32
type: integer
minReplicas:
description: MinReplicas sets a lower bound to the autoscaling feature.
format: int32
type: integer
mode:
description: Mode represents how the collector should be deployed
(deployment, daemonset, statefulset or sidecar)
Expand Down
14 changes: 14 additions & 0 deletions config/crd/bases/opentelemetry.io_opentelemetrycollectors.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,10 @@ spec:
description: Args is the set of arguments to pass to the OpenTelemetry
Collector binary
type: object
autoscale:
description: Autoscale turns on/off the autoscale feature. By default,
it's enabled if the Replicas field is not set.
type: boolean
config:
description: Config is the raw JSON to be used as the collector's
configuration. Refer to the OpenTelemetry Collector documentation
Expand Down Expand Up @@ -216,6 +220,16 @@ spec:
description: ImagePullPolicy indicates the pull policy to be used
for retrieving the container image (Always, Never, IfNotPresent)
type: string
maxReplicas:
description: MaxReplicas sets an upper bound to the autoscaling feature.
When autoscaling is enabled and no value is provided, a default
value is used.
format: int32
type: integer
minReplicas:
description: MinReplicas sets a lower bound to the autoscaling feature.
format: int32
type: integer
mode:
description: Mode represents how the collector should be deployed
(deployment, daemonset, statefulset or sidecar)
Expand Down
12 changes: 12 additions & 0 deletions config/rbac/role.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,18 @@ rules:
- patch
- update
- watch
- apiGroups:
- autoscaling
resources:
- horizontalpodautoscalers
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
- apiGroups:
- coordination.k8s.io
resources:
Expand Down
7 changes: 7 additions & 0 deletions controllers/opentelemetrycollector_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (

"github.com/go-logr/logr"
appsv1 "k8s.io/api/apps/v1"
autoscalingv1 "k8s.io/api/autoscaling/v1"
corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime"
Expand Down Expand Up @@ -84,6 +85,11 @@ func NewReconciler(p Params) *OpenTelemetryCollectorReconciler {
reconcile.Deployments,
true,
},
{
"horizontal pod autoscalers",
reconcile.HorizontalPodAutoscalers,
true,
},
{
"daemon sets",
reconcile.DaemonSets,
Expand Down Expand Up @@ -173,6 +179,7 @@ func (r *OpenTelemetryCollectorReconciler) SetupWithManager(mgr ctrl.Manager) er
Owns(&corev1.ServiceAccount{}).
Owns(&corev1.Service{}).
Owns(&appsv1.Deployment{}).
Owns(&autoscalingv1.HorizontalPodAutoscaler{}).
Owns(&appsv1.DaemonSet{}).
Owns(&appsv1.StatefulSet{}).
Complete(r)
Expand Down
25 changes: 25 additions & 0 deletions docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -1404,6 +1404,13 @@ OpenTelemetryCollectorSpec defines the desired state of OpenTelemetryCollector.
Args is the set of arguments to pass to the OpenTelemetry Collector binary<br/>
</td>
<td>false</td>
</tr><tr>
<td><b>autoscale</b></td>
<td>boolean</td>
<td>
Autoscale turns on/off the autoscale feature. By default, it's enabled if the Replicas field is not set.<br/>
</td>
<td>false</td>
</tr><tr>
<td><b>config</b></td>
<td>string</td>
Expand Down Expand Up @@ -1446,6 +1453,24 @@ OpenTelemetryCollectorSpec defines the desired state of OpenTelemetryCollector.
ImagePullPolicy indicates the pull policy to be used for retrieving the container image (Always, Never, IfNotPresent)<br/>
</td>
<td>false</td>
</tr><tr>
<td><b>maxReplicas</b></td>
<td>integer</td>
<td>
MaxReplicas sets an upper bound to the autoscaling feature. When autoscaling is enabled and no value is provided, a default value is used.<br/>
<br/>
<i>Format</i>: int32<br/>
</td>
<td>false</td>
</tr><tr>
<td><b>minReplicas</b></td>
<td>integer</td>
<td>
MinReplicas sets a lower bound to the autoscaling feature.<br/>
<br/>
<i>Format</i>: int32<br/>
</td>
<td>false</td>
</tr><tr>
<td><b>mode</b></td>
<td>enum</td>
Expand Down
8 changes: 7 additions & 1 deletion pkg/collector/deployment.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,12 @@ func Deployment(cfg config.Config, logger logr.Logger, otelcol v1alpha1.OpenTele
annotations := Annotations(otelcol)
podAnnotations := PodAnnotations(otelcol)

// if autoscale is enabled, set replicas to minReplicas
replicas := otelcol.Spec.Replicas
if otelcol.Spec.Autoscale != nil && *otelcol.Spec.Autoscale {
replicas = otelcol.Spec.MinReplicas
}

return appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{
Name: naming.Collector(otelcol),
Expand All @@ -41,7 +47,7 @@ func Deployment(cfg config.Config, logger logr.Logger, otelcol v1alpha1.OpenTele
Annotations: annotations,
},
Spec: appsv1.DeploymentSpec{
Replicas: otelcol.Spec.Replicas,
Replicas: replicas,
Selector: &metav1.LabelSelector{
MatchLabels: labels,
},
Expand Down
52 changes: 52 additions & 0 deletions pkg/collector/horizontalpodautoscaler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// 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.

package collector

import (
"github.com/go-logr/logr"
autoscalingv1 "k8s.io/api/autoscaling/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

"github.com/open-telemetry/opentelemetry-operator/apis/v1alpha1"
"github.com/open-telemetry/opentelemetry-operator/internal/config"
"github.com/open-telemetry/opentelemetry-operator/pkg/naming"
)

func HorizontalPodAutoscaler(cfg config.Config, logger logr.Logger, otelcol v1alpha1.OpenTelemetryCollector) autoscalingv1.HorizontalPodAutoscaler {
labels := Labels(otelcol)
labels["app.kubernetes.io/name"] = naming.Collector(otelcol)

annotations := Annotations(otelcol)
var cpuTarget int32 = 90
Copy link
Contributor

@avadhut123pisal avadhut123pisal Mar 2, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

var cpuTarget int32 = 90

The value of the cpuTarget is hardcoded here. I think we should take the input of TargetCPUUtilizationPercentage as a part of OpenTelemetryCollectorSpec.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, that make sense to do, but I initially tried to be consistent with jaeger operator https://github.com/jaegertracing/jaeger-operator/pull/856/files where it also hardcoded.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's fine for the first version.

I would recommend adding a comment and extracting the value to a constant.


return autoscalingv1.HorizontalPodAutoscaler{
ObjectMeta: metav1.ObjectMeta{
Name: naming.Collector(otelcol),
Namespace: otelcol.Namespace,
Labels: labels,
Annotations: annotations,
},
Spec: autoscalingv1.HorizontalPodAutoscalerSpec{
ScaleTargetRef: autoscalingv1.CrossVersionObjectReference{
APIVersion: "apps/v1",
Kind: "Deployment",
Name: naming.Collector(otelcol),
},
MinReplicas: otelcol.Spec.MinReplicas,
MaxReplicas: *otelcol.Spec.MaxReplicas,
TargetCPUUtilizationPercentage: &cpuTarget,
},
}
}
54 changes: 54 additions & 0 deletions pkg/collector/horizontalpodautoscaler_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// 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.

package collector_test

import (
"testing"

"github.com/stretchr/testify/assert"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

"github.com/open-telemetry/opentelemetry-operator/apis/v1alpha1"
"github.com/open-telemetry/opentelemetry-operator/internal/config"
. "github.com/open-telemetry/opentelemetry-operator/pkg/collector"
)

func TestHPA(t *testing.T) {
// prepare
enable := true
var minReplicas int32 = 3
var maxReplicas int32 = 5

otelcol := v1alpha1.OpenTelemetryCollector{
ObjectMeta: metav1.ObjectMeta{
Name: "my-instance",
},
Spec: v1alpha1.OpenTelemetryCollectorSpec{
MinReplicas: &minReplicas,
MaxReplicas: &maxReplicas,
Autoscale: &enable,
},
}

cfg := config.New()
hpa := HorizontalPodAutoscaler(cfg, logger, otelcol)

// verify
assert.Equal(t, "my-instance-collector", hpa.Name)
assert.Equal(t, "my-instance-collector", hpa.Labels["app.kubernetes.io/name"])
assert.Equal(t, int32(3), *hpa.Spec.MinReplicas)
assert.Equal(t, int32(5), hpa.Spec.MaxReplicas)
assert.Equal(t, int32(90), *hpa.Spec.TargetCPUUtilizationPercentage)
}
5 changes: 5 additions & 0 deletions pkg/collector/reconcile/deployment.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,11 @@ func expectedDeployments(ctx context.Context, params Params, expected []appsv1.D
updated.ObjectMeta.Labels[k] = v
}

// if autoscale is enabled, use replicas from current Status
if params.Instance.Spec.Autoscale != nil && *params.Instance.Spec.Autoscale {
updated.Spec.Replicas = &existing.Status.Replicas
}

patch := client.MergeFrom(existing)

if err := params.Client.Patch(ctx, updated, patch); err != nil {
Expand Down
Loading