From 98f5eb322827553efee66f5d4071a0a1ce7f1c07 Mon Sep 17 00:00:00 2001 From: Pratapa Lakshmi Date: Wed, 3 Jun 2026 13:40:08 +0530 Subject: [PATCH 1/3] feat(plane-enterprise): native OpenTelemetry APM support Add first-class OTEL configuration to the backend instead of relying on ad-hoc extraEnv. Bumps chart version to 2.5.1. - values.yaml: new `observability.otel` block (off by default) with a nested `collector` sub-block for an optional bundled OTLP collector. - config-secrets/app-env.yaml: when `observability.otel.enabled`, inject OTEL_* into the backend `-app-vars` ConfigMap (scoped to the six workloads that envFrom it: api, worker, beat-worker, automation-consumer, outbox-poller, migrator). Auth headers go into `-app-secrets`. When the endpoint is blank and the bundled collector is enabled, the backend auto-targets the in-cluster collector Service. - templates/observability/otel-collector.yaml: bundled collector (ConfigMap/Service/Deployment), gated on `observability.otel.collector.enabled`, with an overridable config that defaults to an OTLP-in -> debug-out pipeline. Co-Authored-By: Claude Opus 4.8 (1M context) --- charts/plane-enterprise/Chart.yaml | 2 +- .../templates/config-secrets/app-env.yaml | 26 ++++ .../observability/otel-collector.yaml | 120 ++++++++++++++++++ charts/plane-enterprise/values.yaml | 44 +++++++ 4 files changed, 191 insertions(+), 1 deletion(-) create mode 100644 charts/plane-enterprise/templates/observability/otel-collector.yaml diff --git a/charts/plane-enterprise/Chart.yaml b/charts/plane-enterprise/Chart.yaml index 2b5e506..686e0dd 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: 2.7.0 +version: 2.8.0 appVersion: "2.6.3" home: https://plane.so/ diff --git a/charts/plane-enterprise/templates/config-secrets/app-env.yaml b/charts/plane-enterprise/templates/config-secrets/app-env.yaml index 19b9e01..661d4f5 100644 --- a/charts/plane-enterprise/templates/config-secrets/app-env.yaml +++ b/charts/plane-enterprise/templates/config-secrets/app-env.yaml @@ -45,6 +45,10 @@ stringData: {{- if include "plane.s3CAEnabled" . }} AWS_CA_BUNDLE: "/etc/ssl/certs/ca-certificates.crt" {{- end }} + + {{- if and .Values.observability.otel.enabled .Values.observability.otel.headers }} + OTEL_EXPORTER_OTLP_HEADERS: {{ .Values.observability.otel.headers | quote }} + {{- end }} {{- end }} --- @@ -108,3 +112,25 @@ data: {{- else}} CORS_ALLOWED_ORIGINS: "http://{{ .Values.license.licenseDomain }},https://{{ .Values.license.licenseDomain }}" {{- end }} + + {{- /* OpenTelemetry APM for the Django backend (api, worker, beat-worker, + automation-consumer, outbox-poller, migrator). No-op in the app unless + OTEL_ENABLED=1 and an endpoint is set. Auth headers, if any, live in the + app-secrets Secret above. See docs/otel-api-observability in plane-ee. */}} + {{- if .Values.observability.otel.enabled }} + OTEL_ENABLED: "1" + OTEL_SERVICE_NAME: {{ .Values.observability.otel.serviceName | default "plane-api" | quote }} + {{- if .Values.observability.otel.endpoint }} + OTEL_EXPORTER_OTLP_ENDPOINT: {{ .Values.observability.otel.endpoint | quote }} + {{- else if .Values.observability.otel.collector.enabled }} + OTEL_EXPORTER_OTLP_ENDPOINT: "http://{{ .Release.Name }}-otel-collector.{{ .Release.Namespace }}.svc.cluster.local:4317" + {{- else }} + OTEL_EXPORTER_OTLP_ENDPOINT: "" + {{- end }} + OTEL_EXPORTER_OTLP_PROTOCOL: {{ .Values.observability.otel.protocol | default "grpc" | quote }} + OTEL_TRACES_SAMPLER: {{ .Values.observability.otel.tracesSampler | default "parentbased_traceidratio" | quote }} + OTEL_TRACES_SAMPLER_ARG: {{ .Values.observability.otel.tracesSamplerArg | default "0.1" | quote }} + {{- if .Values.observability.otel.resourceAttributes }} + OTEL_RESOURCE_ATTRIBUTES: {{ .Values.observability.otel.resourceAttributes | quote }} + {{- end }} + {{- end }} diff --git a/charts/plane-enterprise/templates/observability/otel-collector.yaml b/charts/plane-enterprise/templates/observability/otel-collector.yaml new file mode 100644 index 0000000..73ed52d --- /dev/null +++ b/charts/plane-enterprise/templates/observability/otel-collector.yaml @@ -0,0 +1,120 @@ +{{- /* + Bundled OpenTelemetry Collector. Rendered only when OTEL is enabled AND the + bundled collector is requested. Self-hosters pointing at an external collector + (Datadog Agent, Grafana Cloud, an existing cluster collector) leave + observability.otel.collector.enabled=false and these resources are omitted. + + When enabled with an empty observability.otel.endpoint, the backend auto-targets + this collector's in-cluster Service (see config-secrets/app-env.yaml). +*/}} +{{- if and .Values.observability.otel.enabled .Values.observability.otel.collector.enabled }} +apiVersion: v1 +kind: ConfigMap +metadata: + namespace: {{ .Release.Namespace }} + name: {{ .Release.Name }}-otel-collector-config + labels: + app.name: {{ .Release.Namespace }}-{{ .Release.Name }}-otel-collector +data: + config.yaml: | +{{- if .Values.observability.otel.collector.config }} +{{ .Values.observability.otel.collector.config | indent 4 }} +{{- else }} + # Default pipeline: receive OTLP, log via `debug`. Override the whole config + # with observability.otel.collector.config to export to a real backend. + receivers: + otlp: + protocols: + grpc: + endpoint: 0.0.0.0:4317 + http: + endpoint: 0.0.0.0:4318 + processors: + batch: + timeout: 10s + send_batch_size: 1024 + exporters: + debug: + verbosity: detailed + service: + pipelines: + traces: + receivers: [otlp] + processors: [batch] + exporters: [debug] + metrics: + receivers: [otlp] + processors: [batch] + exporters: [debug] +{{- end }} + +--- + +apiVersion: v1 +kind: Service +metadata: + namespace: {{ .Release.Namespace }} + name: {{ .Release.Name }}-otel-collector + labels: + app.name: {{ .Release.Namespace }}-{{ .Release.Name }}-otel-collector +spec: + type: ClusterIP + ports: + - name: otlp-grpc + port: 4317 + protocol: TCP + targetPort: 4317 + - name: otlp-http + port: 4318 + protocol: TCP + targetPort: 4318 + selector: + app.name: {{ .Release.Namespace }}-{{ .Release.Name }}-otel-collector + +--- + +apiVersion: apps/v1 +kind: Deployment +metadata: + namespace: {{ .Release.Namespace }} + name: {{ .Release.Name }}-otel-collector + {{- include "plane.labelsAndAnnotations" .Values.observability.otel.collector }} +spec: + replicas: {{ .Values.observability.otel.collector.replicas | default 1 }} + selector: + matchLabels: + app.name: {{ .Release.Namespace }}-{{ .Release.Name }}-otel-collector + template: + metadata: + namespace: {{ .Release.Namespace }} + labels: + app.name: {{ .Release.Namespace }}-{{ .Release.Name }}-otel-collector + annotations: + timestamp: {{ now | quote }} + spec: + {{- include "plane.podScheduling" .Values.observability.otel.collector }} + containers: + - name: {{ .Release.Name }}-otel-collector + imagePullPolicy: {{ .Values.observability.otel.collector.pullPolicy | default "IfNotPresent" }} + image: {{ .Values.observability.otel.collector.image | default "otel/opentelemetry-collector-contrib:0.115.1" }} + args: ["--config=/etc/otel/config.yaml"] + ports: + - name: otlp-grpc + containerPort: 4317 + - name: otlp-http + containerPort: 4318 + resources: + requests: + memory: {{ .Values.observability.otel.collector.memoryRequest | default "128Mi" | quote }} + cpu: {{ .Values.observability.otel.collector.cpuRequest | default "100m" | quote }} + limits: + memory: {{ .Values.observability.otel.collector.memoryLimit | default "512Mi" | quote }} + cpu: {{ .Values.observability.otel.collector.cpuLimit | default "500m" | quote }} + volumeMounts: + - name: config + mountPath: /etc/otel + volumes: + - name: config + configMap: + name: {{ .Release.Name }}-otel-collector-config +{{- end }} diff --git a/charts/plane-enterprise/values.yaml b/charts/plane-enterprise/values.yaml index c110e41..7f63ff4 100644 --- a/charts/plane-enterprise/values.yaml +++ b/charts/plane-enterprise/values.yaml @@ -639,3 +639,47 @@ extraEnv: [] # value: "http://proxy.example.com:8080" # - name: NO_PROXY # value: "localhost,127.0.0.1,.example.com" + +# OpenTelemetry APM for the Django backend (api, worker, beat-worker, +# automation-consumer, outbox-poller, migrator). Off by default — when +# enabled, OTEL env vars are injected into the backend app-vars ConfigMap. +# Point `endpoint` at any OTLP collector. See docs/otel-api-observability +# in the plane-ee repo for the full guide. +observability: + otel: + enabled: false + # Service name reported to your APM backend. + serviceName: plane-api + # OTLP receiver, e.g. http://otel-collector..svc.cluster.local:4317 + endpoint: "" + # grpc | http/protobuf + protocol: grpc + # Standard OTEL head sampler. Set tracesSamplerArg to "1.0" to capture every request. + tracesSampler: parentbased_traceidratio + tracesSamplerArg: "0.1" + # Extra resource attrs, e.g. "deployment.environment=prod,service.version=v2.6.1" + resourceAttributes: "" + # Auth headers for SaaS backends (rendered into app-secrets), e.g. "Authorization=Bearer%20" + headers: "" + # Bundled in-cluster OpenTelemetry Collector. When enabled, the chart deploys + # a collector Deployment/Service/ConfigMap and — if `endpoint` above is empty — + # the backend auto-targets it. Leave disabled to export to an external + # collector (Datadog Agent, Grafana Cloud, an existing cluster collector). + collector: + enabled: false + image: otel/opentelemetry-collector-contrib:0.115.1 + pullPolicy: IfNotPresent + replicas: 1 + memoryRequest: 128Mi + cpuRequest: 100m + memoryLimit: 512Mi + cpuLimit: 500m + # Optional full collector config override (YAML string). When empty, a + # default OTLP-in → `debug`-out pipeline is used (good for verifying spans; + # replace `debug` with a real exporter for production). + config: "" + nodeSelector: {} + tolerations: [] + affinity: {} + labels: {} + annotations: {} From 630c57c061f246ce78c4826ea9dd557ffdf6919d Mon Sep 17 00:00:00 2001 From: Pratapa Lakshmi Date: Mon, 6 Jul 2026 12:34:43 +0530 Subject: [PATCH 2/3] feat(plane-enterprise): extend OTel coverage to Node/pi/space workloads + browser tracing The initial OTel commit only instrumented the Django backend (via the app-vars ConfigMap). Bring the chart to parity with the commercial-deployments kustomize otel-observability component: - New plane.otelServiceEnv helper renders the shared OTEL_* keys inline so the workloads that do NOT consume app-vars get full coverage: the Node services (silo, live), the SSR frontend (space) and Plane Intelligence (pi-api, pi-worker, pi-beat). Each reports its own OTEL_SERVICE_NAME. - Django workers (worker, beat-worker, automation-consumer, outbox-poller) get a per-service OTEL_SERVICE_NAME override so their traces are distinguishable from the api (which keeps the configurable serviceName from app-vars). - app-vars gains OTEL_DEBUG_CONSOLE and the browser/client tracing keys (FRONTEND_OTEL_ENABLED / FRONTEND_OTLP_ENDPOINT / FRONTEND_OTLP_HEADERS), which are served to web/admin/space browsers by the API and so belong on the API alone. - values.yaml: add observability.otel.debugConsole and observability.otel.frontend.*. - Fix the bundled otel-collector's plane.labelsAndAnnotations call to the dict signature introduced by the recommended-labels change on master (rebase fix). Co-Authored-By: Claude Opus 4.8 (1M context) --- .../plane-enterprise/templates/_helpers.tpl | 58 +++++++++++++++++++ .../templates/config-secrets/app-env.yaml | 13 +++++ .../observability/otel-collector.yaml | 2 +- .../automation-consumer.deployment.yaml | 7 ++- .../workloads/beat-worker.deployment.yaml | 7 ++- .../templates/workloads/live.deployment.yaml | 5 +- .../workloads/outbox-poller.deployment.yaml | 7 ++- .../workloads/pi-api.deployment.yaml | 5 +- .../workloads/pi-beat.deployment.yaml | 5 +- .../workloads/pi-worker.deployment.yaml | 5 +- .../templates/workloads/silo.deployment.yaml | 5 +- .../templates/workloads/space.deployment.yaml | 7 ++- .../workloads/worker.deployment.yaml | 5 +- charts/plane-enterprise/values.yaml | 36 +++++++++--- 14 files changed, 147 insertions(+), 20 deletions(-) diff --git a/charts/plane-enterprise/templates/_helpers.tpl b/charts/plane-enterprise/templates/_helpers.tpl index 5df2e59..d9fc52d 100644 --- a/charts/plane-enterprise/templates/_helpers.tpl +++ b/charts/plane-enterprise/templates/_helpers.tpl @@ -242,3 +242,61 @@ Caller must nindent to the correct depth. value: "/ca-bundle/custom-ca-bundle.crt" {{- end }} {{- end -}} + +{{/* +OpenTelemetry inline env vars for a single workload. + +The Django backend (api, worker, beat-worker, automation-consumer, outbox-poller, +migrator) already sources the shared OTEL_* config from the app-vars ConfigMap +(see config-secrets/app-env.yaml). This helper renders those same keys INLINE so +that services which do NOT consume app-vars — the Node services (silo, live), the +SSR frontend (space) and Plane Intelligence (pi-api, pi-worker, pi-beat) — get +full OTel coverage, and so every workload can report its own OTEL_SERVICE_NAME +(inline env wins over envFrom). Kept in sync with the OTEL block in app-env.yaml. + +Call with a dict carrying the root context and the per-service name: + {{- include "plane.otelServiceEnv" (dict "context" $ "service" "silo") }} +Pass "full" false to emit ONLY the OTEL_SERVICE_NAME override — used by Django +workloads that already get the rest of the OTEL_* keys from app-vars. +Caller must indent to the correct depth (matches the s3CA*EnvVars pattern). +*/}} +{{- define "plane.otelServiceEnv" -}} +{{- $ctx := .context -}} +{{- $otel := $ctx.Values.observability.otel -}} +{{- if $otel.enabled -}} +- name: OTEL_SERVICE_NAME + value: {{ .service | quote }} +{{- if not (eq .full false) }} +- name: OTEL_ENABLED + value: "1" +{{- if $otel.endpoint }} +- name: OTEL_EXPORTER_OTLP_ENDPOINT + value: {{ $otel.endpoint | quote }} +{{- else if $otel.collector.enabled }} +- name: OTEL_EXPORTER_OTLP_ENDPOINT + value: "http://{{ $ctx.Release.Name }}-otel-collector.{{ $ctx.Release.Namespace }}.svc.cluster.local:4317" +{{- else }} +- name: OTEL_EXPORTER_OTLP_ENDPOINT + value: "" +{{- end }} +- name: OTEL_EXPORTER_OTLP_PROTOCOL + value: {{ $otel.protocol | default "grpc" | quote }} +- name: OTEL_TRACES_SAMPLER + value: {{ $otel.tracesSampler | default "parentbased_traceidratio" | quote }} +- name: OTEL_TRACES_SAMPLER_ARG + value: {{ $otel.tracesSamplerArg | default "0.1" | quote }} +{{- if $otel.resourceAttributes }} +- name: OTEL_RESOURCE_ATTRIBUTES + value: {{ $otel.resourceAttributes | quote }} +{{- end }} +{{- if $otel.debugConsole }} +- name: OTEL_DEBUG_CONSOLE + value: "1" +{{- end }} +{{- if $otel.headers }} +- name: OTEL_EXPORTER_OTLP_HEADERS + value: {{ $otel.headers | quote }} +{{- end }} +{{- end }} +{{- end }} +{{- end -}} diff --git a/charts/plane-enterprise/templates/config-secrets/app-env.yaml b/charts/plane-enterprise/templates/config-secrets/app-env.yaml index 661d4f5..0cc0339 100644 --- a/charts/plane-enterprise/templates/config-secrets/app-env.yaml +++ b/charts/plane-enterprise/templates/config-secrets/app-env.yaml @@ -133,4 +133,17 @@ data: {{- if .Values.observability.otel.resourceAttributes }} OTEL_RESOURCE_ATTRIBUTES: {{ .Values.observability.otel.resourceAttributes | quote }} {{- end }} + {{- if .Values.observability.otel.debugConsole }} + OTEL_DEBUG_CONSOLE: "1" + {{- end }} + {{- /* Browser/client tracing. Read ONLY by the API, which serves it to the + web/admin/space browsers via its public instance config — a no-op env on + every other workload, so it lives here on the API's app-vars alone. */}} + {{- if .Values.observability.otel.frontend.enabled }} + FRONTEND_OTEL_ENABLED: "1" + FRONTEND_OTLP_ENDPOINT: {{ .Values.observability.otel.frontend.endpoint | default .Values.observability.otel.endpoint | quote }} + {{- if .Values.observability.otel.frontend.headers }} + FRONTEND_OTLP_HEADERS: {{ .Values.observability.otel.frontend.headers | quote }} + {{- end }} + {{- end }} {{- end }} diff --git a/charts/plane-enterprise/templates/observability/otel-collector.yaml b/charts/plane-enterprise/templates/observability/otel-collector.yaml index 73ed52d..c42c38a 100644 --- a/charts/plane-enterprise/templates/observability/otel-collector.yaml +++ b/charts/plane-enterprise/templates/observability/otel-collector.yaml @@ -78,7 +78,7 @@ kind: Deployment metadata: namespace: {{ .Release.Namespace }} name: {{ .Release.Name }}-otel-collector - {{- include "plane.labelsAndAnnotations" .Values.observability.otel.collector }} + {{- include "plane.labelsAndAnnotations" (dict "context" $ "values" .Values.observability.otel.collector) }} spec: replicas: {{ .Values.observability.otel.collector.replicas | default 1 }} selector: diff --git a/charts/plane-enterprise/templates/workloads/automation-consumer.deployment.yaml b/charts/plane-enterprise/templates/workloads/automation-consumer.deployment.yaml index e7b1213..92e215f 100644 --- a/charts/plane-enterprise/templates/workloads/automation-consumer.deployment.yaml +++ b/charts/plane-enterprise/templates/workloads/automation-consumer.deployment.yaml @@ -53,9 +53,14 @@ 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 }} + {{- if or .Values.extraEnv .Values.observability.otel.enabled }} env: + {{- with (include "plane.otelServiceEnv" (dict "context" $ "service" "automation-consumer" "full" false)) }} +{{ . | indent 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 81bc8c9..e32702e 100644 --- a/charts/plane-enterprise/templates/workloads/beat-worker.deployment.yaml +++ b/charts/plane-enterprise/templates/workloads/beat-worker.deployment.yaml @@ -54,9 +54,14 @@ 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 }} + {{- if or .Values.extraEnv .Values.observability.otel.enabled }} env: + {{- with (include "plane.otelServiceEnv" (dict "context" $ "service" "beat-worker" "full" false)) }} +{{ . | indent 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/live.deployment.yaml b/charts/plane-enterprise/templates/workloads/live.deployment.yaml index 573bb4d..d53b0d3 100644 --- a/charts/plane-enterprise/templates/workloads/live.deployment.yaml +++ b/charts/plane-enterprise/templates/workloads/live.deployment.yaml @@ -96,9 +96,12 @@ 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" .) }} + {{- if or .Values.extraEnv (include "plane.s3CAEnabled" .) .Values.observability.otel.enabled }} env: {{- with (include "plane.s3CANodeEnvVars" .) }} +{{ . | indent 10 }} + {{- end }} + {{- with (include "plane.otelServiceEnv" (dict "context" $ "service" "live")) }} {{ . | indent 10 }} {{- end }} {{- if .Values.extraEnv }} diff --git a/charts/plane-enterprise/templates/workloads/outbox-poller.deployment.yaml b/charts/plane-enterprise/templates/workloads/outbox-poller.deployment.yaml index 9993aff..fd95c3e 100644 --- a/charts/plane-enterprise/templates/workloads/outbox-poller.deployment.yaml +++ b/charts/plane-enterprise/templates/workloads/outbox-poller.deployment.yaml @@ -47,9 +47,14 @@ 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 }} + {{- if or .Values.extraEnv .Values.observability.otel.enabled }} env: + {{- with (include "plane.otelServiceEnv" (dict "context" $ "service" "outbox-poller" "full" false)) }} +{{ . | indent 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 1db3271..795ade5 100644 --- a/charts/plane-enterprise/templates/workloads/pi-api.deployment.yaml +++ b/charts/plane-enterprise/templates/workloads/pi-api.deployment.yaml @@ -88,9 +88,12 @@ 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" .) }} + {{- if or .Values.extraEnv (include "plane.s3CAEnabled" .) .Values.observability.otel.enabled }} env: {{- with (include "plane.s3CAEnvVars" .) }} +{{ . | indent 10 }} + {{- end }} + {{- with (include "plane.otelServiceEnv" (dict "context" $ "service" "pi-api")) }} {{ . | indent 10 }} {{- end }} {{- if .Values.extraEnv }} diff --git a/charts/plane-enterprise/templates/workloads/pi-beat.deployment.yaml b/charts/plane-enterprise/templates/workloads/pi-beat.deployment.yaml index d91b6f8..e249417 100644 --- a/charts/plane-enterprise/templates/workloads/pi-beat.deployment.yaml +++ b/charts/plane-enterprise/templates/workloads/pi-beat.deployment.yaml @@ -64,9 +64,12 @@ 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" .) }} + {{- if or .Values.extraEnv (include "plane.s3CAEnabled" .) .Values.observability.otel.enabled }} env: {{- with (include "plane.s3CAEnvVars" .) }} +{{ . | indent 10 }} + {{- end }} + {{- with (include "plane.otelServiceEnv" (dict "context" $ "service" "pi-beat")) }} {{ . | indent 10 }} {{- end }} {{- if .Values.extraEnv }} diff --git a/charts/plane-enterprise/templates/workloads/pi-worker.deployment.yaml b/charts/plane-enterprise/templates/workloads/pi-worker.deployment.yaml index abe1a6d..f090555 100644 --- a/charts/plane-enterprise/templates/workloads/pi-worker.deployment.yaml +++ b/charts/plane-enterprise/templates/workloads/pi-worker.deployment.yaml @@ -64,9 +64,12 @@ 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" .) }} + {{- if or .Values.extraEnv (include "plane.s3CAEnabled" .) .Values.observability.otel.enabled }} env: {{- with (include "plane.s3CAEnvVars" .) }} +{{ . | indent 10 }} + {{- end }} + {{- with (include "plane.otelServiceEnv" (dict "context" $ "service" "pi-worker")) }} {{ . | indent 10 }} {{- end }} {{- if .Values.extraEnv }} diff --git a/charts/plane-enterprise/templates/workloads/silo.deployment.yaml b/charts/plane-enterprise/templates/workloads/silo.deployment.yaml index 32ec41f..1fd273c 100644 --- a/charts/plane-enterprise/templates/workloads/silo.deployment.yaml +++ b/charts/plane-enterprise/templates/workloads/silo.deployment.yaml @@ -122,9 +122,12 @@ 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" .) }} + {{- if or .Values.extraEnv (include "plane.s3CAEnabled" .) .Values.observability.otel.enabled }} env: {{- with (include "plane.s3CANodeEnvVars" .) }} +{{ . | indent 10 }} + {{- end }} + {{- with (include "plane.otelServiceEnv" (dict "context" $ "service" "silo")) }} {{ . | indent 10 }} {{- end }} {{- if .Values.extraEnv }} diff --git a/charts/plane-enterprise/templates/workloads/space.deployment.yaml b/charts/plane-enterprise/templates/workloads/space.deployment.yaml index 91b4f21..0cab8ec 100644 --- a/charts/plane-enterprise/templates/workloads/space.deployment.yaml +++ b/charts/plane-enterprise/templates/workloads/space.deployment.yaml @@ -58,9 +58,14 @@ spec: limits: memory: {{ .Values.services.space.memoryLimit | default "1000Mi" | quote }} cpu: {{ .Values.services.space.cpuLimit | default "500m" | quote}} - {{- if .Values.extraEnv }} + {{- if or .Values.extraEnv .Values.observability.otel.enabled }} env: + {{- with (include "plane.otelServiceEnv" (dict "context" $ "service" "space")) }} +{{ . | indent 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/worker.deployment.yaml b/charts/plane-enterprise/templates/workloads/worker.deployment.yaml index cff2882..f0deb0c 100644 --- a/charts/plane-enterprise/templates/workloads/worker.deployment.yaml +++ b/charts/plane-enterprise/templates/workloads/worker.deployment.yaml @@ -70,9 +70,12 @@ spec: optional: false {{- end }} - {{- if or .Values.extraEnv (include "plane.s3CAEnabled" .) }} + {{- if or .Values.extraEnv (include "plane.s3CAEnabled" .) .Values.observability.otel.enabled }} env: {{- with (include "plane.s3CAEnvVars" .) }} +{{ . | indent 10 }} + {{- end }} + {{- with (include "plane.otelServiceEnv" (dict "context" $ "service" "worker" "full" false)) }} {{ . | indent 10 }} {{- end }} {{- if .Values.extraEnv }} diff --git a/charts/plane-enterprise/values.yaml b/charts/plane-enterprise/values.yaml index 7f63ff4..08b9b1e 100644 --- a/charts/plane-enterprise/values.yaml +++ b/charts/plane-enterprise/values.yaml @@ -640,27 +640,45 @@ extraEnv: [] # - name: NO_PROXY # value: "localhost,127.0.0.1,.example.com" -# OpenTelemetry APM for the Django backend (api, worker, beat-worker, -# automation-consumer, outbox-poller, migrator). Off by default — when -# enabled, OTEL env vars are injected into the backend app-vars ConfigMap. -# Point `endpoint` at any OTLP collector. See docs/otel-api-observability -# in the plane-ee repo for the full guide. +# OpenTelemetry APM. Off by default — when enabled, OTEL env vars are injected +# into every instrumented workload: the Django backend (api, worker, beat-worker, +# automation-consumer, outbox-poller, migrator) via the app-vars ConfigMap, and +# the Node services (silo, live), the SSR frontend (space) and Plane Intelligence +# (pi-api, pi-worker, pi-beat) via inline env. Each workload reports its own +# OTEL_SERVICE_NAME. Point `endpoint` at any OTLP collector. See +# docs/otel-api-observability in the plane-ee repo for the full guide. observability: otel: enabled: false - # Service name reported to your APM backend. + # Service name reported to your APM backend for the api workload. The other + # workloads report their own name (silo, live, space, worker, pi-api, …). serviceName: plane-api # OTLP receiver, e.g. http://otel-collector..svc.cluster.local:4317 endpoint: "" # grpc | http/protobuf protocol: grpc - # Standard OTEL head sampler. Set tracesSamplerArg to "1.0" to capture every request. + # Standard OTEL head sampler. Set tracesSampler=always_on (or tracesSamplerArg + # to "1.0") to capture every request — recommended for test/load/debug envs. tracesSampler: parentbased_traceidratio tracesSamplerArg: "0.1" - # Extra resource attrs, e.g. "deployment.environment=prod,service.version=v2.6.1" + # Extra resource attrs, e.g. "deployment.environment.name=prod,service.version=v2.6.3" resourceAttributes: "" - # Auth headers for SaaS backends (rendered into app-secrets), e.g. "Authorization=Bearer%20" + # Auth headers for SaaS backends (rendered into app-secrets for the Django + # backend, inline for the Node/pi workloads), e.g. "Authorization=Bearer%20" headers: "" + # Emit spans to the container console in addition to the OTLP exporter (debug aid). + debugConsole: false + # Browser/client tracing for the web/admin/space frontends. These keys are + # served to browsers by the API (via its public instance config) and are only + # read by the API workload, so they are injected into app-vars alongside the + # backend OTEL_* keys. Turn on with frontend.enabled=true. + frontend: + enabled: false + # OTLP endpoint the browser posts spans to (must be reachable cross-origin + # from the browser), e.g. https://telemetry.example.com + endpoint: "" + # Optional auth headers for the browser exporter. + headers: "" # Bundled in-cluster OpenTelemetry Collector. When enabled, the chart deploys # a collector Deployment/Service/ConfigMap and — if `endpoint` above is empty — # the backend auto-targets it. Leave disabled to export to an external From 9962e170a86e34e2f4b81bd541e5932b75142508 Mon Sep 17 00:00:00 2001 From: Pratapa Lakshmi Date: Mon, 6 Jul 2026 12:37:52 +0530 Subject: [PATCH 3/3] fix(plane-enterprise): derive OTel collector endpoint port from protocol and honor custom cluster domain Address CodeRabbit review on the bundled-collector endpoint (app-vars ConfigMap and the plane.otelServiceEnv helper): - Port was hardcoded to 4317 (gRPC) regardless of OTEL_EXPORTER_OTLP_PROTOCOL, causing a protocol/port mismatch for http/protobuf. Now selects 4317 for grpc and 4318 otherwise; the collector Service already exposes both. - Endpoint host hardcoded "cluster.local", breaking clusters with a custom domain. Now uses .Values.env.default_cluster_domain (default cluster.local), matching every other in-cluster URL in the chart. Co-Authored-By: Claude Opus 4.8 (1M context) --- charts/plane-enterprise/templates/_helpers.tpl | 2 +- charts/plane-enterprise/templates/config-secrets/app-env.yaml | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/charts/plane-enterprise/templates/_helpers.tpl b/charts/plane-enterprise/templates/_helpers.tpl index d9fc52d..1dcfc13 100644 --- a/charts/plane-enterprise/templates/_helpers.tpl +++ b/charts/plane-enterprise/templates/_helpers.tpl @@ -274,7 +274,7 @@ Caller must indent to the correct depth (matches the s3CA*EnvVars pattern). value: {{ $otel.endpoint | quote }} {{- else if $otel.collector.enabled }} - name: OTEL_EXPORTER_OTLP_ENDPOINT - value: "http://{{ $ctx.Release.Name }}-otel-collector.{{ $ctx.Release.Namespace }}.svc.cluster.local:4317" + value: "http://{{ $ctx.Release.Name }}-otel-collector.{{ $ctx.Release.Namespace }}.svc.{{ $ctx.Values.env.default_cluster_domain | default "cluster.local" }}:{{ eq ($otel.protocol | default "grpc") "grpc" | ternary "4317" "4318" }}" {{- else }} - name: OTEL_EXPORTER_OTLP_ENDPOINT value: "" diff --git a/charts/plane-enterprise/templates/config-secrets/app-env.yaml b/charts/plane-enterprise/templates/config-secrets/app-env.yaml index 0cc0339..47a1e85 100644 --- a/charts/plane-enterprise/templates/config-secrets/app-env.yaml +++ b/charts/plane-enterprise/templates/config-secrets/app-env.yaml @@ -123,7 +123,8 @@ data: {{- if .Values.observability.otel.endpoint }} OTEL_EXPORTER_OTLP_ENDPOINT: {{ .Values.observability.otel.endpoint | quote }} {{- else if .Values.observability.otel.collector.enabled }} - OTEL_EXPORTER_OTLP_ENDPOINT: "http://{{ .Release.Name }}-otel-collector.{{ .Release.Namespace }}.svc.cluster.local:4317" + {{- $collectorPort := eq (.Values.observability.otel.protocol | default "grpc") "grpc" | ternary "4317" "4318" }} + OTEL_EXPORTER_OTLP_ENDPOINT: "http://{{ .Release.Name }}-otel-collector.{{ .Release.Namespace }}.svc.{{ .Values.env.default_cluster_domain | default "cluster.local" }}:{{ $collectorPort }}" {{- else }} OTEL_EXPORTER_OTLP_ENDPOINT: "" {{- end }}