Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion charts/plane-enterprise/Chart.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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/
Expand Down
34 changes: 34 additions & 0 deletions charts/plane-enterprise/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 `<release>-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 `<release>-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 `<release>-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.
Expand Down Expand Up @@ -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

Expand Down
79 changes: 79 additions & 0 deletions charts/plane-enterprise/questions.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
50 changes: 50 additions & 0 deletions charts/plane-enterprise/templates/_helpers.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -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 -}}
63 changes: 63 additions & 0 deletions charts/plane-enterprise/templates/config-secrets/otel.yaml
Original file line number Diff line number Diff line change
@@ -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 }}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 }}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading