diff --git a/.chloggen/1689-expose-prometheusexporter-pod-when-enabled.yaml b/.chloggen/1689-expose-prometheusexporter-pod-when-enabled.yaml new file mode 100755 index 0000000000..2545b5cbc0 --- /dev/null +++ b/.chloggen/1689-expose-prometheusexporter-pod-when-enabled.yaml @@ -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: Expose the Prometheus exporter port in the OpenTelemetry Collector container when it is used in the configuration. + +# One or more tracking issues related to the change +issues: [1689] + +# (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: diff --git a/pkg/collector/container.go b/pkg/collector/container.go index 716913b0b0..264d9a6ebe 100644 --- a/pkg/collector/container.go +++ b/pkg/collector/container.go @@ -16,7 +16,10 @@ package collector import ( "fmt" + "net" "sort" + "strconv" + "strings" "github.com/go-logr/logr" corev1 "k8s.io/api/core/v1" @@ -183,9 +186,65 @@ func getConfigContainerPorts(logger logr.Logger, cfg string) map[string]corev1.C ContainerPort: metricsPort, Protocol: corev1.ProtocolTCP, } + + promExporterPorts, errs := getPrometheusExporterPorts(c) + for _, err := range errs { + logger.V(2).Info("There was a problem getting the prometheus exporter port: %s", err) + } + for _, promPort := range promExporterPorts { + ports[promPort.Name] = promPort + } + return ports } +func getPrometheusExporterPort(exporterConfig map[interface{}]interface{}) (int32, error) { + var promPort int32 = 0 + if endpoint, ok := exporterConfig["endpoint"]; ok { + _, port, err := net.SplitHostPort(endpoint.(string)) + if err != nil { + return 0, err + } + i64, err := strconv.ParseInt(port, 10, 32) + if err != nil { + return 0, err + } + promPort = int32(i64) + } + return promPort, nil +} + +func getPrometheusExporterPorts(c map[interface{}]interface{}) ([]corev1.ContainerPort, []error) { + errors := make([]error, 0) + ports := make([]corev1.ContainerPort, 0) + if exporters, ok := c["exporters"]; ok && exporters != nil { + for e, exporterConfig := range exporters.(map[interface{}]interface{}) { + exporterName := e.(string) + if strings.Contains(exporterName, "prometheus") { + containerPort, err := getPrometheusExporterPort(exporterConfig.(map[interface{}]interface{})) + if err != nil { + errors = append(errors, + fmt.Errorf( + "there was a problem getting the port. Exporter %s", + exporterName, + ), + ) + } + ports = append(ports, + corev1.ContainerPort{ + Name: naming.PortName(exporterName), + ContainerPort: containerPort, + Protocol: corev1.ProtocolTCP, + }, + ) + } + } + } else { + errors = append(errors, fmt.Errorf("no exporters specified in the configuration")) + } + return ports, errors +} + func portMapToList(portMap map[string]corev1.ContainerPort) []corev1.ContainerPort { ports := make([]corev1.ContainerPort, 0, len(portMap)) for _, p := range portMap { diff --git a/pkg/collector/container_test.go b/pkg/collector/container_test.go index 6f39364f33..4b7822df1d 100644 --- a/pkg/collector/container_test.go +++ b/pkg/collector/container_test.go @@ -177,6 +177,43 @@ service: }, }, }, + { + description: "prometheus exporter", + specConfig: `exporters: + prometheus: + endpoint: "0.0.0.0:9090"`, + specPorts: []corev1.ServicePort{}, + expectedPorts: []corev1.ContainerPort{ + metricContainerPort, + { + Name: "prometheus", + ContainerPort: 9090, + Protocol: corev1.ProtocolTCP, + }, + }, + }, + { + description: "multiple prometheus exporters", + specConfig: `exporters: + prometheus/prod: + endpoint: "0.0.0.0:9090" + prometheus/dev: + endpoint: "0.0.0.0:9091"`, + specPorts: []corev1.ServicePort{}, + expectedPorts: []corev1.ContainerPort{ + metricContainerPort, + { + Name: "prometheus-prod", + ContainerPort: 9090, + Protocol: corev1.ProtocolTCP, + }, + { + Name: "prometheus-dev", + ContainerPort: 9091, + Protocol: corev1.ProtocolTCP, + }, + }, + }, } for _, testCase := range tests { diff --git a/pkg/naming/ports.go b/pkg/naming/ports.go new file mode 100644 index 0000000000..108f8b75c1 --- /dev/null +++ b/pkg/naming/ports.go @@ -0,0 +1,40 @@ +// 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 naming + +import ( + "strings" + "unicode/utf8" +) + +// PortName returns a dns-safe string for the given name. +// Any char that is not [a-z0-9] is replaced by "-" or "a". +// Replacement character "a" is used only at the beginning or at the end of the name. +// The function does not change length of the string. +func PortName(name string) string { + var d []rune + + for i, x := range strings.ToLower(name) { + if regex.Match([]byte(string(x))) { + d = append(d, x) + } else if i == 0 || i == utf8.RuneCountInString(name)-1 { + d = append(d, 'a') + } else { + d = append(d, '-') + } + } + + return string(d) +} diff --git a/pkg/naming/ports_test.go b/pkg/naming/ports_test.go new file mode 100644 index 0000000000..9e433c95f3 --- /dev/null +++ b/pkg/naming/ports_test.go @@ -0,0 +1,43 @@ +// 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 naming + +import ( + "regexp" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestPortName(t *testing.T) { + var tests = []struct { + in string + out string + }{ + {"simplest", "simplest"}, + {"prometheus/dev", "prometheus-dev"}, + {"prometheus.dev", "prometheus-dev"}, + {"prometheus-", "prometheusa"}, + {"-prometheus", "aprometheus"}, + } + rule, err := regexp.Compile(`^[a-z0-9]([-a-z0-9]*[a-z0-9])?$`) + assert.NoError(t, err) + + for _, tt := range tests { + assert.Equal(t, tt.out, PortName(tt.in)) + matched := rule.Match([]byte(tt.out)) + assert.True(t, matched, "%v is not a valid name", tt.out) + } +}