From 06f3f5af6f01de914e43c70ffa23a751a4af9312 Mon Sep 17 00:00:00 2001 From: Pratapa Lakshmi Date: Wed, 19 Aug 2026 11:28:14 +0530 Subject: [PATCH] feat(plane-enterprise): add opt-in OpenTelemetry support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds native, opt-in OTel wiring to the chart (mirrors the kustomize otel-observability component used in commercial-deployments). Off by default — with observability.otel.enabled=false the rendered manifests are byte-identical to the previous chart. - observability.otel.* values block: enabled, endpoint, protocol, headers, environment, resourceAttributes, debugConsole, sampler, samplerArg, and a frontend.{enabled,endpoint,headers} block for browser tracing. - Shared -otel-vars ConfigMap (templates/config-secrets/otel.yaml), rendered only when enabled. - OTEL_EXPORTER_OTLP_HEADERS carries the collector ingestion credential, so it is rendered into a -otel-secrets Secret instead of the ConfigMap. external_secrets.otel_env_existingSecret supplies it from a Secret the operator manages (ESO/Vault/sealed-secrets); no secretRef is emitted when no headers are configured at all. - Wires the 16 backend workloads (api, external-api, worker, worker-importers, beat-worker, automation-consumer, agent-consumer, webhook-consumer, outbox-poller, silo, live, live-exporter, space, pi-api, pi-beat, pi-worker) via envFrom + an inline per-workload OTEL_SERVICE_NAME so each reports its own service.name. web/admin are intentionally not wired — their only OTel is browser tracing, served to them by the API via the frontend.* keys. - Helpers: plane.otel.enabled / plane.otel.secretEnabled / plane.otel.envFrom / plane.otel.serviceEnv. - Rancher questions.yml "OpenTelemetry" group + README settings table. Defaults encode three lessons from validating tracing on a live cluster: - sampler defaults to always_on: parentbased_* defers to an upstream traceparent's sampled flag (dropping browser-initiated POST traces even at ratio 1.0); always_on captures everything for test/debug. Switch to parentbased_traceidratio for prod. - environment is emitted as OTEL_ENVIRONMENT, the dedicated var every service reads (api/pi observability setup, live/silo tracers, space SSR entry), rather than hand-written into OTEL_RESOURCE_ATTRIBUTES. The node/pi services pass their resolved environment as an EXPLICIT resource attribute, which wins over the env detector — so a value smuggled through OTEL_RESOURCE_ATTRIBUTES is silently overridden by their NODE_ENV fallback and the environment tag stops matching across services. - frontend.headers defaults to a non-empty value to force the browser OTLP exporter onto XHR (sendBeacon fails CORS against a wildcard ACAO), and FRONTEND_OTEL_ENABLED is only emitted when the browser endpoint is also set — matching the API's own is_otel_enabled gate. Verified with `helm template` in both states: enabled renders the ConfigMap, the Secret and all 16 workloads with distinct OTEL_SERVICE_NAME values; the default (disabled) render diffs clean against master. Co-Authored-By: Claude Opus 5 (1M context) --- charts/plane-enterprise/Chart.yaml | 2 +- charts/plane-enterprise/README.md | 34 ++++++++ charts/plane-enterprise/questions.yml | 79 +++++++++++++++++++ .../plane-enterprise/templates/_helpers.tpl | 50 ++++++++++++ .../templates/config-secrets/otel.yaml | 63 +++++++++++++++ .../workloads/agent-consumer.deployment.yaml | 6 +- .../templates/workloads/api.deployment.yaml | 4 +- .../automation-consumer.deployment.yaml | 6 +- .../workloads/beat-worker.deployment.yaml | 6 +- .../workloads/external-api.deployment.yaml | 4 +- .../workloads/live-exporter.deployment.yaml | 2 + .../templates/workloads/live.deployment.yaml | 4 +- .../workloads/outbox-poller.deployment.yaml | 6 +- .../workloads/pi-api.deployment.yaml | 4 +- .../workloads/pi-beat.deployment.yaml | 4 +- .../workloads/pi-worker.deployment.yaml | 4 +- .../templates/workloads/silo.deployment.yaml | 4 +- .../templates/workloads/space.deployment.yaml | 9 ++- .../webhook-consumer.deployment.yaml | 6 +- .../worker-importers.deployment.yaml | 4 +- .../workloads/worker.deployment.yaml | 4 +- charts/plane-enterprise/values.yaml | 49 ++++++++++++ 22 files changed, 338 insertions(+), 16 deletions(-) create mode 100644 charts/plane-enterprise/templates/config-secrets/otel.yaml diff --git a/charts/plane-enterprise/Chart.yaml b/charts/plane-enterprise/Chart.yaml index d852fd84..6a85f88d 100644 --- a/charts/plane-enterprise/Chart.yaml +++ b/charts/plane-enterprise/Chart.yaml @@ -5,7 +5,7 @@ description: Meet Plane. An Enterprise software development tool to manage issue type: application -version: 3.2.1 +version: 3.3.0 appVersion: "3.1.1" home: https://plane.so/ diff --git a/charts/plane-enterprise/README.md b/charts/plane-enterprise/README.md index 33c7a0ac..39bc2d2d 100644 --- a/charts/plane-enterprise/README.md +++ b/charts/plane-enterprise/README.md @@ -891,6 +891,39 @@ Note: When the email service is enabled, the cert-issuer will be automatically c | -------- | :-----: | :------: | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | extraEnv | [] | No | Global extra environment variables that will be applied to all workloads. This allows you to add custom environment variables to all deployments (web, api, worker, etc.). Useful for proxy settings, custom configurations, or any environment-specific variables. Some example variables are HTTP_PROXY, HTTPS_PROXY, NO_PROXY. | +### Observability (OpenTelemetry) + +Opt-in OpenTelemetry (traces, logs and metrics) for the backend services. Nothing is +injected unless `observability.otel.enabled=true`. + +When enabled, the chart renders a shared `-otel-vars` ConfigMap and mounts it +via `envFrom` into `api`, `external-api`, `worker`, `worker-importers`, `beat-worker`, +`automation-consumer`, `agent-consumer`, `webhook-consumer`, `outbox-poller`, `silo`, +`live`, `live-exporter`, `space`, `pi-api`, `pi-beat` and `pi-worker`. Each workload also +gets an inline `OTEL_SERVICE_NAME` so it reports its own `service.name`. `web` and +`admin` are deliberately not wired — their only telemetry is browser tracing, which the +API serves to browsers from its instance config via the `frontend.*` keys below. + +`observability.otel.headers` usually carries a collector ingestion credential, so it is +rendered into a `-otel-secrets` Secret rather than the ConfigMap. Set +`external_secrets.otel_env_existingSecret` to supply `OTEL_EXPORTER_OTLP_HEADERS` from a +Secret you manage yourself (External Secrets Operator, Vault, sealed-secrets, ...). + +| Setting | Default | Required | Description | +| -------------------------------------- | :------------------: | :------: | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| observability.otel.enabled | false | | Master switch. When `false` no OTel ConfigMap, Secret or env var is rendered at all. | +| observability.otel.endpoint | `''` | Yes | OTLP collector endpoint (required when enabled — the services skip OTel bootstrap without it). An `https://` endpoint uses secure gRPC. | +| observability.otel.protocol | `grpc` | | OTLP transport: `grpc` or `http/protobuf`. | +| observability.otel.headers | `''` | | Extra OTLP exporter headers as `k1=v1,k2=v2` (e.g. a collector ingestion key). Rendered into the `-otel-secrets` Secret. | +| observability.otel.environment | `''` | | Deployment environment tag (e.g. `prod`, `staging`). Emitted by every service as the `deployment.environment.name` resource attribute, so cross-service environment filtering lines up. | +| observability.otel.resourceAttributes | `''` | | Additional OTel resource attributes as `k1=v1,k2=v2`. | +| observability.otel.debugConsole | false | | Also print spans to stdout. Debug only. | +| observability.otel.sampler | `always_on` | | Trace sampler. `always_on` exports every span the service sees and ignores an upstream `traceparent`'s sampling decision — use it for test/debug so browser-initiated POST traces aren't dropped. For production prefer `parentbased_traceidratio` with a ratio. | +| observability.otel.samplerArg | `'1.0'` | | Sampling ratio (0.0–1.0) for the ratio-based samplers. Ignored by `always_on`. | +| observability.otel.frontend.enabled | false | | Browser/client tracing for `web`, `admin` and `space`. Read only by the API, which serves it to browsers over its public instance endpoint. Takes effect only when `frontend.endpoint` is also set. | +| observability.otel.frontend.endpoint | `''` | | Public OTLP/HTTP endpoint the browser posts to. Must be internet-reachable and CORS-enabled for the Plane web origin; the client appends `/v1/traces`. | +| observability.otel.frontend.headers | `x-otlp-browser=1` | | Must be non-empty cross-origin: a header forces the browser exporter onto XHR instead of `navigator.sendBeacon`, which sends credentials and is rejected by CORS against a wildcard `Access-Control-Allow-Origin`. The value is arbitrary and public. | + ## External Secrets Config To configure the external secrets for your application, you need to define specific environment variables for each secret category. Below is a list of the required secrets and their respective environment variables. @@ -957,6 +990,7 @@ To configure the external secrets for your application, you need to define speci | | `CUSTOM_LLM_API_KEY` | required if `services.pi.ai_providers.custom_llm.enabled` is `true` | Custom LLM API key | `your_custom_llm_api_key` | | | `BR_AWS_SECRET_ACCESS_KEY` | required if `services.pi.ai_providers.embedding_model.enabled` is `true` | AWS secret for embedding model | `your_aws_secret_access_key` | | | `BR_AWS_SESSION_TOKEN` | required if embedding model uses temporary credentials | AWS session token for embedding model | `your_aws_session_token` | +| otel_env_existingSecret | `OTEL_EXPORTER_OTLP_HEADERS` | Optional (only if `observability.otel.enabled=true`) | OTLP exporter headers, e.g. a collector ingestion key. Leave `otel_env_existingSecret` blank to let the chart create this Secret from `observability.otel.headers`. | `x-api-key=your_collector_key` | ## Custom Ingress Routes diff --git a/charts/plane-enterprise/questions.yml b/charts/plane-enterprise/questions.yml index 26f6f9a5..30e436a2 100644 --- a/charts/plane-enterprise/questions.yml +++ b/charts/plane-enterprise/questions.yml @@ -1793,3 +1793,82 @@ questions: type: string default: "" group: "External Secrets" +- variable: external_secrets.otel_env_existingSecret + label: "OpenTelemetry Envs Secrets File Name" + description: "Supplies OTEL_EXPORTER_OTLP_HEADERS. Leave blank to let the chart create the Secret from the OTLP Exporter Headers value." + type: string + default: "" + group: "External Secrets" + +- variable: observability.otel.enabled + label: "Enable OpenTelemetry" + description: "Export traces, logs and metrics from the backend services over OTLP. Off by default." + type: boolean + default: false + group: "OpenTelemetry" + show_subquestion_if: true + subquestions: + - variable: observability.otel.endpoint + label: "OTLP Endpoint" + description: "OTLP collector endpoint. An https:// endpoint uses secure gRPC." + type: string + default: "" + - variable: observability.otel.protocol + label: "OTLP Protocol" + type: enum + options: + - "grpc" + - "http/protobuf" + default: "grpc" + - variable: observability.otel.headers + label: "OTLP Exporter Headers" + description: "Extra OTLP exporter headers as k1=v1,k2=v2 (e.g. a collector ingestion/auth key). Stored in a Secret, not a ConfigMap." + type: string + default: "" + - variable: observability.otel.environment + label: "Deployment Environment" + description: "Emitted as deployment.environment.name (e.g. prod, staging). Use for cross-service environment filtering." + type: string + default: "" + - variable: observability.otel.resourceAttributes + label: "Extra Resource Attributes" + description: "Additional OTel resource attributes as k1=v1,k2=v2." + type: string + default: "" + - variable: observability.otel.sampler + label: "Trace Sampler" + description: "always_on captures every trace (recommended for test/debug). In production use parentbased_traceidratio; unlike always_on it honours an upstream traceparent's sampling decision." + type: enum + options: + - "always_on" + - "parentbased_traceidratio" + - "traceidratio" + - "always_off" + default: "always_on" + - variable: observability.otel.samplerArg + label: "Trace Sampler Ratio" + description: "Sampling ratio 0.0-1.0 for ratio-based samplers. Ignored by always_on." + type: string + default: "1.0" + show_if: "observability.otel.sampler=parentbased_traceidratio || observability.otel.sampler=traceidratio" + - variable: observability.otel.debugConsole + label: "Print Spans to Stdout (debug)" + type: boolean + default: false + - variable: observability.otel.frontend.enabled + label: "Enable Browser Tracing" + description: "Browser tracing for web/admin/space, served to browsers by the API. Requires a browser OTLP endpoint below." + type: boolean + default: false + - variable: observability.otel.frontend.endpoint + label: "Browser OTLP Endpoint" + description: "Public OTLP/HTTP endpoint the browser posts to (must be internet-reachable and CORS-enabled)." + type: string + default: "" + show_if: "observability.otel.frontend.enabled=true" + - variable: observability.otel.frontend.headers + label: "Browser OTLP Headers" + description: "A non-empty value forces the browser exporter onto XHR (required cross-origin; sendBeacon fails CORS)." + type: string + default: "x-otlp-browser=1" + show_if: "observability.otel.frontend.enabled=true" diff --git a/charts/plane-enterprise/templates/_helpers.tpl b/charts/plane-enterprise/templates/_helpers.tpl index 5df2e59f..de139b01 100644 --- a/charts/plane-enterprise/templates/_helpers.tpl +++ b/charts/plane-enterprise/templates/_helpers.tpl @@ -242,3 +242,53 @@ Caller must nindent to the correct depth. value: "/ca-bundle/custom-ca-bundle.crt" {{- end }} {{- end -}} + +{{/* +OpenTelemetry — returns "true" when observability.otel.enabled is set, else "". +*/}} +{{- define "plane.otel.enabled" -}} +{{- if and .Values.observability .Values.observability.otel .Values.observability.otel.enabled -}}true{{- end -}} +{{- end -}} + +{{/* +Returns "true" when the OTLP exporter headers are sourced from a Secret — either +because observability.otel.headers is set (chart-managed Secret) or because an +existing Secret was supplied. Empty otherwise, so no secretRef is emitted for a +deployment that needs no ingestion credentials. +*/}} +{{- define "plane.otel.secretEnabled" -}} +{{- if eq (include "plane.otel.enabled" .) "true" -}} +{{- if or .Values.observability.otel.headers .Values.external_secrets.otel_env_existingSecret -}}true{{- end -}} +{{- end -}} +{{- end -}} + +{{/* +envFrom entries for the shared OTEL ConfigMap (+ the OTLP headers Secret, when +one is in play). Call with the root context and nindent to the envFrom list +depth, e.g. + {{- include "plane.otel.envFrom" $ | nindent 10 }} +*/}} +{{- define "plane.otel.envFrom" -}} +{{- if eq (include "plane.otel.enabled" .) "true" -}} +- configMapRef: + name: {{ .Release.Name }}-otel-vars + optional: false +{{- if eq (include "plane.otel.secretEnabled" .) "true" }} +- secretRef: + name: {{ if not (empty .Values.external_secrets.otel_env_existingSecret) }}{{ .Values.external_secrets.otel_env_existingSecret }}{{ else }}{{ .Release.Name }}-otel-secrets{{ end }} + optional: false +{{- end }} +{{- end -}} +{{- end -}} + +{{/* +Per-workload OTEL_SERVICE_NAME (overrides the shared ConfigMap so each workload +reports its own service.name). Call with a dict and nindent, e.g. + {{- include "plane.otel.serviceEnv" (dict "ctx" $ "service" "api") | nindent 10 }} +*/}} +{{- define "plane.otel.serviceEnv" -}} +{{- if eq (include "plane.otel.enabled" .ctx) "true" -}} +- name: OTEL_SERVICE_NAME + value: {{ .service | quote }} +{{- end -}} +{{- end -}} diff --git a/charts/plane-enterprise/templates/config-secrets/otel.yaml b/charts/plane-enterprise/templates/config-secrets/otel.yaml new file mode 100644 index 00000000..396b9912 --- /dev/null +++ b/charts/plane-enterprise/templates/config-secrets/otel.yaml @@ -0,0 +1,63 @@ +{{- if eq (include "plane.otel.enabled" .) "true" }} +# Shared OpenTelemetry env for the backend workloads. Mounted via envFrom; each +# workload additionally sets an inline OTEL_SERVICE_NAME (see plane.otel.serviceEnv). +apiVersion: v1 +kind: ConfigMap +metadata: + namespace: {{ .Release.Namespace }} + name: {{ .Release.Name }}-otel-vars + labels: + {{- include "plane.commonLabels" $ | nindent 4 }} +data: + OTEL_ENABLED: "1" + {{- with .Values.observability.otel.endpoint }} + OTEL_EXPORTER_OTLP_ENDPOINT: {{ . | quote }} + {{- end }} + OTEL_EXPORTER_OTLP_PROTOCOL: {{ .Values.observability.otel.protocol | default "grpc" | quote }} + {{- with .Values.observability.otel.environment }} + # Read by every service and emitted as the deployment.environment.name resource + # attribute (the current semconv key, NOT the legacy deployment.environment). + # Set here rather than hand-written into OTEL_RESOURCE_ATTRIBUTES: the node/pi + # services pass this value as an explicit attribute, which wins over anything + # the env detector picks up from OTEL_RESOURCE_ATTRIBUTES. + OTEL_ENVIRONMENT: {{ . | quote }} + {{- end }} + {{- with .Values.observability.otel.resourceAttributes }} + OTEL_RESOURCE_ATTRIBUTES: {{ . | quote }} + {{- end }} + {{- if .Values.observability.otel.debugConsole }} + OTEL_DEBUG_CONSOLE: "1" + {{- end }} + {{- with .Values.observability.otel.sampler }} + OTEL_TRACES_SAMPLER: {{ . | quote }} + {{- end }} + {{- with .Values.observability.otel.samplerArg }} + OTEL_TRACES_SAMPLER_ARG: {{ . | quote }} + {{- end }} + {{- if and .Values.observability.otel.frontend.enabled .Values.observability.otel.frontend.endpoint }} + # Browser tracing config. Read only by the API, which serves it to browsers + # over its UNAUTHENTICATED instance endpoint — these three values are public by + # design, so they belong in the ConfigMap and not in the Secret below. + FRONTEND_OTEL_ENABLED: "1" + FRONTEND_OTLP_ENDPOINT: {{ .Values.observability.otel.frontend.endpoint | quote }} + {{- with .Values.observability.otel.frontend.headers }} + FRONTEND_OTLP_HEADERS: {{ . | quote }} + {{- end }} + {{- end }} +{{- if and (eq (include "plane.otel.secretEnabled" .) "true") (empty .Values.external_secrets.otel_env_existingSecret) }} +--- +# OTEL_EXPORTER_OTLP_HEADERS carries the collector ingestion credential, so it is +# kept out of the ConfigMap. Set external_secrets.otel_env_existingSecret to +# supply it from a Secret you manage instead (ESO, Vault, sealed-secrets, ...). +apiVersion: v1 +kind: Secret +type: Opaque +metadata: + namespace: {{ .Release.Namespace }} + name: {{ .Release.Name }}-otel-secrets + labels: + {{- include "plane.commonLabels" $ | nindent 4 }} +stringData: + OTEL_EXPORTER_OTLP_HEADERS: {{ .Values.observability.otel.headers | quote }} +{{- end }} +{{- end }} diff --git a/charts/plane-enterprise/templates/workloads/agent-consumer.deployment.yaml b/charts/plane-enterprise/templates/workloads/agent-consumer.deployment.yaml index bd765027..aea217f9 100644 --- a/charts/plane-enterprise/templates/workloads/agent-consumer.deployment.yaml +++ b/charts/plane-enterprise/templates/workloads/agent-consumer.deployment.yaml @@ -69,9 +69,13 @@ spec: name: {{ if not (empty .Values.external_secrets.silo_env_existingSecret) }}{{ .Values.external_secrets.silo_env_existingSecret }}{{ else }}{{ .Release.Name }}-silo-secrets{{ end }} optional: false {{- end }} - {{- if .Values.extraEnv }} + {{- with (include "plane.otel.envFrom" $) }}{{ . | nindent 10 }}{{- end }} + {{- if or .Values.extraEnv (eq (include "plane.otel.enabled" .) "true") }} env: + {{- with (include "plane.otel.serviceEnv" (dict "ctx" $ "service" "agent-consumer")) }}{{ . | nindent 10 }}{{- end }} + {{- if .Values.extraEnv }} {{- toYaml .Values.extraEnv | nindent 10 }} + {{- end }} {{- end }} serviceAccount: {{ .Release.Name }}-srv-account diff --git a/charts/plane-enterprise/templates/workloads/api.deployment.yaml b/charts/plane-enterprise/templates/workloads/api.deployment.yaml index 2e140495..4b1b27a8 100644 --- a/charts/plane-enterprise/templates/workloads/api.deployment.yaml +++ b/charts/plane-enterprise/templates/workloads/api.deployment.yaml @@ -94,11 +94,13 @@ spec: optional: false {{- end }} - {{- if or .Values.extraEnv (include "plane.s3CAEnabled" .) }} + {{- with (include "plane.otel.envFrom" $) }}{{ . | nindent 10 }}{{- end }} + {{- if or .Values.extraEnv (include "plane.s3CAEnabled" .) (eq (include "plane.otel.enabled" .) "true") }} env: {{- with (include "plane.s3CAEnvVars" .) }} {{ . | indent 10 }} {{- end }} + {{- with (include "plane.otel.serviceEnv" (dict "ctx" $ "service" "api")) }}{{ . | nindent 10 }}{{- end }} {{- if .Values.extraEnv }} {{- toYaml .Values.extraEnv | nindent 10 }} {{- end }} diff --git a/charts/plane-enterprise/templates/workloads/automation-consumer.deployment.yaml b/charts/plane-enterprise/templates/workloads/automation-consumer.deployment.yaml index 07517df9..c9e9641b 100644 --- a/charts/plane-enterprise/templates/workloads/automation-consumer.deployment.yaml +++ b/charts/plane-enterprise/templates/workloads/automation-consumer.deployment.yaml @@ -53,9 +53,13 @@ spec: - secretRef: name: {{ if not (empty .Values.external_secrets.opensearch_existingSecret) }}{{ .Values.external_secrets.opensearch_existingSecret }}{{ else }}{{ .Release.Name }}-opensearch-secrets{{ end }} optional: false - {{- if .Values.extraEnv }} + {{- with (include "plane.otel.envFrom" $) }}{{ . | nindent 10 }}{{- end }} + {{- if or .Values.extraEnv (eq (include "plane.otel.enabled" .) "true") }} env: + {{- with (include "plane.otel.serviceEnv" (dict "ctx" $ "service" "automation-consumer")) }}{{ . | nindent 10 }}{{- end }} + {{- if .Values.extraEnv }} {{- toYaml .Values.extraEnv | nindent 10 }} + {{- end }} {{- end }} serviceAccount: {{ .Release.Name }}-srv-account diff --git a/charts/plane-enterprise/templates/workloads/beat-worker.deployment.yaml b/charts/plane-enterprise/templates/workloads/beat-worker.deployment.yaml index 81bc8c9b..752dc54f 100644 --- a/charts/plane-enterprise/templates/workloads/beat-worker.deployment.yaml +++ b/charts/plane-enterprise/templates/workloads/beat-worker.deployment.yaml @@ -54,9 +54,13 @@ spec: name: {{ if not (empty .Values.external_secrets.silo_env_existingSecret) }}{{ .Values.external_secrets.silo_env_existingSecret }}{{ else }}{{ .Release.Name }}-silo-secrets{{ end }} optional: false {{- end }} - {{- if .Values.extraEnv }} + {{- with (include "plane.otel.envFrom" $) }}{{ . | nindent 10 }}{{- end }} + {{- if or .Values.extraEnv (eq (include "plane.otel.enabled" .) "true") }} env: + {{- with (include "plane.otel.serviceEnv" (dict "ctx" $ "service" "beat-worker")) }}{{ . | nindent 10 }}{{- end }} + {{- if .Values.extraEnv }} {{- toYaml .Values.extraEnv | nindent 10 }} + {{- end }} {{- end }} serviceAccount: {{ .Release.Name }}-srv-account diff --git a/charts/plane-enterprise/templates/workloads/external-api.deployment.yaml b/charts/plane-enterprise/templates/workloads/external-api.deployment.yaml index df2e916b..813712bb 100644 --- a/charts/plane-enterprise/templates/workloads/external-api.deployment.yaml +++ b/charts/plane-enterprise/templates/workloads/external-api.deployment.yaml @@ -95,11 +95,13 @@ spec: optional: false {{- end }} - {{- if or .Values.extraEnv (include "plane.s3CAEnabled" .) }} + {{- with (include "plane.otel.envFrom" $) }}{{ . | nindent 10 }}{{- end }} + {{- if or .Values.extraEnv (include "plane.s3CAEnabled" .) (eq (include "plane.otel.enabled" .) "true") }} env: {{- with (include "plane.s3CAEnvVars" .) }} {{ . | indent 10 }} {{- end }} + {{- with (include "plane.otel.serviceEnv" (dict "ctx" $ "service" "external-api")) }}{{ . | nindent 10 }}{{- end }} {{- if .Values.extraEnv }} {{- toYaml .Values.extraEnv | nindent 10 }} {{- end }} diff --git a/charts/plane-enterprise/templates/workloads/live-exporter.deployment.yaml b/charts/plane-enterprise/templates/workloads/live-exporter.deployment.yaml index eb781a73..d750468a 100644 --- a/charts/plane-enterprise/templates/workloads/live-exporter.deployment.yaml +++ b/charts/plane-enterprise/templates/workloads/live-exporter.deployment.yaml @@ -43,9 +43,11 @@ spec: - secretRef: name: {{ if not (empty .Values.external_secrets.live_env_existingSecret) }}{{ .Values.external_secrets.live_env_existingSecret }}{{ else }}{{ .Release.Name }}-live-secrets{{ end }} optional: false + {{- with (include "plane.otel.envFrom" $) }}{{ . | nindent 10 }}{{- end }} env: - name: LIVE_MODE value: "exporter" + {{- with (include "plane.otel.serviceEnv" (dict "ctx" $ "service" "live-exporter")) }}{{ . | nindent 10 }}{{- end }} {{- if .Values.extraEnv }} {{- toYaml .Values.extraEnv | nindent 10 }} {{- end }} diff --git a/charts/plane-enterprise/templates/workloads/live.deployment.yaml b/charts/plane-enterprise/templates/workloads/live.deployment.yaml index 573bb4d0..b1759894 100644 --- a/charts/plane-enterprise/templates/workloads/live.deployment.yaml +++ b/charts/plane-enterprise/templates/workloads/live.deployment.yaml @@ -96,11 +96,13 @@ spec: - secretRef: name: {{ if not (empty .Values.external_secrets.live_env_existingSecret) }}{{ .Values.external_secrets.live_env_existingSecret }}{{ else }}{{ .Release.Name }}-live-secrets{{ end }} optional: false - {{- if or .Values.extraEnv (include "plane.s3CAEnabled" .) }} + {{- with (include "plane.otel.envFrom" $) }}{{ . | nindent 10 }}{{- end }} + {{- if or .Values.extraEnv (include "plane.s3CAEnabled" .) (eq (include "plane.otel.enabled" .) "true") }} env: {{- with (include "plane.s3CANodeEnvVars" .) }} {{ . | indent 10 }} {{- end }} + {{- with (include "plane.otel.serviceEnv" (dict "ctx" $ "service" "live")) }}{{ . | nindent 10 }}{{- end }} {{- if .Values.extraEnv }} {{- toYaml .Values.extraEnv | nindent 10 }} {{- end }} diff --git a/charts/plane-enterprise/templates/workloads/outbox-poller.deployment.yaml b/charts/plane-enterprise/templates/workloads/outbox-poller.deployment.yaml index 9993affb..839fce04 100644 --- a/charts/plane-enterprise/templates/workloads/outbox-poller.deployment.yaml +++ b/charts/plane-enterprise/templates/workloads/outbox-poller.deployment.yaml @@ -47,9 +47,13 @@ spec: - secretRef: name: {{ if not (empty .Values.external_secrets.app_env_existingSecret) }}{{ .Values.external_secrets.app_env_existingSecret }}{{ else }}{{ .Release.Name }}-app-secrets{{ end }} optional: false - {{- if .Values.extraEnv }} + {{- with (include "plane.otel.envFrom" $) }}{{ . | nindent 10 }}{{- end }} + {{- if or .Values.extraEnv (eq (include "plane.otel.enabled" .) "true") }} env: + {{- with (include "plane.otel.serviceEnv" (dict "ctx" $ "service" "outbox-poller")) }}{{ . | nindent 10 }}{{- end }} + {{- if .Values.extraEnv }} {{- toYaml .Values.extraEnv | nindent 10 }} + {{- end }} {{- end }} serviceAccount: {{ .Release.Name }}-srv-account diff --git a/charts/plane-enterprise/templates/workloads/pi-api.deployment.yaml b/charts/plane-enterprise/templates/workloads/pi-api.deployment.yaml index 1db32716..0eaf47ac 100644 --- a/charts/plane-enterprise/templates/workloads/pi-api.deployment.yaml +++ b/charts/plane-enterprise/templates/workloads/pi-api.deployment.yaml @@ -88,11 +88,13 @@ spec: - secretRef: name: {{ if not (empty .Values.external_secrets.opensearch_existingSecret) }}{{ .Values.external_secrets.opensearch_existingSecret }}{{ else }}{{ .Release.Name }}-opensearch-secrets{{ end }} optional: false - {{- if or .Values.extraEnv (include "plane.s3CAEnabled" .) }} + {{- with (include "plane.otel.envFrom" $) }}{{ . | nindent 10 }}{{- end }} + {{- if or .Values.extraEnv (include "plane.s3CAEnabled" .) (eq (include "plane.otel.enabled" .) "true") }} env: {{- with (include "plane.s3CAEnvVars" .) }} {{ . | indent 10 }} {{- end }} + {{- with (include "plane.otel.serviceEnv" (dict "ctx" $ "service" "pi-api")) }}{{ . | nindent 10 }}{{- end }} {{- if .Values.extraEnv }} {{- toYaml .Values.extraEnv | nindent 10 }} {{- end }} diff --git a/charts/plane-enterprise/templates/workloads/pi-beat.deployment.yaml b/charts/plane-enterprise/templates/workloads/pi-beat.deployment.yaml index d91b6f88..f882ba13 100644 --- a/charts/plane-enterprise/templates/workloads/pi-beat.deployment.yaml +++ b/charts/plane-enterprise/templates/workloads/pi-beat.deployment.yaml @@ -64,11 +64,13 @@ spec: - secretRef: name: {{ if not (empty .Values.external_secrets.opensearch_existingSecret) }}{{ .Values.external_secrets.opensearch_existingSecret }}{{ else }}{{ .Release.Name }}-opensearch-secrets{{ end }} optional: false - {{- if or .Values.extraEnv (include "plane.s3CAEnabled" .) }} + {{- with (include "plane.otel.envFrom" $) }}{{ . | nindent 10 }}{{- end }} + {{- if or .Values.extraEnv (include "plane.s3CAEnabled" .) (eq (include "plane.otel.enabled" .) "true") }} env: {{- with (include "plane.s3CAEnvVars" .) }} {{ . | indent 10 }} {{- end }} + {{- with (include "plane.otel.serviceEnv" (dict "ctx" $ "service" "pi-beat")) }}{{ . | nindent 10 }}{{- end }} {{- if .Values.extraEnv }} {{- toYaml .Values.extraEnv | nindent 10 }} {{- end }} diff --git a/charts/plane-enterprise/templates/workloads/pi-worker.deployment.yaml b/charts/plane-enterprise/templates/workloads/pi-worker.deployment.yaml index abe1a6d5..aef0d344 100644 --- a/charts/plane-enterprise/templates/workloads/pi-worker.deployment.yaml +++ b/charts/plane-enterprise/templates/workloads/pi-worker.deployment.yaml @@ -64,11 +64,13 @@ spec: - secretRef: name: {{ if not (empty .Values.external_secrets.opensearch_existingSecret) }}{{ .Values.external_secrets.opensearch_existingSecret }}{{ else }}{{ .Release.Name }}-opensearch-secrets{{ end }} optional: false - {{- if or .Values.extraEnv (include "plane.s3CAEnabled" .) }} + {{- with (include "plane.otel.envFrom" $) }}{{ . | nindent 10 }}{{- end }} + {{- if or .Values.extraEnv (include "plane.s3CAEnabled" .) (eq (include "plane.otel.enabled" .) "true") }} env: {{- with (include "plane.s3CAEnvVars" .) }} {{ . | indent 10 }} {{- end }} + {{- with (include "plane.otel.serviceEnv" (dict "ctx" $ "service" "pi-worker")) }}{{ . | nindent 10 }}{{- end }} {{- if .Values.extraEnv }} {{- toYaml .Values.extraEnv | nindent 10 }} {{- end }} diff --git a/charts/plane-enterprise/templates/workloads/silo.deployment.yaml b/charts/plane-enterprise/templates/workloads/silo.deployment.yaml index 32ec41ff..44a6eb56 100644 --- a/charts/plane-enterprise/templates/workloads/silo.deployment.yaml +++ b/charts/plane-enterprise/templates/workloads/silo.deployment.yaml @@ -122,11 +122,13 @@ spec: - secretRef: name: {{ if not (empty .Values.external_secrets.doc_store_existingSecret) }}{{ .Values.external_secrets.doc_store_existingSecret }}{{ else }}{{ .Release.Name }}-doc-store-secrets{{ end }} optional: false - {{- if or .Values.extraEnv (include "plane.s3CAEnabled" .) }} + {{- with (include "plane.otel.envFrom" $) }}{{ . | nindent 10 }}{{- end }} + {{- if or .Values.extraEnv (include "plane.s3CAEnabled" .) (eq (include "plane.otel.enabled" .) "true") }} env: {{- with (include "plane.s3CANodeEnvVars" .) }} {{ . | indent 10 }} {{- end }} + {{- with (include "plane.otel.serviceEnv" (dict "ctx" $ "service" "silo")) }}{{ . | nindent 10 }}{{- end }} {{- if .Values.extraEnv }} {{- toYaml .Values.extraEnv | nindent 10 }} {{- end }} diff --git a/charts/plane-enterprise/templates/workloads/space.deployment.yaml b/charts/plane-enterprise/templates/workloads/space.deployment.yaml index 91b4f217..1a023890 100644 --- a/charts/plane-enterprise/templates/workloads/space.deployment.yaml +++ b/charts/plane-enterprise/templates/workloads/space.deployment.yaml @@ -58,9 +58,16 @@ spec: limits: memory: {{ .Values.services.space.memoryLimit | default "1000Mi" | quote }} cpu: {{ .Values.services.space.cpuLimit | default "500m" | quote}} - {{- if .Values.extraEnv }} + {{- if eq (include "plane.otel.enabled" .) "true" }} + envFrom: + {{- with (include "plane.otel.envFrom" $) }}{{ . | nindent 10 }}{{- end }} + {{- end }} + {{- if or .Values.extraEnv (eq (include "plane.otel.enabled" .) "true") }} env: + {{- with (include "plane.otel.serviceEnv" (dict "ctx" $ "service" "space")) }}{{ . | nindent 10 }}{{- end }} + {{- if .Values.extraEnv }} {{- toYaml .Values.extraEnv | nindent 10 }} + {{- end }} {{- end }} serviceAccount: {{ .Release.Name }}-srv-account serviceAccountName: {{ .Release.Name }}-srv-account diff --git a/charts/plane-enterprise/templates/workloads/webhook-consumer.deployment.yaml b/charts/plane-enterprise/templates/workloads/webhook-consumer.deployment.yaml index 865a28df..5375c6a5 100644 --- a/charts/plane-enterprise/templates/workloads/webhook-consumer.deployment.yaml +++ b/charts/plane-enterprise/templates/workloads/webhook-consumer.deployment.yaml @@ -69,9 +69,13 @@ spec: name: {{ if not (empty .Values.external_secrets.silo_env_existingSecret) }}{{ .Values.external_secrets.silo_env_existingSecret }}{{ else }}{{ .Release.Name }}-silo-secrets{{ end }} optional: false {{- end }} - {{- if .Values.extraEnv }} + {{- with (include "plane.otel.envFrom" $) }}{{ . | nindent 10 }}{{- end }} + {{- if or .Values.extraEnv (eq (include "plane.otel.enabled" .) "true") }} env: + {{- with (include "plane.otel.serviceEnv" (dict "ctx" $ "service" "webhook-consumer")) }}{{ . | nindent 10 }}{{- end }} + {{- if .Values.extraEnv }} {{- toYaml .Values.extraEnv | nindent 10 }} + {{- end }} {{- end }} serviceAccount: {{ .Release.Name }}-srv-account diff --git a/charts/plane-enterprise/templates/workloads/worker-importers.deployment.yaml b/charts/plane-enterprise/templates/workloads/worker-importers.deployment.yaml index 32451114..636ec2ae 100644 --- a/charts/plane-enterprise/templates/workloads/worker-importers.deployment.yaml +++ b/charts/plane-enterprise/templates/workloads/worker-importers.deployment.yaml @@ -71,11 +71,13 @@ spec: optional: false {{- end }} - {{- if or .Values.extraEnv (include "plane.s3CAEnabled" .) }} + {{- with (include "plane.otel.envFrom" $) }}{{ . | nindent 10 }}{{- end }} + {{- if or .Values.extraEnv (include "plane.s3CAEnabled" .) (eq (include "plane.otel.enabled" .) "true") }} env: {{- with (include "plane.s3CAEnvVars" .) }} {{ . | indent 10 }} {{- end }} + {{- with (include "plane.otel.serviceEnv" (dict "ctx" $ "service" "worker-importers")) }}{{ . | nindent 10 }}{{- end }} {{- if .Values.extraEnv }} {{- toYaml .Values.extraEnv | nindent 10 }} {{- end }} diff --git a/charts/plane-enterprise/templates/workloads/worker.deployment.yaml b/charts/plane-enterprise/templates/workloads/worker.deployment.yaml index cff2882c..5a548d14 100644 --- a/charts/plane-enterprise/templates/workloads/worker.deployment.yaml +++ b/charts/plane-enterprise/templates/workloads/worker.deployment.yaml @@ -70,11 +70,13 @@ spec: optional: false {{- end }} - {{- if or .Values.extraEnv (include "plane.s3CAEnabled" .) }} + {{- with (include "plane.otel.envFrom" $) }}{{ . | nindent 10 }}{{- end }} + {{- if or .Values.extraEnv (include "plane.s3CAEnabled" .) (eq (include "plane.otel.enabled" .) "true") }} env: {{- with (include "plane.s3CAEnvVars" .) }} {{ . | indent 10 }} {{- end }} + {{- with (include "plane.otel.serviceEnv" (dict "ctx" $ "service" "worker")) }}{{ . | nindent 10 }}{{- end }} {{- if .Values.extraEnv }} {{- toYaml .Values.extraEnv | nindent 10 }} {{- end }} diff --git a/charts/plane-enterprise/values.yaml b/charts/plane-enterprise/values.yaml index 33cd01ed..56f03792 100644 --- a/charts/plane-enterprise/values.yaml +++ b/charts/plane-enterprise/values.yaml @@ -580,6 +580,9 @@ external_secrets: silo_env_existingSecret: '' pi_api_env_existingSecret: '' runner_env_existingSecret: '' + # Supplies OTEL_EXPORTER_OTLP_HEADERS (collector ingestion credential) when + # observability.otel.enabled is set; leave blank to let the chart create it. + otel_env_existingSecret: '' env: storageClass: '' @@ -757,3 +760,49 @@ extraEnv: [] # value: "http://proxy.example.com:8080" # - name: NO_PROXY # value: "localhost,127.0.0.1,.example.com" + +# OpenTelemetry (traces + logs + metrics). Off by default. When enabled, a shared +# ConfigMap (-otel-vars) is mounted via envFrom into the backend +# workloads (api, external-api, worker, worker-importers, beat-worker, +# automation-consumer, agent-consumer, webhook-consumer, outbox-poller, silo, +# live, live-exporter, space, pi-api, pi-beat, pi-worker), and each gets an +# inline OTEL_SERVICE_NAME so it reports its own service.name. web/admin are NOT +# wired: their only OTel is browser tracing, configured from the API's instance +# config via the frontend.* keys below — not per-workload env. +observability: + otel: + enabled: false + # OTLP collector endpoint. An https:// endpoint => secure gRPC. + endpoint: '' + protocol: grpc + # Extra OTLP exporter headers, "k1=v1,k2=v2" (e.g. a collector ingestion key). + # Rendered into a Secret, not the ConfigMap — see + # external_secrets.otel_env_existingSecret to supply it yourself instead. + headers: '' + # Deployment environment tag. Emitted as deployment.environment.name (the + # CURRENT semconv key — matches the node/pi/browser services for cross-service + # filtering). Leave blank to omit. + environment: '' + # Any additional resource attributes, "k1=v1,k2=v2". + resourceAttributes: '' + # Print spans to stdout (debug only). + debugConsole: false + # Trace sampler. always_on = export every span the service sees and, unlike + # parentbased_*, does NOT defer to an upstream traceparent's sampled flag — so + # browser-initiated POST/user-action traces aren't silently dropped (the "only + # GET shows up" symptom). For prod use parentbased_traceidratio with a ratio + # AND ensure browser tracing samples consistently. samplerArg is ignored by + # always_on. + sampler: always_on + samplerArg: '1.0' + # Browser/client tracing for web/admin/space — served to browsers by the API + # (read only by the API container). Turns on when enabled AND endpoint is set. + frontend: + enabled: false + # PUBLIC OTLP/HTTP endpoint the browser posts to (must be internet-reachable + # and CORS-enabled for the Plane web origin; the client appends /v1/traces). + endpoint: '' + # A non-empty header is REQUIRED cross-origin: it forces the web exporter + # onto XHR instead of navigator.sendBeacon (sendBeacon sends credentials, + # which CORS rejects against a wildcard ACAO). Value is arbitrary. + headers: 'x-otlp-browser=1'