Skip to content

Commit

Permalink
Create Service Monitors for the Prometheus exporters (#1983)
Browse files Browse the repository at this point in the history
* Create Service Monitors for the Prometheus exporters

* Remove testing changes

Signed-off-by: Israel Blancas <iblancasa@gmail.com>

* Remove pull policy

Signed-off-by: Israel Blancas <iblancasa@gmail.com>

* Apply changes requested in PR

* Fix E2E test

Signed-off-by: Israel Blancas <iblancasa@gmail.com>

* Update E2E test to use the enableMetrics flag

Signed-off-by: Israel Blancas <iblancasa@gmail.com>

* Use the enable metrics to create the SM or not

Signed-off-by: Israel Blancas <iblancasa@gmail.com>

* Update field description

Signed-off-by: Israel Blancas <iblancasa@gmail.com>

* Add missing docs

Signed-off-by: Israel Blancas <iblancasa@gmail.com>

* Remove config

Signed-off-by: Israel Blancas <iblancasa@gmail.com>

* Ensue the ServiceMonitor matches a specific OpenTelemetry Collector instance

Signed-off-by: Israel Blancas <iblancasa@gmail.com>

* Update generated files

Signed-off-by: Israel Blancas <iblancasa@gmail.com>

* Fix comment

Signed-off-by: Israel Blancas <iblancasa@gmail.com>

---------

Signed-off-by: Israel Blancas <iblancasa@gmail.com>
  • Loading branch information
iblancasa authored Aug 21, 2023
1 parent c3d8153 commit d3c98ae
Show file tree
Hide file tree
Showing 74 changed files with 927 additions and 202 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix'
change_type: enhancement

# The name of the component, or a single word describing the area of concern, (e.g. operator, target allocator, github action)
component: operator

# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
note: Create ServiceMonitors when the Prometheus exporters are used.

# One or more tracking issues related to the change
issues: [1963]

# (Optional) One or more lines of additional information to render under the primary note.
# These lines will be padded with 2 spaces and then inserted directly into the document.
# Use pipe (|) for multiline entries.
subtext:
2 changes: 1 addition & 1 deletion apis/v1alpha1/opentelemetrycollector_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -426,7 +426,7 @@ type AutoscalerSpec struct {

// MetricsConfigSpec defines a metrics config.
type MetricsConfigSpec struct {
// EnableMetrics specifies if ServiceMonitors should be created for the OpenTelemetry Collector.
// EnableMetrics specifies if ServiceMonitor should be created for the OpenTelemetry Collector and Prometheus Exporters.
// The operator.observability.prometheus feature gate must be enabled to use this feature.
//
// +optional
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,9 +83,9 @@ spec:
- description: Metrics defines the metrics configuration for operands.
displayName: Metrics Config
path: observability.metrics
- description: EnableMetrics specifies if ServiceMonitors should be created
for the OpenTelemetry Collector. The operator.observability.prometheus feature
gate must be enabled to use this feature.
- description: EnableMetrics specifies if ServiceMonitor should be created for
the OpenTelemetry Collector and Prometheus Exporters. The operator.observability.prometheus
feature gate must be enabled to use this feature.
displayName: Create ServiceMonitors for OpenTelemetry Collector
path: observability.metrics.enableMetrics
version: v1alpha1
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3690,9 +3690,9 @@ spec:
description: Metrics defines the metrics configuration for operands.
properties:
enableMetrics:
description: EnableMetrics specifies if ServiceMonitors should
be created for the OpenTelemetry Collector. The operator.observability.prometheus
feature gate must be enabled to use this feature.
description: EnableMetrics specifies if ServiceMonitor should
be created for the OpenTelemetry Collector and Prometheus
Exporters. The operator.observability.
type: boolean
type: object
type: object
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3687,9 +3687,9 @@ spec:
description: Metrics defines the metrics configuration for operands.
properties:
enableMetrics:
description: EnableMetrics specifies if ServiceMonitors should
be created for the OpenTelemetry Collector. The operator.observability.prometheus
feature gate must be enabled to use this feature.
description: EnableMetrics specifies if ServiceMonitor should
be created for the OpenTelemetry Collector and Prometheus
Exporters. The operator.observability.
type: boolean
type: object
type: object
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,9 @@ spec:
- description: Metrics defines the metrics configuration for operands.
displayName: Metrics Config
path: observability.metrics
- description: EnableMetrics specifies if ServiceMonitors should be created
for the OpenTelemetry Collector. The operator.observability.prometheus feature
gate must be enabled to use this feature.
- description: EnableMetrics specifies if ServiceMonitor should be created for
the OpenTelemetry Collector and Prometheus Exporters. The operator.observability.prometheus
feature gate must be enabled to use this feature.
displayName: Create ServiceMonitors for OpenTelemetry Collector
path: observability.metrics.enableMetrics
version: v1alpha1
Expand Down
2 changes: 1 addition & 1 deletion docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -11410,7 +11410,7 @@ Metrics defines the metrics configuration for operands.
<td><b>enableMetrics</b></td>
<td>boolean</td>
<td>
EnableMetrics specifies if ServiceMonitors should be created for the OpenTelemetry Collector. The operator.observability.prometheus feature gate must be enabled to use this feature.<br/>
EnableMetrics specifies if ServiceMonitor should be created for the OpenTelemetry Collector and Prometheus Exporters. The operator.observability.<br/>
</td>
<td>false</td>
</tr></tbody>
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ require (
github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.8.4
go.opentelemetry.io/collector/featuregate v0.77.0
go.opentelemetry.io/collector/semconv v0.82.0
go.opentelemetry.io/otel v1.16.0
gopkg.in/yaml.v2 v2.4.0
k8s.io/api v0.28.0
Expand Down
4 changes: 3 additions & 1 deletion go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,7 @@ github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdv
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
github.com/hashicorp/go-version v1.2.1 h1:zEfKbn2+PDgroKdiOzqiE8rsmLqU2uwi5PB5pBJ3TkI=
github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.6.0 h1:uL2shRDx7RTrOrTCUZEGP/wJUFiUI8QT6E7z5o8jga4=
Expand Down Expand Up @@ -526,6 +526,8 @@ go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
go.opentelemetry.io/collector/featuregate v0.77.0 h1:m1/IzaXoQh6SgF6CM80vrBOCf5zSJ2GVISfA27fYzGU=
go.opentelemetry.io/collector/featuregate v0.77.0/go.mod h1:/kVAsGUCyJXIDSgHftCN63QiwAEVHRLX2Kh/S+dqgHY=
go.opentelemetry.io/collector/semconv v0.82.0 h1:WUeT2a+uZjI6kLvwcBaJnGvo7KSQ/9dIFRcxOQdXucc=
go.opentelemetry.io/collector/semconv v0.82.0/go.mod h1:TlYPtzvsXyHOgr5eATi43qEMqwSmIziivJB2uctKswo=
go.opentelemetry.io/otel v1.16.0 h1:Z7GVAX/UkAXPKsy94IU+i6thsQS4nb7LviLpnaNeW8s=
go.opentelemetry.io/otel v1.16.0/go.mod h1:vl0h9NUa1D5s1nv3A5vZOYWn8av4K8Ml6JDeHrT/bx4=
go.opentelemetry.io/otel/trace v1.16.0 h1:8JRpaObFoW0pxuVPapkgH8UhHQj+bJW8jJsCZEu5MQs=
Expand Down
97 changes: 95 additions & 2 deletions internal/manifests/collector/adapters/config_to_ports.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,91 @@ import (
"github.com/mitchellh/mapstructure"
corev1 "k8s.io/api/core/v1"

"github.com/open-telemetry/opentelemetry-operator/internal/manifests/collector/parser"
exporterParser "github.com/open-telemetry/opentelemetry-operator/internal/manifests/collector/parser/exporter"
receiverParser "github.com/open-telemetry/opentelemetry-operator/internal/manifests/collector/parser/receiver"
)

var (
// ErrNoExporters indicates that there are no exporters in the configuration.
ErrNoExporters = errors.New("no exporters available as part of the configuration")

// ErrNoReceivers indicates that there are no receivers in the configuration.
ErrNoReceivers = errors.New("no receivers available as part of the configuration")

// ErrReceiversNotAMap indicates that the receivers property isn't a map of values.
ErrReceiversNotAMap = errors.New("receivers property in the configuration doesn't contain valid receivers")

// ErrExportersNotAMap indicates that the exporters property isn't a map of values.
ErrExportersNotAMap = errors.New("exporters property in the configuration doesn't contain valid exporters")
)

// ConfigToExporterPorts converts the incoming configuration object into a set of service ports required by the exporters.
func ConfigToExporterPorts(logger logr.Logger, config map[interface{}]interface{}) ([]corev1.ServicePort, error) {
// now, we gather which ports we might need to open
// for that, we get all the exporters and check their `endpoint` properties,
// extracting the port from it. The port name has to be a "DNS_LABEL", so, we try to make it follow the pattern:
// ${instance.Name}-${exporter.name}-${exporter.qualifier}
// the exporter-name is typically the node name from the exporters map
// the exporter-qualifier is what comes after the slash in the exporter name, but typically nil
// examples:
// ```yaml
// exporters:
// exampleexporter:
// endpoint: 0.0.0.0:12345
// exampleexporter/settings:
// endpoint: 0.0.0.0:12346
// in this case, we have two ports, named: "exampleexporter" and "exampleexporter-settings"
exportersProperty, ok := config["exporters"]
if !ok {
return nil, ErrNoExporters
}
expEnabled := GetEnabledExporters(logger, config)
if expEnabled == nil {
return nil, ErrExportersNotAMap
}
exporters, ok := exportersProperty.(map[interface{}]interface{})
if !ok {
return nil, ErrExportersNotAMap
}

ports := []corev1.ServicePort{}
for key, val := range exporters {
// This check will pass only the enabled exporters,
// then only the related ports will be opened.
if !expEnabled[key] {
continue
}
exporter, ok := val.(map[interface{}]interface{})
if !ok {
logger.Info("exporter doesn't seem to be a map of properties", "exporter", key)
exporter = map[interface{}]interface{}{}
}

exprtName := key.(string)
exprtParser, err := exporterParser.For(logger, exprtName, exporter)
if err != nil {
logger.V(2).Info("no parser found for '%s'", exprtName)
continue
}

exprtPorts, err := exprtParser.Ports()
if err != nil {
logger.Error(err, "parser for '%s' has returned an error: %w", exprtName, err)
continue
}

if len(exprtPorts) > 0 {
ports = append(ports, exprtPorts...)
}
}

sort.Slice(ports, func(i, j int) bool {
return ports[i].Name < ports[j].Name
})

return ports, nil
}

// ConfigToReceiverPorts converts the incoming configuration object into a set of service ports required by the receivers.
func ConfigToReceiverPorts(logger logr.Logger, config map[interface{}]interface{}) ([]corev1.ServicePort, error) {
// now, we gather which ports we might need to open
Expand Down Expand Up @@ -78,7 +152,7 @@ func ConfigToReceiverPorts(logger logr.Logger, config map[interface{}]interface{
}

rcvrName := key.(string)
rcvrParser := parser.For(logger, rcvrName, receiver)
rcvrParser := receiverParser.For(logger, rcvrName, receiver)

rcvrPorts, err := rcvrParser.Ports()
if err != nil {
Expand All @@ -101,6 +175,25 @@ func ConfigToReceiverPorts(logger logr.Logger, config map[interface{}]interface{
return ports, nil
}

func ConfigToPorts(logger logr.Logger, config map[interface{}]interface{}) []corev1.ServicePort {
ports, err := ConfigToReceiverPorts(logger, config)
if err != nil {
logger.Error(err, "there was a problem while getting the ports from the receivers")
}

exporterPorts, err := ConfigToExporterPorts(logger, config)
if err != nil {
logger.Error(err, "there was a problem while getting the ports from the exporters")
}
ports = append(ports, exporterPorts...)

sort.Slice(ports, func(i, j int) bool {
return ports[i].Name < ports[j].Name
})

return ports
}

// ConfigToMetricsPort gets the port number for the metrics endpoint from the collector config if it has been set.
func ConfigToMetricsPort(logger logr.Logger, config map[interface{}]interface{}) (int32, error) {
// we don't need to unmarshal the whole config, just follow the keys down to
Expand Down
4 changes: 2 additions & 2 deletions internal/manifests/collector/adapters/config_to_ports_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import (
logf "sigs.k8s.io/controller-runtime/pkg/log"

"github.com/open-telemetry/opentelemetry-operator/internal/manifests/collector/adapters"
"github.com/open-telemetry/opentelemetry-operator/internal/manifests/collector/parser"
"github.com/open-telemetry/opentelemetry-operator/internal/manifests/collector/parser/receiver"
)

var logger = logf.Log.WithName("unit-tests")
Expand Down Expand Up @@ -179,7 +179,7 @@ func TestParserFailed(t *testing.T) {
return nil, errors.New("mocked error")
},
}
parser.Register("mock", func(logger logr.Logger, name string, config map[interface{}]interface{}) parser.ReceiverParser {
receiver.Register("mock", func(logger logr.Logger, name string, config map[interface{}]interface{}) receiver.ReceiverParser {
return mockParser
})

Expand Down
42 changes: 25 additions & 17 deletions internal/manifests/collector/adapters/config_validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,25 +21,33 @@ import (
// Following Otel Doc: Configuring a receiver does not enable it. The receivers are enabled via pipelines within the service section.
// GetEnabledReceivers returns all enabled receivers as a true flag set. If it can't find any receiver, it will return a nil interface.
func GetEnabledReceivers(_ logr.Logger, config map[interface{}]interface{}) map[interface{}]bool {
cfgReceivers, ok := config["receivers"]
return getEnabledComponents(config, "receivers")
}

func GetEnabledExporters(_ logr.Logger, config map[interface{}]interface{}) map[interface{}]bool {
return getEnabledComponents(config, "exporters")
}

func getEnabledComponents(config map[interface{}]interface{}, componentType string) map[interface{}]bool {
cfgComponents, ok := config[componentType]
if !ok {
return nil
}
receivers, ok := cfgReceivers.(map[interface{}]interface{})
components, ok := cfgComponents.(map[interface{}]interface{})
if !ok {
return nil
}
availableReceivers := map[interface{}]bool{}
availableComponents := map[interface{}]bool{}

for recvID := range receivers {
for compID := range components {

//Safe Cast
receiverID, withReceiver := recvID.(string)
if !withReceiver {
componentID, withComponent := compID.(string)
if !withComponent {
return nil
}
//Getting all receivers present in the receivers section and setting them to false.
availableReceivers[receiverID] = false
//Getting all components present in the components (exporters,receivers...) section and setting them to false.
availableComponents[componentID] = false
}

cfgService, withService := config["service"].(map[interface{}]interface{})
Expand Down Expand Up @@ -77,35 +85,35 @@ func GetEnabledReceivers(_ logr.Logger, config map[interface{}]interface{}) map[
return nil
}
for pipSpecID, pipSpecCfg := range pipelineDesc {
if pipSpecID.(string) == "receivers" {
if pipSpecID.(string) == componentType {
receiversList, ok := pipSpecCfg.([]interface{})
if !ok {
continue
}
// If receiversList is empty means that we haven't any enabled Receiver.
if len(receiversList) == 0 {
availableReceivers = nil
availableComponents = nil
} else {
// All enabled receivers will be set as true
for _, recKey := range receiversList {
for _, comKey := range receiversList {
//Safe Cast
receiverKey, ok := recKey.(string)
receiverKey, ok := comKey.(string)
if !ok {
return nil
}
availableReceivers[receiverKey] = true
availableComponents[receiverKey] = true
}
}
//Removing all non-enabled receivers
for recID, recKey := range availableReceivers {
if !(recKey) {
delete(availableReceivers, recID)
for comID, comKey := range availableComponents {
if !(comKey) {
delete(availableComponents, comID)
}
}
}
}
}
}
}
return availableReceivers
return availableComponents
}
Loading

0 comments on commit d3c98ae

Please sign in to comment.