Skip to content
Open
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: 2.5.0
version: 2.5.1
appVersion: "2.6.1"

home: https://plane.so/
Expand Down
26 changes: 26 additions & 0 deletions charts/plane-enterprise/templates/config-secrets/app-env.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,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 }}
---

Expand Down Expand Up @@ -104,3 +108,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"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Hardcoded cluster.local breaks custom cluster domains.

Elsewhere in this same template the cluster domain is parameterized (e.g. Line 36 and Line 97 use {{ .Values.env.default_cluster_domain | default "cluster.local" }}). The auto-target endpoint here pins cluster.local, so clusters with a custom DNS domain won't resolve the collector Service.

♻️ Align with the existing pattern
-  OTEL_EXPORTER_OTLP_ENDPOINT: "http://{{ .Release.Name }}-otel-collector.{{ .Release.Namespace }}.svc.cluster.local:4317"
+  OTEL_EXPORTER_OTLP_ENDPOINT: "http://{{ .Release.Name }}-otel-collector.{{ .Release.Namespace }}.svc.{{ .Values.env.default_cluster_domain | default "cluster.local" }}:4317"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@charts/plane-enterprise/templates/config-secrets/app-env.yaml` at line 122,
The OTEL_EXPORTER_OTLP_ENDPOINT value is hardcoded to "cluster.local" which
breaks clusters with custom domains; update the OTEL_EXPORTER_OTLP_ENDPOINT
entry to construct the service FQDN using the same pattern used elsewhere by
interpolating {{ .Release.Name }}, {{ .Release.Namespace }} and the
parameterized cluster domain via {{ .Values.env.default_cluster_domain | default
"cluster.local" }} so the endpoint resolves correctly for custom cluster domains
while preserving the collector host and port.

{{- else }}
OTEL_EXPORTER_OTLP_ENDPOINT: ""
{{- end }}
OTEL_EXPORTER_OTLP_PROTOCOL: {{ .Values.observability.otel.protocol | default "grpc" | quote }}
Comment on lines +121 to +126

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Auto-target endpoint hardcodes gRPC port 4317, ignoring protocol.

When endpoint is empty and the bundled collector is enabled, the backend is always wired to port 4317 (gRPC). If the user sets observability.otel.protocol: http/protobuf, the exporter (line 126 emits OTEL_EXPORTER_OTLP_PROTOCOL) will speak HTTP/protobuf against the gRPC port and exports will fail silently. The collector listens on both ports, so the client must select the matching one.

🐛 Proposed fix: pick port by protocol
   {{- else if .Values.observability.otel.collector.enabled }}
-  OTEL_EXPORTER_OTLP_ENDPOINT: "http://{{ .Release.Name }}-otel-collector.{{ .Release.Namespace }}.svc.cluster.local:4317"
+  {{- if eq (.Values.observability.otel.protocol | default "grpc") "grpc" }}
+  OTEL_EXPORTER_OTLP_ENDPOINT: "http://{{ .Release.Name }}-otel-collector.{{ .Release.Namespace }}.svc.cluster.local:4317"
+  {{- else }}
+  OTEL_EXPORTER_OTLP_ENDPOINT: "http://{{ .Release.Name }}-otel-collector.{{ .Release.Namespace }}.svc.cluster.local:4318"
+  {{- end }}
   {{- else }}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@charts/plane-enterprise/templates/config-secrets/app-env.yaml` around lines
121 - 126, The OTEL_EXPORTER_OTLP_ENDPOINT currently hardcodes port 4317 (gRPC)
regardless of .Values.observability.otel.protocol, causing protocol/port
mismatch; update the template that sets OTEL_EXPORTER_OTLP_ENDPOINT to choose
port based on the protocol (OTEL_EXPORTER_OTLP_PROTOCOL) — use 4317 for "grpc"
and 4318 for "http/protobuf" (or map other protocol values accordingly) when
.Values.observability.otel.collector.enabled is true, keeping the same host
formation ({{ .Release.Name }}-otel-collector.{{ .Release.Namespace
}}.svc.cluster.local) so the exporter speaks the correct port for the configured
protocol.

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 }}
120 changes: 120 additions & 0 deletions charts/plane-enterprise/templates/observability/otel-collector.yaml
Original file line number Diff line number Diff line change
@@ -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 }}
44 changes: 44 additions & 0 deletions charts/plane-enterprise/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -613,3 +613,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.<ns>.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<token>"
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: {}