From 7c573a9ffe543a33d3f1aae439e3540bac303f05 Mon Sep 17 00:00:00 2001 From: Ludovic Ortega Date: Thu, 27 Jun 2024 20:52:07 +0200 Subject: [PATCH] [extension/observer/k8sobserver] add k8s.ingress endpoint (#33005) **Description:** Add support for k8s.ingress endpoint As described in the issue, this will allow users to dynamically obtain Kubernetes ingress ressource, facilitating the monitoring of certificate expiration with the Blackbox Exporter for example. I didn't create a global ingress resource with sub-endpoints for each path because I don't see a relevant 'target'. I'm uncertain about the impact of my decision regarding the 'UID' as it's structured as '{namespace}/{ingress UID}/{host}{path}'. Since a path automatically starts with a '/', and can contain other '/', should I escape them ? **Link to tracking Issue:** #32971 --------- Signed-off-by: Ludovic Ortega Co-authored-by: Chris Mark --- .chloggen/feat_k8sobserver_ingress.yaml | 27 +++++ extension/observer/endpoints.go | 39 +++++++ extension/observer/k8sobserver/README.md | 6 +- extension/observer/k8sobserver/config.go | 6 +- extension/observer/k8sobserver/config_test.go | 13 ++- extension/observer/k8sobserver/handler.go | 20 ++++ .../observer/k8sobserver/handler_test.go | 64 +++++++++++ .../observer/k8sobserver/ingress_endpoint.go | 103 ++++++++++++++++++ .../k8sobserver/ingress_endpoint_test.go | 59 ++++++++++ .../observer/k8sobserver/k8s_fixtures_test.go | 92 ++++++++++++++++ .../observer/k8sobserver/testdata/config.yaml | 2 + receiver/receivercreator/README.md | 29 ++++- receiver/receivercreator/config.go | 2 +- receiver/receivercreator/config_test.go | 1 + receiver/receivercreator/factory.go | 3 + receiver/receivercreator/rules.go | 2 +- receiver/receivercreator/testdata/config.yaml | 2 + 17 files changed, 457 insertions(+), 13 deletions(-) create mode 100644 .chloggen/feat_k8sobserver_ingress.yaml create mode 100644 extension/observer/k8sobserver/ingress_endpoint.go create mode 100644 extension/observer/k8sobserver/ingress_endpoint_test.go diff --git a/.chloggen/feat_k8sobserver_ingress.yaml b/.chloggen/feat_k8sobserver_ingress.yaml new file mode 100644 index 000000000000..4fa82c75ba6d --- /dev/null +++ b/.chloggen/feat_k8sobserver_ingress.yaml @@ -0,0 +1,27 @@ +# Use this changelog template to create an entry for release notes. + +# 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. filelogreceiver) +component: k8sobserver + +# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`). +note: Add support for k8s.ingress endpoint. + +# Mandatory: One or more tracking issues related to the change. You can use the PR number here if no issue exists. +issues: [32971] + +# (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: + +# If your change doesn't affect end users or the exported elements of any package, +# you should instead start your pull request title with [chore] or use the "Skip Changelog" label. +# Optional: The change log or logs in which this entry should be included. +# e.g. '[user]' or '[user, api]' +# Include 'user' if the change is relevant to end users. +# Include 'api' if there is a change to a library API. +# Default: '[user]' +change_logs: [] diff --git a/extension/observer/endpoints.go b/extension/observer/endpoints.go index 86cfc5e8e72a..2a9493bb2f60 100644 --- a/extension/observer/endpoints.go +++ b/extension/observer/endpoints.go @@ -26,6 +26,8 @@ const ( PodType EndpointType = "pod" // K8sServiceType is a service endpoint. K8sServiceType EndpointType = "k8s.service" + // K8sIngressType is a ingress endpoint. + K8sIngressType EndpointType = "k8s.ingress" // K8sNodeType is a Kubernetes Node endpoint. K8sNodeType EndpointType = "k8s.node" // HostPortType is a hostport endpoint. @@ -151,6 +153,43 @@ func (s *K8sService) Type() EndpointType { return K8sServiceType } +// K8sIngress is a discovered k8s ingress. +type K8sIngress struct { + // Name of the ingress. + Name string + // UID is the unique ID in the cluster for the ingress. + UID string + // Labels is a map of user-specified metadata. + Labels map[string]string + // Annotations is a map of user-specified metadata. + Annotations map[string]string + // Namespace must be unique for ingress with same name. + Namespace string + // Scheme represents whether the ingress path is accessible via HTTPS or HTTP. + Scheme string + // Host is the fully qualified domain name of a network host + Host string + // Path that map requests to backends + Path string +} + +func (s *K8sIngress) Env() EndpointEnv { + return map[string]any{ + "uid": s.UID, + "name": s.Name, + "labels": s.Labels, + "annotations": s.Annotations, + "namespace": s.Namespace, + "scheme": s.Scheme, + "host": s.Host, + "path": s.Path, + } +} + +func (s *K8sIngress) Type() EndpointType { + return K8sIngressType +} + // Pod is a discovered k8s pod. type Pod struct { // Name of the pod. diff --git a/extension/observer/k8sobserver/README.md b/extension/observer/k8sobserver/README.md index ea77d04dc120..9375a7a02efd 100644 --- a/extension/observer/k8sobserver/README.md +++ b/extension/observer/k8sobserver/README.md @@ -13,7 +13,7 @@ The `k8s_observer` is a [Receiver Creator](../../../receiver/receivercreator/README.md)-compatible "watch observer" that will detect and report -Kubernetes pod, port, service and node endpoints via the Kubernetes API. +Kubernetes pod, port, service, ingress and node endpoints via the Kubernetes API. ## Example Config @@ -25,6 +25,7 @@ extensions: observe_pods: true observe_nodes: true observe_services: true + observe_ingresses: true receivers: receiver_creator: @@ -70,4 +71,5 @@ All fields are optional. | node | string | | The node name to limit the discovery of pod, port, and node endpoints. Providing no value (the default) results in discovering endpoints for all available nodes. | | observe_pods | bool | `true` | Whether to report observer pod and port endpoints. If `true` and `node` is specified it will only discover pod and port endpoints whose `spec.nodeName` matches the provided node name. If `true` and `node` isn't specified, it will discover all available pod and port endpoints. Please note that Collector connectivity to pods from other nodes is dependent on your cluster configuration and isn't guaranteed. | | observe_nodes | bool | `false` | Whether to report observer k8s.node endpoints. If `true` and `node` is specified it will only discover node endpoints whose `metadata.name` matches the provided node name. If `true` and `node` isn't specified, it will discover all available node endpoints. Please note that Collector connectivity to nodes is dependent on your cluster configuration and isn't guaranteed.| -| observe_services | bool | `false` | Whether to report observer k8s.service endpoints.| +| observe_services | bool | `false` | Whether to report observer k8s.service endpoints.| +| observe_ingresses | bool | `false` | Whether to report observer k8s.ingress endpoints.| diff --git a/extension/observer/k8sobserver/config.go b/extension/observer/k8sobserver/config.go index 9f202be4c246..24821c894507 100644 --- a/extension/observer/k8sobserver/config.go +++ b/extension/observer/k8sobserver/config.go @@ -36,12 +36,14 @@ type Config struct { ObserveNodes bool `mapstructure:"observe_nodes"` // ObserveServices determines whether to report observer service and port endpoints. `false` by default. ObserveServices bool `mapstructure:"observe_services"` + // ObserveIngresses determines whether to report observer ingress. `false` by default. + ObserveIngresses bool `mapstructure:"observe_ingresses"` } // Validate checks if the extension configuration is valid func (cfg *Config) Validate() error { - if !cfg.ObservePods && !cfg.ObserveNodes && !cfg.ObserveServices { - return fmt.Errorf("one of observe_pods, observe_nodes and observe_services must be true") + if !cfg.ObservePods && !cfg.ObserveNodes && !cfg.ObserveServices && !cfg.ObserveIngresses { + return fmt.Errorf("one of observe_pods, observe_nodes, observe_services and observe_ingresses must be true") } return nil } diff --git a/extension/observer/k8sobserver/config_test.go b/extension/observer/k8sobserver/config_test.go index 551303679508..65148580d1dc 100644 --- a/extension/observer/k8sobserver/config_test.go +++ b/extension/observer/k8sobserver/config_test.go @@ -39,11 +39,12 @@ func TestLoadConfig(t *testing.T) { { id: component.NewIDWithName(metadata.Type, "observe-all"), expected: &Config{ - Node: "", - APIConfig: k8sconfig.APIConfig{AuthType: k8sconfig.AuthTypeNone}, - ObservePods: true, - ObserveNodes: true, - ObserveServices: true, + Node: "", + APIConfig: k8sconfig.APIConfig{AuthType: k8sconfig.AuthTypeNone}, + ObservePods: true, + ObserveNodes: true, + ObserveServices: true, + ObserveIngresses: true, }, }, { @@ -52,7 +53,7 @@ func TestLoadConfig(t *testing.T) { }, { id: component.NewIDWithName(metadata.Type, "invalid_no_observing"), - expectedErr: "one of observe_pods, observe_nodes and observe_services must be true", + expectedErr: "one of observe_pods, observe_nodes, observe_services and observe_ingresses must be true", }, } for _, tt := range tests { diff --git a/extension/observer/k8sobserver/handler.go b/extension/observer/k8sobserver/handler.go index 859738a5c9c0..f1c89bb24b4b 100644 --- a/extension/observer/k8sobserver/handler.go +++ b/extension/observer/k8sobserver/handler.go @@ -9,6 +9,7 @@ import ( "go.uber.org/zap" v1 "k8s.io/api/core/v1" + networkingv1 "k8s.io/api/networking/v1" "k8s.io/client-go/tools/cache" "github.com/open-telemetry/opentelemetry-collector-contrib/extension/observer" @@ -49,6 +50,8 @@ func (h *handler) OnAdd(objectInterface any, _ bool) { endpoints = convertPodToEndpoints(h.idNamespace, object) case *v1.Service: endpoints = convertServiceToEndpoints(h.idNamespace, object) + case *networkingv1.Ingress: + endpoints = convertIngressToEndpoints(h.idNamespace, object) case *v1.Node: endpoints = append(endpoints, convertNodeToEndpoint(h.idNamespace, object)) default: // unsupported @@ -92,6 +95,19 @@ func (h *handler) OnUpdate(oldObjectInterface, newObjectInterface any) { newEndpoints[e.ID] = e } + case *networkingv1.Ingress: + newIngress, ok := newObjectInterface.(*networkingv1.Ingress) + if !ok { + h.logger.Warn("skip updating endpoint for ingress as the update is of different type", zap.Any("oldIngress", oldObjectInterface), zap.Any("newObject", newObjectInterface)) + return + } + for _, e := range convertIngressToEndpoints(h.idNamespace, oldObject) { + oldEndpoints[e.ID] = e + } + for _, e := range convertIngressToEndpoints(h.idNamespace, newIngress) { + newEndpoints[e.ID] = e + } + case *v1.Node: newNode, ok := newObjectInterface.(*v1.Node) if !ok { @@ -165,6 +181,10 @@ func (h *handler) OnDelete(objectInterface any) { if object != nil { endpoints = convertServiceToEndpoints(h.idNamespace, object) } + case *networkingv1.Ingress: + if object != nil { + endpoints = convertIngressToEndpoints(h.idNamespace, object) + } case *v1.Node: if object != nil { endpoints = append(endpoints, convertNodeToEndpoint(h.idNamespace, object)) diff --git a/extension/observer/k8sobserver/handler_test.go b/extension/observer/k8sobserver/handler_test.go index 402b5fcc4213..8e813c650a7a 100644 --- a/extension/observer/k8sobserver/handler_test.go +++ b/extension/observer/k8sobserver/handler_test.go @@ -165,6 +165,70 @@ func TestServiceEndpointsChanged(t *testing.T) { }, th.ListEndpoints()) } +func TestIngressEndpointsAdded(t *testing.T) { + th := newTestHandler() + th.OnAdd(ingress, true) + assert.ElementsMatch(t, []observer.Endpoint{ + { + ID: "test-1/ingress-1-UID/host-1/", + Target: "https://host-1/", + Details: &observer.K8sIngress{ + Name: "application-ingress", + Namespace: "default", + UID: "test-1/ingress-1-UID/host-1/", + Labels: map[string]string{"env": "prod"}, + Scheme: "https", + Host: "host-1", + Path: "/", + }, + }}, th.ListEndpoints()) +} + +func TestIngressEndpointsRemoved(t *testing.T) { + th := newTestHandler() + th.OnAdd(ingress, true) + th.OnDelete(ingress) + assert.Empty(t, th.ListEndpoints()) +} + +func TestIngressEndpointsChanged(t *testing.T) { + th := newTestHandler() + // Nothing changed. + th.OnUpdate(ingress, ingress) + require.Empty(t, th.ListEndpoints()) + + // Labels changed. + changedLabels := ingress.DeepCopy() + changedLabels.Labels["new-label"] = "value" + th.OnUpdate(ingress, changedLabels) + + endpoints := th.ListEndpoints() + require.ElementsMatch(t, + []observer.EndpointID{"test-1/ingress-1-UID/host-1/"}, + []observer.EndpointID{endpoints[0].ID}, + ) + + // Running state changed, one added and one removed. + updatedIngress := ingress.DeepCopy() + updatedIngress.Labels["updated-label"] = "true" + th.OnUpdate(ingress, updatedIngress) + require.ElementsMatch(t, []observer.Endpoint{ + { + ID: "test-1/ingress-1-UID/host-1/", + Target: "https://host-1/", + Details: &observer.K8sIngress{ + Name: "application-ingress", + Namespace: "default", + UID: "test-1/ingress-1-UID/host-1/", + Labels: map[string]string{"env": "prod", "updated-label": "true"}, + Scheme: "https", + Host: "host-1", + Path: "/", + }, + }, + }, th.ListEndpoints()) +} + func TestNodeEndpointsAdded(t *testing.T) { th := newTestHandler() th.OnAdd(node1V1, true) diff --git a/extension/observer/k8sobserver/ingress_endpoint.go b/extension/observer/k8sobserver/ingress_endpoint.go new file mode 100644 index 000000000000..57c6a1af3a16 --- /dev/null +++ b/extension/observer/k8sobserver/ingress_endpoint.go @@ -0,0 +1,103 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package k8sobserver // import "github.com/open-telemetry/opentelemetry-collector-contrib/extension/observer/k8sobserver" + +import ( + "fmt" + "net/url" + "strings" + + v1 "k8s.io/api/networking/v1" + + "github.com/open-telemetry/opentelemetry-collector-contrib/extension/observer" +) + +// convertIngressToEndpoints converts a ingress instance into a slice of endpoints. The endpoints +// include an endpoint for each path that is mapped to an ingress. +func convertIngressToEndpoints(idNamespace string, ingress *v1.Ingress) []observer.Endpoint { + endpoints := []observer.Endpoint{} + + // Loop through every ingress rule to get every defined path. + for _, rule := range ingress.Spec.Rules { + scheme := getScheme(rule.Host, getTLSHosts(ingress)) + + if rule.HTTP != nil { + // Create endpoint for each ingress rule. + for _, path := range rule.HTTP.Paths { + endpointID := observer.EndpointID(fmt.Sprintf("%s/%s/%s%s", idNamespace, ingress.UID, rule.Host, path.Path)) + endpoints = append(endpoints, observer.Endpoint{ + ID: endpointID, + Target: (&url.URL{ + Scheme: scheme, + Host: rule.Host, + Path: path.Path, + }).String(), + Details: &observer.K8sIngress{ + Name: ingress.Name, + UID: string(endpointID), + Labels: ingress.Labels, + Annotations: ingress.Annotations, + Namespace: ingress.Namespace, + Scheme: scheme, + Host: rule.Host, + Path: path.Path, + }, + }) + } + } + + } + + return endpoints +} + +// getTLSHosts return a list of tls hosts for an ingress ressource. +func getTLSHosts(i *v1.Ingress) []string { + var hosts []string + + for _, tls := range i.Spec.TLS { + hosts = append(hosts, tls.Hosts...) + } + + return hosts +} + +// matchesHostPattern returns true if the host matches the host pattern or wildcard pattern. +func matchesHostPattern(pattern string, host string) bool { + // if host match the pattern (host pattern). + if pattern == host { + return true + } + + // if string does not contains any dot, don't do the next part as it's for wildcard pattern. + if !strings.Contains(host, ".") { + return false + } + + patternParts := strings.Split(pattern, ".") + hostParts := strings.Split(host, ".") + + // If the first part of the pattern is not a wildcard pattern. + if patternParts[0] != "*" { + return false + } + + // If host and pattern without wildcard part does not match. + if strings.Join(patternParts[1:], ".") != strings.Join(hostParts[1:], ".") { + return false + } + + return true +} + +// getScheme return the scheme of an ingress host based on tls configuration. +func getScheme(host string, tlsHosts []string) string { + for _, pattern := range tlsHosts { + if matchesHostPattern(pattern, host) { + return "https" + } + } + + return "http" +} diff --git a/extension/observer/k8sobserver/ingress_endpoint_test.go b/extension/observer/k8sobserver/ingress_endpoint_test.go new file mode 100644 index 000000000000..dcdb67c1da10 --- /dev/null +++ b/extension/observer/k8sobserver/ingress_endpoint_test.go @@ -0,0 +1,59 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package k8sobserver // import "github.com/open-telemetry/opentelemetry-collector-contrib/extension/observer/k8sobserver" + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/open-telemetry/opentelemetry-collector-contrib/extension/observer" +) + +func TestIngressObjectToPortEndpoint(t *testing.T) { + expectedEndpoints := []observer.Endpoint{ + { + ID: "namespace/ingress-1-UID/host-1/", + Target: "https://host-1/", + Details: &observer.K8sIngress{ + Name: "application-ingress", + Namespace: "default", + UID: "namespace/ingress-1-UID/host-1/", + Labels: map[string]string{"env": "prod"}, + Scheme: "https", + Host: "host-1", + Path: "/", + }, + }, + { + ID: "namespace/ingress-1-UID/host.2.host/", + Target: "https://host.2.host/", + Details: &observer.K8sIngress{ + Name: "application-ingress", + Namespace: "default", + UID: "namespace/ingress-1-UID/host.2.host/", + Labels: map[string]string{"env": "prod"}, + Scheme: "https", + Host: "host.2.host", + Path: "/", + }, + }, + { + ID: "namespace/ingress-1-UID/host.3.host/test", + Target: "http://host.3.host/test", + Details: &observer.K8sIngress{ + Name: "application-ingress", + Namespace: "default", + UID: "namespace/ingress-1-UID/host.3.host/test", + Labels: map[string]string{"env": "prod"}, + Scheme: "http", + Host: "host.3.host", + Path: "/test", + }, + }, + } + + endpoints := convertIngressToEndpoints("namespace", ingressMultipleHost) + require.Equal(t, expectedEndpoints, endpoints) +} diff --git a/extension/observer/k8sobserver/k8s_fixtures_test.go b/extension/observer/k8sobserver/k8s_fixtures_test.go index 4f0acd4aa273..887f01a0eef1 100644 --- a/extension/observer/k8sobserver/k8s_fixtures_test.go +++ b/extension/observer/k8sobserver/k8s_fixtures_test.go @@ -5,6 +5,7 @@ package k8sobserver import ( v1 "k8s.io/api/core/v1" + networkingv1 "k8s.io/api/networking/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" ) @@ -134,6 +135,97 @@ var serviceWithClusterIPV2 = func() *v1.Service { return service }() +var ingress = &networkingv1.Ingress{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "application-ingress", + UID: types.UID("ingress-1-UID"), + Labels: map[string]string{ + "env": "prod", + }, + }, + Spec: networkingv1.IngressSpec{ + Rules: []networkingv1.IngressRule{ + { + Host: "host-1", + IngressRuleValue: networkingv1.IngressRuleValue{ + HTTP: &networkingv1.HTTPIngressRuleValue{ + Paths: []networkingv1.HTTPIngressPath{ + { + Path: "/", + }, + }, + }, + }, + }, + }, + TLS: []networkingv1.IngressTLS{ + { + Hosts: []string{"host-1"}, + }, + }, + }, +} + +var ingressMultipleHost = &networkingv1.Ingress{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "application-ingress", + UID: types.UID("ingress-1-UID"), + Labels: map[string]string{ + "env": "prod", + }, + }, + Spec: networkingv1.IngressSpec{ + Rules: []networkingv1.IngressRule{ + { + Host: "host-invalid", + }, + { + Host: "host-1", + IngressRuleValue: networkingv1.IngressRuleValue{ + HTTP: &networkingv1.HTTPIngressRuleValue{ + Paths: []networkingv1.HTTPIngressPath{ + { + Path: "/", + }, + }, + }, + }, + }, + { + Host: "host.2.host", + IngressRuleValue: networkingv1.IngressRuleValue{ + HTTP: &networkingv1.HTTPIngressRuleValue{ + Paths: []networkingv1.HTTPIngressPath{ + { + Path: "/", + }, + }, + }, + }, + }, + { + Host: "host.3.host", + IngressRuleValue: networkingv1.IngressRuleValue{ + HTTP: &networkingv1.HTTPIngressRuleValue{ + Paths: []networkingv1.HTTPIngressPath{ + { + Path: "/test", + }, + }, + }, + }, + }, + }, + TLS: []networkingv1.IngressTLS{ + { + Hosts: []string{"host-1", "*.2.host"}, + }, + }, + }, +} + // newNode is a helper function for creating Nodes for testing. func newNode(name, hostname string) *v1.Node { return &v1.Node{ diff --git a/extension/observer/k8sobserver/testdata/config.yaml b/extension/observer/k8sobserver/testdata/config.yaml index 8e6163829025..6ff12eadc1e8 100644 --- a/extension/observer/k8sobserver/testdata/config.yaml +++ b/extension/observer/k8sobserver/testdata/config.yaml @@ -7,9 +7,11 @@ k8s_observer/observe-all: observe_nodes: true observe_pods: true observe_services: true + observe_ingresses: true k8s_observer/invalid_auth: auth_type: not a real auth type k8s_observer/invalid_no_observing: observe_nodes: false observe_pods: false observe_services: false + observe_ingresses: false diff --git a/receiver/receivercreator/README.md b/receiver/receivercreator/README.md index 5008cc342c3b..971b7e95c9d4 100644 --- a/receiver/receivercreator/README.md +++ b/receiver/receivercreator/README.md @@ -222,6 +222,21 @@ targeting it will have different variables available. | service_type | The type of the kubernetes service: ClusterIP, NodePort, LoadBalancer, ExternalName | String | | cluster_ip | The cluster IP assigned to the service | String | +### Kubernetes Ingress + +| Variable | Description | Data Type | +|----------------|---------------------------------------------------------------------------------------|-------------------------------| +| type | `"k8s.ingress"` | String | +| id | ID of source endpoint | String | +| name | The name of the Kubernetes ingress | String | +| namespace | The namespace of the ingress | String | +| uid | The unique ID for the ingress | String | +| labels | The map of labels set on the ingress | Map with String key and value | +| annotations | The map of annotations set on the ingress | Map with String key and value | +| scheme | Scheme represents whether the ingress path is accessible via HTTPS or HTTP. | String | +| host | Host is the FQDN that map to backends | String | +| path | Path that map requests to backends | String | + ### Kubernetes Node | Variable | Description | Data Type | @@ -247,6 +262,7 @@ extensions: k8s_observer: observe_nodes: true observe_services: true + observe_ingresses: true host_observer: receivers: @@ -326,6 +342,17 @@ receivers: - endpoint: 'http://`endpoint`:`"prometheus.io/port" in annotations ? annotations["prometheus.io/port"] : 9090``"prometheus.io/path" in annotations ? annotations["prometheus.io/path"] : "/health"`' method: GET collection_interval: 10s + receiver_creator/4: + watch_observers: [k8s_observer] + receivers: + httpcheck: + # Configure probing if standard prometheus annotations are set on the pod. + rule: type == "k8s.ingress" && annotations["prometheus.io/probe"] == "true" + config: + targets: + - endpoint: '`scheme`://`endpoint`:`port``"prometheus.io/path" in annotations ? annotations["prometheus.io/path"] : "/health"`' + method: GET + collection_interval: 10s processors: exampleprocessor: @@ -336,7 +363,7 @@ exporters: service: pipelines: metrics: - receivers: [receiver_creator/1, receiver_creator/2, receiver_creator/3] + receivers: [receiver_creator/1, receiver_creator/2, receiver_creator/3, receiver_creator/4] processors: [exampleprocessor] exporters: [exampleexporter] extensions: [k8s_observer, host_observer] diff --git a/receiver/receivercreator/config.go b/receiver/receivercreator/config.go index 281a9e0feadf..c12109946910 100644 --- a/receiver/receivercreator/config.go +++ b/receiver/receivercreator/config.go @@ -92,7 +92,7 @@ func (cfg *Config) Unmarshal(componentParser *confmap.Conf) error { for endpointType := range cfg.ResourceAttributes { switch endpointType { - case observer.ContainerType, observer.K8sServiceType, observer.HostPortType, observer.K8sNodeType, observer.PodType, observer.PortType: + case observer.ContainerType, observer.K8sServiceType, observer.K8sIngressType, observer.HostPortType, observer.K8sNodeType, observer.PodType, observer.PortType: default: return fmt.Errorf("resource attributes for unsupported endpoint type %q", endpointType) } diff --git a/receiver/receivercreator/config_test.go b/receiver/receivercreator/config_test.go index 7d4dbd9d0540..c58700c35cfc 100644 --- a/receiver/receivercreator/config_test.go +++ b/receiver/receivercreator/config_test.go @@ -114,6 +114,7 @@ func TestLoadConfig(t *testing.T) { observer.PortType: {"port.key": "port.value"}, observer.HostPortType: {"hostport.key": "hostport.value"}, observer.K8sServiceType: {"k8s.service.key": "k8s.service.value"}, + observer.K8sIngressType: {"k8s.ingress.key": "k8s.ingress.value"}, observer.K8sNodeType: {"k8s.node.key": "k8s.node.value"}, }, }, diff --git a/receiver/receivercreator/factory.go b/receiver/receivercreator/factory.go index fd07f1d90092..abba7f8139fe 100644 --- a/receiver/receivercreator/factory.go +++ b/receiver/receivercreator/factory.go @@ -42,6 +42,9 @@ func createDefaultConfig() component.Config { observer.K8sServiceType: map[string]string{ conventions.AttributeK8SNamespaceName: "`namespace`", }, + observer.K8sIngressType: map[string]string{ + conventions.AttributeK8SNamespaceName: "`namespace`", + }, observer.PortType: map[string]string{ conventions.AttributeK8SPodName: "`pod.name`", conventions.AttributeK8SPodUID: "`pod.uid`", diff --git a/receiver/receivercreator/rules.go b/receiver/receivercreator/rules.go index 1e8d68715948..422e7813ff1b 100644 --- a/receiver/receivercreator/rules.go +++ b/receiver/receivercreator/rules.go @@ -22,7 +22,7 @@ type rule struct { // ruleRe is used to verify the rule starts type check. var ruleRe = regexp.MustCompile( - fmt.Sprintf(`^type\s*==\s*(%q|%q|%q|%q|%q|%q)`, observer.PodType, observer.K8sServiceType, observer.PortType, observer.HostPortType, observer.ContainerType, observer.K8sNodeType), + fmt.Sprintf(`^type\s*==\s*(%q|%q|%q|%q|%q|%q|%q)`, observer.PodType, observer.K8sServiceType, observer.K8sIngressType, observer.PortType, observer.HostPortType, observer.ContainerType, observer.K8sNodeType), ) // newRule creates a new rule instance. diff --git a/receiver/receivercreator/testdata/config.yaml b/receiver/receivercreator/testdata/config.yaml index f0632e39de08..040e0e04d92f 100644 --- a/receiver/receivercreator/testdata/config.yaml +++ b/receiver/receivercreator/testdata/config.yaml @@ -27,5 +27,7 @@ receiver_creator/1: hostport.key: hostport.value k8s.service: k8s.service.key: k8s.service.value + k8s.ingress: + k8s.ingress.key: k8s.ingress.value k8s.node: k8s.node.key: k8s.node.value