From 0c8413cab3c40e6096e6df8d3f7a73b1a51c2dfe Mon Sep 17 00:00:00 2001 From: Nir Zeira Date: Wed, 1 Jul 2026 15:48:23 +0300 Subject: [PATCH] feat(helm): expose OTEL headers, service name, and resource attributes The Go controller already reads standard OTEL SDK env vars through `autoexport.NewSpanExporter` (headers, protocol, endpoint, insecure, timeout) and `resource.WithFromEnv` (service name, resource attributes), but the Helm chart's `otel.tracing` / `otel.logging` config only surfaces endpoint/protocol/insecure/timeout. Anyone sending traces to an authenticated OTLP backend (Langfuse Cloud, Honeycomb, Datadog, New Relic, Grafana Cloud, ...) currently has to bypass the chart's `otel.*` config entirely and inject env vars via `controller.envFrom` from an out-of-band ConfigMap/Secret. This change adds four declarative knobs, all optional and backward compatible (nothing is rendered when unset): otel.serviceName -> OTEL_SERVICE_NAME otel.resourceAttributes (map) -> OTEL_RESOURCE_ATTRIBUTES otel.tracing.exporter.otlp.headers (map) -> OTEL_EXPORTER_OTLP_TRACES_HEADERS otel.logging.exporter.otlp.headers (map) -> OTEL_EXPORTER_OTLP_LOGS_HEADERS Map values are rendered as `key1=v1,key2=v2` with sorted keys for deterministic output. Secret headers (Basic auth, API keys) should continue to be overlaid through `controller.envFrom` referencing a Secret; the values.yaml comments document this pattern. Adds `tests/controller-configmap_test.yaml` covering: - backward compat: no new keys rendered with default values - each new field renders independently when set - map fields serialize in sorted key order - an end-to-end Langfuse Cloud style render Signed-off-by: Nir Zeira Co-authored-by: Cursor --- .../templates/controller-configmap.yaml | 24 ++++ .../tests/controller-configmap_test.yaml | 130 ++++++++++++++++++ helm/kagent/values.yaml | 33 +++++ 3 files changed, 187 insertions(+) create mode 100644 helm/kagent/tests/controller-configmap_test.yaml diff --git a/helm/kagent/templates/controller-configmap.yaml b/helm/kagent/templates/controller-configmap.yaml index 0436ac15f..8c9f536a5 100644 --- a/helm/kagent/templates/controller-configmap.yaml +++ b/helm/kagent/templates/controller-configmap.yaml @@ -27,6 +27,16 @@ data: # OpenTelemetry Configuration OTEL_TRACING_ENABLED: {{ .Values.otel.tracing.enabled | quote }} OTEL_LOGGING_ENABLED: {{ .Values.otel.logging.enabled | quote }} + {{- with .Values.otel.serviceName }} + OTEL_SERVICE_NAME: {{ . | quote }} + {{- end }} + {{- if .Values.otel.resourceAttributes }} + {{- $pairs := list }} + {{- range $k := keys .Values.otel.resourceAttributes | sortAlpha }} + {{- $pairs = append $pairs (printf "%s=%s" $k (index $.Values.otel.resourceAttributes $k | toString)) }} + {{- end }} + OTEL_RESOURCE_ATTRIBUTES: {{ join "," $pairs | quote }} + {{- end }} {{- $tracesEndpoint := .Values.otel.tracing.exporter.otlp.endpoint }} {{- $logsEndpoint := .Values.otel.logging.exporter.otlp.endpoint }} {{- if and $tracesEndpoint $logsEndpoint (eq $tracesEndpoint $logsEndpoint) }} @@ -51,6 +61,20 @@ data: OTEL_EXPORTER_OTLP_LOGS_TIMEOUT: {{ .Values.otel.logging.exporter.otlp.timeout | quote }} {{- end }} {{- end }} + {{- if .Values.otel.tracing.exporter.otlp.headers }} + {{- $pairs := list }} + {{- range $k := keys .Values.otel.tracing.exporter.otlp.headers | sortAlpha }} + {{- $pairs = append $pairs (printf "%s=%s" $k (index $.Values.otel.tracing.exporter.otlp.headers $k | toString)) }} + {{- end }} + OTEL_EXPORTER_OTLP_TRACES_HEADERS: {{ join "," $pairs | quote }} + {{- end }} + {{- if .Values.otel.logging.exporter.otlp.headers }} + {{- $pairs := list }} + {{- range $k := keys .Values.otel.logging.exporter.otlp.headers | sortAlpha }} + {{- $pairs = append $pairs (printf "%s=%s" $k (index $.Values.otel.logging.exporter.otlp.headers $k | toString)) }} + {{- end }} + OTEL_EXPORTER_OTLP_LOGS_HEADERS: {{ join "," $pairs | quote }} + {{- end }} {{- if .Values.proxy.url }} PROXY_URL: {{ .Values.proxy.url | quote }} {{- end }} diff --git a/helm/kagent/tests/controller-configmap_test.yaml b/helm/kagent/tests/controller-configmap_test.yaml new file mode 100644 index 000000000..4d55c2a39 --- /dev/null +++ b/helm/kagent/tests/controller-configmap_test.yaml @@ -0,0 +1,130 @@ +suite: test controller configmap +templates: + - controller-configmap.yaml +tests: + - it: should render the controller configmap + asserts: + - isKind: + of: ConfigMap + - equal: + path: metadata.name + value: RELEASE-NAME-controller + - hasDocuments: + count: 1 + + - it: should not emit OTEL_SERVICE_NAME when otel.serviceName is empty + asserts: + - notExists: + path: data.OTEL_SERVICE_NAME + + - it: should emit OTEL_SERVICE_NAME when otel.serviceName is set + set: + otel: + serviceName: my-kagent + asserts: + - equal: + path: data.OTEL_SERVICE_NAME + value: my-kagent + + - it: should not emit OTEL_RESOURCE_ATTRIBUTES when otel.resourceAttributes is empty + asserts: + - notExists: + path: data.OTEL_RESOURCE_ATTRIBUTES + + - it: should emit OTEL_RESOURCE_ATTRIBUTES sorted by key when set + set: + otel: + resourceAttributes: + team: platform + deployment.environment.name: production + langfuse.environment: production + asserts: + - equal: + path: data.OTEL_RESOURCE_ATTRIBUTES + value: deployment.environment.name=production,langfuse.environment=production,team=platform + + - it: should not emit OTEL_EXPORTER_OTLP_TRACES_HEADERS when tracing.headers is empty + asserts: + - notExists: + path: data.OTEL_EXPORTER_OTLP_TRACES_HEADERS + + - it: should emit OTEL_EXPORTER_OTLP_TRACES_HEADERS sorted by key when set + set: + otel: + tracing: + exporter: + otlp: + headers: + x-tenant-id: platform + x-region: us-central1 + asserts: + - equal: + path: data.OTEL_EXPORTER_OTLP_TRACES_HEADERS + value: x-region=us-central1,x-tenant-id=platform + + - it: should not emit OTEL_EXPORTER_OTLP_LOGS_HEADERS when logging.headers is empty + asserts: + - notExists: + path: data.OTEL_EXPORTER_OTLP_LOGS_HEADERS + + - it: should emit OTEL_EXPORTER_OTLP_LOGS_HEADERS when logging.headers is set + set: + otel: + logging: + exporter: + otlp: + headers: + x-log-tenant: platform + asserts: + - equal: + path: data.OTEL_EXPORTER_OTLP_LOGS_HEADERS + value: x-log-tenant=platform + + - it: should keep pre-existing OTEL keys unchanged + asserts: + - equal: + path: data.OTEL_TRACING_ENABLED + value: "false" + - equal: + path: data.OTEL_LOGGING_ENABLED + value: "false" + + - it: renders the full Langfuse-Cloud style tracing configuration + set: + otel: + serviceName: kagent + resourceAttributes: + deployment.environment.name: production + langfuse.environment: production + tracing: + enabled: true + exporter: + otlp: + endpoint: https://cloud.langfuse.com/api/public/otel/v1/traces + protocol: http/protobuf + insecure: false + timeout: 15000 + headers: + x-tenant-id: platform + asserts: + - equal: + path: data.OTEL_TRACING_ENABLED + value: "true" + - equal: + path: data.OTEL_SERVICE_NAME + value: kagent + - equal: + path: data.OTEL_RESOURCE_ATTRIBUTES + value: deployment.environment.name=production,langfuse.environment=production + - equal: + path: data.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT + value: https://cloud.langfuse.com/api/public/otel/v1/traces + - equal: + path: data.OTEL_EXPORTER_OTLP_TRACES_PROTOCOL + value: http/protobuf + - equal: + path: data.OTEL_EXPORTER_OTLP_TRACES_INSECURE + value: "false" + - equal: + path: data.OTEL_EXPORTER_OTLP_TRACES_HEADERS + value: x-tenant-id=platform diff --git a/helm/kagent/values.yaml b/helm/kagent/values.yaml index 5fbe1bd0e..8f7d2feb4 100644 --- a/helm/kagent/values.yaml +++ b/helm/kagent/values.yaml @@ -839,6 +839,22 @@ oauth2-proxy: # ============================================================================== otel: + # -- Value for the OpenTelemetry `service.name` resource attribute + # (OTEL_SERVICE_NAME). When empty the controller falls back to its built-in + # default ("kagent-controller"). Takes precedence over `service.name` in + # `resourceAttributes` per the OTEL specification. + serviceName: "" + # -- Extra OpenTelemetry resource attributes (OTEL_RESOURCE_ATTRIBUTES). + # Rendered as `key1=value1,key2=value2` and merged with the controller's + # built-in resource attributes via `resource.WithFromEnv()`. Useful for + # tagging traces/logs with environment, cluster, team, tenant, etc. + # @default -- {} (no extra attributes) + resourceAttributes: {} + # Example: + # resourceAttributes: + # deployment.environment.name: production + # langfuse.environment: production + # team: platform tracing: enabled: false exporter: @@ -847,6 +863,17 @@ otel: protocol: "grpc" timeout: 15000 # milliseconds insecure: true + # -- OTLP exporter headers for traces + # (OTEL_EXPORTER_OTLP_TRACES_HEADERS). Rendered as `k1=v1,k2=v2`. + # Use for non-secret headers only; for authenticated OTLP backends + # (Langfuse Cloud, Honeycomb, Datadog, New Relic, Grafana Cloud, …) + # inject the `Authorization` / API-key header from a `Secret` via + # `controller.envFrom` — that env var overrides this ConfigMap key. + # @default -- {} (no headers) + headers: {} + # Example (non-secret only): + # headers: + # x-tenant-id: platform logging: enabled: false exporter: @@ -854,3 +881,9 @@ otel: endpoint: "" timeout: 15000 # milliseconds insecure: true + # -- OTLP exporter headers for logs + # (OTEL_EXPORTER_OTLP_LOGS_HEADERS). Rendered as `k1=v1,k2=v2`. + # See the note under `tracing.exporter.otlp.headers` for how to inject + # secret headers via `controller.envFrom`. + # @default -- {} (no headers) + headers: {}