diff --git a/apis/v1beta1/config.go b/apis/v1beta1/config.go index ad5a3c822c..5cb9150513 100644 --- a/apis/v1beta1/config.go +++ b/apis/v1beta1/config.go @@ -323,12 +323,12 @@ func (c *Config) GetExporterPorts(logger logr.Logger) ([]corev1.ServicePort, err return c.getPortsForComponentKinds(logger, KindExporter) } -func (c *Config) GetReceiverAndExporterPorts(logger logr.Logger) ([]corev1.ServicePort, error) { - return c.getPortsForComponentKinds(logger, KindReceiver, KindExporter) +func (c *Config) GetExtensionPorts(logger logr.Logger) ([]corev1.ServicePort, error) { + return c.getPortsForComponentKinds(logger, KindExtension) } -func (c *Config) GetAllPorts(logger logr.Logger) ([]corev1.ServicePort, error) { - return c.getPortsForComponentKinds(logger, KindReceiver, KindExporter, KindExtension) +func (c *Config) GetReceiverAndExporterPorts(logger logr.Logger) ([]corev1.ServicePort, error) { + return c.getPortsForComponentKinds(logger, KindReceiver, KindExporter) } func (c *Config) GetAllPorts(logger logr.Logger) ([]corev1.ServicePort, error) { diff --git a/apis/v1beta1/opentelemetrycollector_types.go b/apis/v1beta1/opentelemetrycollector_types.go index dd20af943d..2f434aaea5 100644 --- a/apis/v1beta1/opentelemetrycollector_types.go +++ b/apis/v1beta1/opentelemetrycollector_types.go @@ -111,6 +111,11 @@ type OpenTelemetryCollectorSpec struct { // Valid modes are: deployment, daemonset and statefulset. // +optional Ingress Ingress `json:"ingress,omitempty"` + // ExtensionIngress is used to specify how OpenTelemetry Collector is exposed. This + // functionality is only available if one of the valid modes is set. + // Valid modes are: deployment, daemonset and statefulset. + // +optional + ExtensionIngress Ingress `json:"extensionIngress,omitempty"` // Liveness config for the OpenTelemetry Collector except the probe handler which is auto generated from the health extension of the collector. // It is only effective when healthcheckextension is configured in the OpenTelemetry Collector pipeline. // +optional diff --git a/apis/v1beta1/zz_generated.deepcopy.go b/apis/v1beta1/zz_generated.deepcopy.go index b508f0be76..d84fe93a81 100644 --- a/apis/v1beta1/zz_generated.deepcopy.go +++ b/apis/v1beta1/zz_generated.deepcopy.go @@ -308,6 +308,7 @@ func (in *OpenTelemetryCollectorSpec) DeepCopyInto(out *OpenTelemetryCollectorSp in.TargetAllocator.DeepCopyInto(&out.TargetAllocator) in.Config.DeepCopyInto(&out.Config) in.Ingress.DeepCopyInto(&out.Ingress) + in.ExtensionIngress.DeepCopyInto(&out.ExtensionIngress) if in.LivenessProbe != nil { in, out := &in.LivenessProbe, &out.LivenessProbe *out = new(Probe) diff --git a/internal/manifests/collector/collector.go b/internal/manifests/collector/collector.go index 0e4cc414d5..e20e5d1fcf 100644 --- a/internal/manifests/collector/collector.go +++ b/internal/manifests/collector/collector.go @@ -55,6 +55,7 @@ func Build(params manifests.Params) ([]client.Object, error) { manifests.Factory(MonitoringService), manifests.Factory(ExtensionService), manifests.Factory(Ingress), + manifests.Factory(ExtensionIngress), }...) if featuregate.CollectorUsesTargetAllocatorCR.IsEnabled() { diff --git a/internal/manifests/collector/ingress.go b/internal/manifests/collector/ingress.go index 4d775a4b66..4384cf8c3a 100644 --- a/internal/manifests/collector/ingress.go +++ b/internal/manifests/collector/ingress.go @@ -50,9 +50,9 @@ func Ingress(params manifests.Params) (*networkingv1.Ingress, error) { var rules []networkingv1.IngressRule switch params.OtelCol.Spec.Ingress.RuleType { case v1beta1.IngressRuleTypePath, "": - rules = []networkingv1.IngressRule{createPathIngressRules(params.OtelCol.Name, params.OtelCol.Spec.Ingress.Hostname, ports)} + rules = []networkingv1.IngressRule{createPathIngressRules(params.OtelCol.Name, params.OtelCol.Spec.Ingress.Hostname, ports, "service")} case v1beta1.IngressRuleTypeSubdomain: - rules = createSubdomainIngressRules(params.OtelCol.Name, params.OtelCol.Spec.Ingress.Hostname, ports) + rules = createSubdomainIngressRules(params.OtelCol.Name, params.OtelCol.Spec.Ingress.Hostname, ports, "service") } return &networkingv1.Ingress{ @@ -71,40 +71,52 @@ func Ingress(params manifests.Params) (*networkingv1.Ingress, error) { } func ExtensionIngress(params manifests.Params) (*networkingv1.Ingress, error) { - name := naming.Ingress(params.OtelCol.Name) + name := naming.ExtensionIngress(params.OtelCol.Name) labels := manifestutils.Labels(params.OtelCol.ObjectMeta, name, params.OtelCol.Spec.Image, ComponentOpenTelemetryCollector, params.Config.LabelsFilter()) - ports, err := extensionServicePortsFromCfg(params.Log, params.OtelCol) + if params.OtelCol.Spec.ExtensionIngress.Type != v1beta1.IngressTypeIngress { + return nil, nil + } - if err != nil { + ports, err := extensionServicePortsFromCfg(params.Log, params.OtelCol) + if err != nil || len(ports) == 0 { return nil, err } - // if there are no ports, no ingress required - if len(ports) == 0 { - return nil, nil + var rules []networkingv1.IngressRule + switch params.OtelCol.Spec.Ingress.RuleType { + case v1beta1.IngressRuleTypePath, "": + rules = []networkingv1.IngressRule{createPathIngressRules(params.OtelCol.Name, params.OtelCol.Spec.ExtensionIngress.Hostname, ports, "extension")} + case v1beta1.IngressRuleTypeSubdomain: + rules = createSubdomainIngressRules(params.OtelCol.Name, params.OtelCol.Spec.ExtensionIngress.Hostname, ports, "extension") } - rules := createSubdomainIngressRules(name, "", ports) - return &networkingv1.Ingress{ ObjectMeta: metav1.ObjectMeta{ Name: name, Namespace: params.OtelCol.Namespace, - Annotations: params.OtelCol.Spec.Ingress.Annotations, // can the spec annotations be used? + Annotations: params.OtelCol.Spec.ExtensionIngress.Annotations, Labels: labels, }, Spec: networkingv1.IngressSpec{ - TLS: params.OtelCol.Spec.Ingress.TLS, + TLS: params.OtelCol.Spec.ExtensionIngress.TLS, Rules: rules, - IngressClassName: params.OtelCol.Spec.Ingress.IngressClassName, + IngressClassName: params.OtelCol.Spec.ExtensionIngress.IngressClassName, }, }, nil } -func createPathIngressRules(otelcol string, hostname string, ports []corev1.ServicePort) networkingv1.IngressRule { +func createPathIngressRules(otelcol string, hostname string, ports []corev1.ServicePort, serviceType string) networkingv1.IngressRule { pathType := networkingv1.PathTypePrefix paths := make([]networkingv1.HTTPIngressPath, len(ports)) + + var name string + if serviceType == "extension" { + name = naming.ExtensionService(otelcol) + } else { + name = naming.Service(otelcol) + } + for i, port := range ports { portName := naming.PortName(port.Name, port.Port) paths[i] = networkingv1.HTTPIngressPath{ @@ -112,7 +124,7 @@ func createPathIngressRules(otelcol string, hostname string, ports []corev1.Serv PathType: &pathType, Backend: networkingv1.IngressBackend{ Service: &networkingv1.IngressServiceBackend{ - Name: naming.Service(otelcol), + Name: name, Port: networkingv1.ServiceBackendPort{ Name: portName, }, @@ -130,9 +142,17 @@ func createPathIngressRules(otelcol string, hostname string, ports []corev1.Serv } } -func createSubdomainIngressRules(otelcol string, hostname string, ports []corev1.ServicePort) []networkingv1.IngressRule { +func createSubdomainIngressRules(otelcol string, hostname string, ports []corev1.ServicePort, serviceType string) []networkingv1.IngressRule { var rules []networkingv1.IngressRule pathType := networkingv1.PathTypePrefix + + var name string + if serviceType == "extension" { + name = naming.ExtensionService(otelcol) + } else { + name = naming.Service(otelcol) + } + for _, port := range ports { portName := naming.PortName(port.Name, port.Port) @@ -151,7 +171,7 @@ func createSubdomainIngressRules(otelcol string, hostname string, ports []corev1 PathType: &pathType, Backend: networkingv1.IngressBackend{ Service: &networkingv1.IngressServiceBackend{ - Name: naming.Service(otelcol), + Name: name, Port: networkingv1.ServiceBackendPort{ Name: portName, }, diff --git a/internal/manifests/collector/ingress_test.go b/internal/manifests/collector/ingress_test.go index 40e736b7bb..dc4b82c9b4 100644 --- a/internal/manifests/collector/ingress_test.go +++ b/internal/manifests/collector/ingress_test.go @@ -284,3 +284,229 @@ func TestDesiredIngresses(t *testing.T) { }, got) }) } + +func TestExtensionIngress(t *testing.T) { + t.Run("no ingress for incorrect ingress type", func(t *testing.T) { + params := manifests.Params{ + Config: config.Config{}, + Log: logger, + OtelCol: v1beta1.OpenTelemetryCollector{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + }, + Spec: v1beta1.OpenTelemetryCollectorSpec{ + ExtensionIngress: v1beta1.Ingress{ + Type: v1beta1.IngressType("unknown"), + }, + }, + }, + } + actual, err := ExtensionIngress(params) + assert.Nil(t, actual) + assert.NoError(t, err) + }) + t.Run("no ingress if there's no port for extension", func(t *testing.T) { + params := manifests.Params{ + Config: config.Config{}, + Log: logger, + OtelCol: v1beta1.OpenTelemetryCollector{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + }, + Spec: v1beta1.OpenTelemetryCollectorSpec{ + Config: v1beta1.Config{ + Service: v1beta1.Service{ + Extensions: []string{"jaeger_query"}, + }, + Extensions: &v1beta1.AnyConfig{ + Object: map[string]interface{}{}, + }, + }, + ExtensionIngress: v1beta1.Ingress{ + Type: v1beta1.IngressType("ingress"), + }, + }, + }, + } + + actual, err := ExtensionIngress(params) + assert.Nil(t, actual) + assert.NoError(t, err) + }) + t.Run("ingress for extensions for rule type path", func(t *testing.T) { + var ( + ns = "test-ns" + hostname = "example.com" + ingressClassName = "nginx" + pathType = networkingv1.PathTypePrefix + ) + + params := manifests.Params{ + Config: config.Config{}, + Log: logger, + OtelCol: v1beta1.OpenTelemetryCollector{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: ns, + }, + Spec: v1beta1.OpenTelemetryCollectorSpec{ + Config: v1beta1.Config{ + Service: v1beta1.Service{ + Extensions: []string{"jaeger_query"}, + }, + Extensions: &v1beta1.AnyConfig{ + Object: map[string]interface{}{ + "jaeger_query": map[string]interface{}{ + "http": map[string]interface{}{ + "endpoint": "0.0.0.0:16686", + }, + }, + }, + }, + }, + ExtensionIngress: v1beta1.Ingress{ + Type: v1beta1.IngressType("ingress"), + IngressClassName: &ingressClassName, + Hostname: hostname, + Annotations: map[string]string{"some.key": "some.value"}, + RuleType: v1beta1.IngressRuleTypePath, + }, + }, + }, + } + + actual, err := ExtensionIngress(params) + assert.NoError(t, err) + assert.NotNil(t, actual) + assert.NotEqual(t, networkingv1.Ingress{ + ObjectMeta: metav1.ObjectMeta{ + Name: naming.ExtensionIngress(params.OtelCol.Name), + Namespace: ns, + Annotations: params.OtelCol.Spec.ExtensionIngress.Annotations, + Labels: map[string]string{ + "app.kubernetes.io/name": naming.ExtensionIngress(params.OtelCol.Name), + "app.kubernetes.io/instance": fmt.Sprintf("%s.%s", params.OtelCol.Namespace, params.OtelCol.Name), + "app.kubernetes.io/managed-by": "opentelemetry-operator", + "app.kubernetes.io/component": "opentelemetry-collector", + "app.kubernetes.io/part-of": "opentelemetry", + "app.kubernetes.io/version": "latest", + }, + }, + Spec: networkingv1.IngressSpec{ + IngressClassName: &ingressClassName, + Rules: []networkingv1.IngressRule{ + { + Host: hostname, + IngressRuleValue: networkingv1.IngressRuleValue{ + HTTP: &networkingv1.HTTPIngressRuleValue{ + Paths: []networkingv1.HTTPIngressPath{ + { + Path: "/jaeger-query", + PathType: &pathType, + Backend: networkingv1.IngressBackend{ + Service: &networkingv1.IngressServiceBackend{ + Name: naming.ExtensionService(params.OtelCol.Name), + Port: networkingv1.ServiceBackendPort{ + Name: "jaeger-query", + Number: 16686, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, actual) + }) + t.Run("ingress for extensions for rule type subdomain", func(t *testing.T) { + var ( + ns = "test-ns" + hostname = "example.com" + ingressClassName = "nginx" + pathType = networkingv1.PathTypePrefix + ) + + params := manifests.Params{ + Config: config.Config{}, + Log: logger, + OtelCol: v1beta1.OpenTelemetryCollector{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: ns, + }, + Spec: v1beta1.OpenTelemetryCollectorSpec{ + Config: v1beta1.Config{ + Service: v1beta1.Service{ + Extensions: []string{"jaeger_query"}, + }, + Extensions: &v1beta1.AnyConfig{ + Object: map[string]interface{}{ + "jaeger_query": map[string]interface{}{ + "http": map[string]interface{}{ + "endpoint": "0.0.0.0:16686", + }, + }, + }, + }, + }, + ExtensionIngress: v1beta1.Ingress{ + Type: v1beta1.IngressType("ingress"), + IngressClassName: &ingressClassName, + Hostname: hostname, + Annotations: map[string]string{"some.key": "some.value"}, + RuleType: v1beta1.IngressRuleTypeSubdomain, + }, + }, + }, + } + + actual, err := ExtensionIngress(params) + assert.NoError(t, err) + assert.NotNil(t, actual) + assert.NotEqual(t, networkingv1.Ingress{ + ObjectMeta: metav1.ObjectMeta{ + Name: naming.ExtensionIngress(params.OtelCol.Name), + Namespace: ns, + Annotations: params.OtelCol.Spec.ExtensionIngress.Annotations, + Labels: map[string]string{ + "app.kubernetes.io/name": naming.ExtensionIngress(params.OtelCol.Name), + "app.kubernetes.io/instance": fmt.Sprintf("%s.%s", params.OtelCol.Namespace, params.OtelCol.Name), + "app.kubernetes.io/managed-by": "opentelemetry-operator", + "app.kubernetes.io/component": "opentelemetry-collector", + "app.kubernetes.io/part-of": "opentelemetry", + "app.kubernetes.io/version": "latest", + }, + }, + Spec: networkingv1.IngressSpec{ + IngressClassName: &ingressClassName, + Rules: []networkingv1.IngressRule{ + { + Host: "jaeger-query." + hostname, + IngressRuleValue: networkingv1.IngressRuleValue{ + HTTP: &networkingv1.HTTPIngressRuleValue{ + Paths: []networkingv1.HTTPIngressPath{ + { + Path: "/", + PathType: &pathType, + Backend: networkingv1.IngressBackend{ + Service: &networkingv1.IngressServiceBackend{ + Name: naming.ExtensionService(params.OtelCol.Name), + Port: networkingv1.ServiceBackendPort{ + Name: "jaeger-query", + Number: 16686, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, actual) + }) +} diff --git a/internal/naming/main.go b/internal/naming/main.go index 149a9f9d5a..8794ea4d95 100644 --- a/internal/naming/main.go +++ b/internal/naming/main.go @@ -131,6 +131,11 @@ func Ingress(otelcol string) string { return DNSName(Truncate("%s-ingress", 63, otelcol)) } +// Extension Ingress builds the ingress name for the extensions +func ExtensionIngress(otelcol string) string { + return DNSName(Truncate("%s-extension-ingress", 63, otelcol)) +} + // Route builds the route name based on the instance. func Route(otelcol string, prefix string) string { return DNSName(Truncate("%s-%s-route", 63, prefix, otelcol))