diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..485dee6 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.idea diff --git a/stackpacks/otel-k8s-crd/icons/cr.svg b/stackpacks/otel-k8s-crd/icons/cr.svg index 2fa473b..efefc1c 100644 --- a/stackpacks/otel-k8s-crd/icons/cr.svg +++ b/stackpacks/otel-k8s-crd/icons/cr.svg @@ -1,83 +1,4 @@ - - - - - - - - - - image/svg+xml - - - - - - - - - - - - + + + diff --git a/stackpacks/otel-k8s-crd/icons/crd.svg b/stackpacks/otel-k8s-crd/icons/crd.svg index c59c325..ff6700b 100644 --- a/stackpacks/otel-k8s-crd/icons/crd.svg +++ b/stackpacks/otel-k8s-crd/icons/crd.svg @@ -1,83 +1,4 @@ - - - - - - - - - - image/svg+xml - - - - - - - - - - - - + + + diff --git a/stackpacks/otel-k8s-crd/resources/configuration.md b/stackpacks/otel-k8s-crd/resources/configuration.md index 726ae2e..bd07d34 100644 --- a/stackpacks/otel-k8s-crd/resources/configuration.md +++ b/stackpacks/otel-k8s-crd/resources/configuration.md @@ -1,110 +1,32 @@ ## Prerequisites -- SUSE Observability Agent installed with the k8s resource collector enabled -- Network connectivity from the agent to the platform's OTLP ingest endpoint +- SUSE Observability Agent installed with StackPacks 2.0 support enabled +- The k8s resource collector enabled in the agent +- Network connectivity from the agent to the platform OTLP ingest endpoint -## Configuration +## Minimal Configuration -Enable StackPacks 2.0 support in your agent Helm values. This activates the OTel components, including the k8s resource collector. The collector is enabled by default when OTel is active, but can still be configured under `otel.k8sResourceCollector`: +The k8s resource collector is enabled by default when StackPacks 2.0 support is enabled. Configure which Custom Resource API groups are collected with `otel.k8sResourceCollector.crDiscovery.apiGroups`: ```yaml global: features: experimentalStackpacks: true -otel: - k8sResourceCollector: - crdDiscovery: - discoveryMode: api_groups # or "all" to watch every API group - snapshotInterval: 5m # periodic full-state emission from the informer cache (min: 1m) -``` - -If you are not enabling StackPacks 2.0 globally, set `otel.enabled: true` instead. - -### API Group Filtering -`apiGroupFilters.include` and `apiGroupFilters.exclude` are maps of pattern → bool. Wildcards are supported (e.g. `*.example.com`). To disable a default pattern from an overlay values file, set its value to `false`: - -```yaml otel: k8sResourceCollector: - crdDiscovery: + crDiscovery: discoveryMode: api_groups - apiGroupFilters: + apiGroups: include: - # Disable the chart default `"*": true` so only the patterns below match. - "*": false - "*.example.com": true + "policies.kubewarden.io": true + "kubevirt.io": true exclude: "internal.example.com": true ``` -The chart default is `include: { "*": true }`. There must be at least one truthy entry when `discoveryMode` is `api_groups`. - -### Restricting RBAC - -By default the collector is granted wildcard read permissions for every custom resource in the cluster. To restrict it to specific API groups, set `useWildcard: false` and list the groups under `crdApiGroups` (map of group → bool, same disable semantics): - -```yaml -otel: - k8sResourceCollector: - rbac: - useWildcard: false - crdApiGroups: - "policies.kubewarden.io": true - "longhorn.io": true -``` - -### Watching Additional Kubernetes Resources - -`objects` lets you watch arbitrary Kubernetes resources (built-ins or third-party) alongside the CRD-discovered custom resources. Each entry is keyed by the resource plural (required) and maps to a spec: - -```yaml -otel: - k8sResourceCollector: - objects: - pods: - group: "" # core group - namespaces: ["kube-system"] - deployments: - group: apps - labelSelector: "app=foo" - fieldSelector: "status.phase=Running" -``` - -Spec fields: - -- `group` — API group; empty string (`""`) for the core group -- `version` — optional; defaults to the API server's preferred version -- `namespaces` — optional; cluster-wide when empty -- `labelSelector` / `fieldSelector` — optional Kubernetes selectors - -Entries that overlap a CRD covered by `crdDiscovery.apiGroupFilters` are rejected at startup. When `rbac.useWildcard: false`, the chart auto-derives `get`/`list`/`watch` RBAC for each `objects` entry (deduped per group) — no need to add the group to `crdApiGroups`. - -To disable a default `objects` entry from an overlay file, set its value to `null` or `false`. - -`deniedObjects` extends the receiver's built-in denylist (core `Secrets`, `ConfigMaps`) with resources that must not appear under `objects`. The key is the resource plural; the spec only needs `group`: - -```yaml -otel: - k8sResourceCollector: - deniedObjects: - certificates: - group: cert-manager.io -``` - -### OTLP Endpoint Override - -By default, telemetry is sent to `/otel` over HTTP (proxied via the platform's Envoy router; the default path is HTTP-only). If your platform exposes a dedicated OTLP ingress, override the OTel platform endpoint. HTTP endpoints take precedence over gRPC endpoints when both are configured. - -```yaml -otel: - # gRPC: bare host:port, no scheme. A port is required. - platformGrpcOtlpEndpoint: otlp-my-instance.stackstate.io:443 -``` +The chart does not collect every CR API group by default. Enabled integration presets add common SUSE-related API groups, and you can add more with `crDiscovery.apiGroups.include`. Set `discoveryMode: all` to collect CRs for every CRD API group. CRDs themselves are always collected; `apiGroups` controls which Custom Resource instances are collected. -For a dedicated HTTP ingress, provide a full URL with scheme: +When `rbac.useWildcard: false`, the same truthy `crDiscovery.apiGroups.include` entries are also used for restricted RBAC. Kubernetes RBAC only supports exact API groups or `"*"`, so wildcard patterns like `"*.example.com"` require `rbac.useWildcard: true`. -```yaml -otel: - platformHttpOtlpEndpoint: https://otlp-http-my-instance.stackstate.io:4318 -``` +For the full configuration guide, see the SUSE Observability documentation for the k8s resource collector. diff --git a/stackpacks/otel-k8s-crd/settings/component-mappings/custom-resource-definitions.sty b/stackpacks/otel-k8s-crd/settings/component-mappings/custom-resource-definitions.sty index 610c4db..00c8454 100644 --- a/stackpacks/otel-k8s-crd/settings/component-mappings/custom-resource-definitions.sty +++ b/stackpacks/otel-k8s-crd/settings/component-mappings/custom-resource-definitions.sty @@ -20,13 +20,14 @@ nodes: value: "log.attributes['k8s.resource.group']" - name: "kind" value: "log.attributes['k8s.resource.kind']" + - name: "customResourcesWatched" + value: "'k8s.crd.custom_resources_watched' in log.attributes && log.attributes['k8s.crd.custom_resources_watched'] ? 'Yes' : 'No'" output: identifier: "'urn:k8s:crd:/cluster/' + vars.clusterName + ':group/' + vars.group + ':kind/' + vars.kind" name: "vars.name" typeName: "'custom resource definition'" required: - configuration: "omit(log.body['object'], ['status', 'metadata'])" - status: "log.body['object']['status']" + configuration: "log.body['object']['spec']" tags: - source: "'otel-k8s-crd'" target: "stackpack" @@ -36,7 +37,10 @@ nodes: target: "k8s.crd.kind" - source: "vars.clusterName" target: "cluster-name" + - source: "vars.customResourcesWatched" + target: "k8s.crd.custom_resources_watched" optional: + status: "log.body['object']['status']" tags: - source: "log.body['object']['metadata']['creationTimestamp']" target: "k8s.creationTimestamp" diff --git a/stackpacks/otel-k8s-crd/settings/main-menu.sty b/stackpacks/otel-k8s-crd/settings/main-menu.sty index 5311723..4966499 100644 --- a/stackpacks/otel-k8s-crd/settings/main-menu.sty +++ b/stackpacks/otel-k8s-crd/settings/main-menu.sty @@ -1,7 +1,7 @@ nodes: - _type: MainMenuGroup - name: Custom Resources - iconbase64: !icon crd.svg + name: Kubernetes + iconbase64: "" defaultOpen: true items: [] - identifier: "urn:stackpack:otel-k8s-crd:main-menu-group:custom-resources" + identifier: "urn:stackpack:otel-k8s-crd:main-menu-group:kubernetes" diff --git a/stackpacks/otel-k8s-crd/settings/presentations/common-filters.sty b/stackpacks/otel-k8s-crd/settings/presentations/common-filters.sty index 57cfef2..521906c 100644 --- a/stackpacks/otel-k8s-crd/settings/presentations/common-filters.sty +++ b/stackpacks/otel-k8s-crd/settings/presentations/common-filters.sty @@ -34,6 +34,14 @@ nodes: _type: "TagFilter" tagKey: "k8s.crd.kind" moreTab: "Custom Resources" + - filterId: "crd_custom_resources_watched" + displayName: + singular: "CRs collected" + plural: "CRs collected" + filter: + _type: "TagFilter" + tagKey: "k8s.crd.custom_resources_watched" + moreTab: "Custom Resources" - filterId: "cr_api_group" displayName: singular: "API Group" diff --git a/stackpacks/otel-k8s-crd/settings/presentations/custom-resource-definitions.sty b/stackpacks/otel-k8s-crd/settings/presentations/custom-resource-definitions.sty index b696650..e527fe4 100644 --- a/stackpacks/otel-k8s-crd/settings/presentations/custom-resource-definitions.sty +++ b/stackpacks/otel-k8s-crd/settings/presentations/custom-resource-definitions.sty @@ -14,26 +14,27 @@ nodes: - filterId: "crd_cluster" - filterId: "crd_api_group" - filterId: "crd_kind" + - filterId: "crd_custom_resources_watched" overview: name: singular: "custom resource definition" plural: "custom resource definitions" title: "Custom Resource Definitions" mainMenu: - group: "Custom Resources" - order: 2 + group: "Kubernetes" + order: 0 columns: - - columnId: health - title: "Health" - projection: - _type: "HealthProjection" - value: "healthState" - columnId: crd_name title: "Name" projection: _type: "ComponentLinkProjection" name: "name" identifier: "identifiers[0]" + - columnId: crd_crs_collected + title: "CRs collected" + projection: + _type: "TextProjection" + value: "tags.singleValue('k8s.crd.custom_resources_watched')" - columnId: crd_api_group title: "API Group" projection: @@ -93,7 +94,7 @@ nodes: projection: _type: "TagProjection" provisioning: - externalComponentSelector: "external.mapping.identifier == 'urn:stackpack:otel-k8s-crd:otel-component-mapping:custom-resource-definition'" + topologySourceSelector: "source.identifier == identifiers[0]" showConfiguration: true showStatus: true relatedResources: diff --git a/stackpacks/otel-k8s-crd/settings/presentations/custom-resources.sty b/stackpacks/otel-k8s-crd/settings/presentations/custom-resources.sty index 66e0654..c4d3162 100644 --- a/stackpacks/otel-k8s-crd/settings/presentations/custom-resources.sty +++ b/stackpacks/otel-k8s-crd/settings/presentations/custom-resources.sty @@ -29,14 +29,9 @@ nodes: plural: "custom resources" title: "Custom Resources" mainMenu: - group: "Custom Resources" - order: 1 + group: "Kubernetes" + order: 0 columns: - - columnId: health - title: "Health" - projection: - _type: "HealthProjection" - value: "healthState" - columnId: cr_name title: "Name" projection: @@ -114,7 +109,7 @@ nodes: projection: _type: "TagProjection" provisioning: - externalComponentSelector: "external.mapping.identifier == 'urn:stackpack:otel-k8s-crd:otel-component-mapping:custom-resource'" + topologySourceSelector: "source.identifier == identifiers[0]" showConfiguration: true showStatus: true relatedResources: diff --git a/stackpacks/otel-k8s-crd/stackpack.yaml b/stackpacks/otel-k8s-crd/stackpack.yaml index f022a97..33a98ba 100644 --- a/stackpacks/otel-k8s-crd/stackpack.yaml +++ b/stackpacks/otel-k8s-crd/stackpack.yaml @@ -1,5 +1,5 @@ name: "otel-k8s-crd" -version: "0.0.6" +version: "0.0.8" schemaVersion: "2.0" displayName: "Kubernetes Custom Resources" categories: [ "Kubernetes" ] diff --git a/stackpacks/suse-observability-2/settings/monitors/k8s-resource-collector.sty b/stackpacks/suse-observability-2/settings/monitors/k8s-resource-collector.sty new file mode 100644 index 0000000..9381a07 --- /dev/null +++ b/stackpacks/suse-observability-2/settings/monitors/k8s-resource-collector.sty @@ -0,0 +1,66 @@ +nodes: + - _type: "Monitor" + name: "K8s resource collector - no records emitted" + tags: + - k8s-resource-collector + - otel-k8s-crd + function: urn:stackpack:common:monitor-function:threshold + identifier: "urn:stackpack:suse-observability-2:shared:monitor:k8s-resource-collector-no-records-emitted" + status: "ENABLED" + description: "Monitors whether the k8s resource collector is emitting CRD or CR log records." + arguments: + metric: + query: sum by (k8s_cluster_name, k8s_namespace_name, k8s_pod_name) (increase(receiver_k8sresource_records_emitted_total[15m])) + unit: "short" + aliasTemplate: "Records emitted in 15m" + comparator: LT + threshold: 1 + failureState: "DEVIATING" + urnTemplate: "urn:kubernetes:/${k8s_cluster_name}:${k8s_namespace_name}:pod/${k8s_pod_name}" + titleTemplate: "K8s resource collector - no records emitted" + intervalSeconds: 300 + remediationHint: "Check that the suse-observability-agent k8s-resource-collector pod is running, has Kubernetes API/RBAC access, and can export OTLP telemetry to SUSE Observability." + + - _type: "Monitor" + name: "K8s resource collector - oversized CR payloads dropped" + tags: + - k8s-resource-collector + - otel-k8s-crd + function: urn:stackpack:common:monitor-function:threshold + identifier: "urn:stackpack:suse-observability-2:shared:monitor:k8s-resource-collector-oversized-cr-payloads" + status: "ENABLED" + description: "Monitors CR log records dropped because their serialized payload exceeded max_cr_data_size. " + arguments: + metric: + query: sum by (k8s_cluster_name, k8s_namespace_name, k8s_pod_name, api_group, kind) (increase(receiver_k8sresource_oversized_cr_payloads_total[5m])) + unit: "short" + aliasTemplate: "Oversized CR payloads dropped in 5m" + comparator: GT + threshold: 0 + failureState: "DEVIATING" + urnTemplate: "urn:kubernetes:/${k8s_cluster_name}:${k8s_namespace_name}:pod/${k8s_pod_name}" + titleTemplate: "K8s resource collector - oversized CR payloads dropped" + intervalSeconds: 300 + remediationHint: "Review which Custom Resources are being collected. Use the [Metrics Explorer](/#/metrics?metrics=%5B%5B%5B%22histogram_quantile%28%5Cn%20%200.95%2C%5Cn%20%20sum%20by%20%28le%2C%20api_group%2C%20kind%29%20%28%5Cn%20%20%20%20rate%28receiver_k8sresource_oversized_cr_payload_size_bytes_bucket%5B5m%5D%29%5Cn%20%20%29%5Cn%29%22%5D%5D%2C%5B0%2C%5B0%2C%22bytes%22%2C6%2C%5Btrue%2C%22%22%5D%5D%5D%5D) to see the 95th percentile size of dropped CR payloads by API group and kind. Increase `otel.k8sResourceCollector.crDiscovery.maxCrDataSize` only when larger payloads are expected and safe to forward." + + - _type: "Monitor" + name: "K8s resource collector - informer reconcile failures" + tags: + - k8s-resource-collector + - otel-k8s-crd + function: urn:stackpack:common:monitor-function:threshold + identifier: "urn:stackpack:suse-observability-2:shared:monitor:k8s-resource-collector-informer-reconcile-failures" + status: "ENABLED" + description: "Monitors failed informer reconciliation attempts for CR and static Kubernetes resource watches." + arguments: + metric: + query: sum by (k8s_cluster_name, k8s_namespace_name, k8s_pod_name, kind) (increase(receiver_k8sresource_informer_reconciles_total{outcome="failed"}[5m])) + unit: "short" + aliasTemplate: "Informer reconcile failures in 5m" + comparator: GT + threshold: 0 + failureState: "DEVIATING" + urnTemplate: "urn:kubernetes:/${k8s_cluster_name}:${k8s_namespace_name}:pod/${k8s_pod_name}" + titleTemplate: "K8s resource collector - informer reconcile failures" + intervalSeconds: 300 + remediationHint: "Check k8s-resource-collector logs and RBAC. Failed CR informer reconciliation usually means the collector cannot list/watch a discovered resource." diff --git a/stackpacks/suse-observability-2/stackpack.yaml b/stackpacks/suse-observability-2/stackpack.yaml index 5174bfa..367f1a3 100644 --- a/stackpacks/suse-observability-2/stackpack.yaml +++ b/stackpacks/suse-observability-2/stackpack.yaml @@ -1,5 +1,5 @@ name: suse-observability-2 -version: "0.0.4" +version: "0.0.5" schemaVersion: "2.0" displayName: "SUSE Observability 2.0" categories: [ "SUSE Observability" ]