feat: Add native OpenTelemetry (OTLP) support for metrics and traces#1559
Open
knechtionscoding wants to merge 1 commit into
Open
feat: Add native OpenTelemetry (OTLP) support for metrics and traces#1559knechtionscoding wants to merge 1 commit into
knechtionscoding wants to merge 1 commit into
Conversation
knechtionscoding
had a problem deploying
to
ok-to-test
July 24, 2026 09:51 — with
GitHub Actions
Error
knechtionscoding
marked this pull request as ready for review
July 24, 2026 09:53
There was a problem hiding this comment.
All reported issues were addressed across 23 files
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
Wire opt-in OTLP export into the controller and spawner. When OTEL_EXPORTER_OTLP_ENDPOINT is set, metrics are pushed by bridging the existing controller-runtime Prometheus registry into an OTLP reader, and reconcile loops emit spans. The spawner stamps W3C trace context onto Task annotations and the controller injects TRACEPARENT into agent pods, giving an end-to-end spawner -> Task -> pod trace. With no endpoint configured the behavior is unchanged (Prometheus /metrics only). Folds in the distributed-tracing scope from kelos-dev#797. Fixes kelos-dev#1003 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
knechtionscoding
force-pushed
the
agent/otel-otlp-support
branch
from
July 24, 2026 10:46
7f4261f to
4310b85
Compare
knechtionscoding
had a problem deploying
to
ok-to-test
July 24, 2026 10:46 — with
GitHub Actions
Error
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What type of PR is this?
/kind feature
What this PR does / why we need it:
Adds native OpenTelemetry (OTLP) support for metrics and traces to the Kelos
controller and spawner. Previously the only observability surface was the
Prometheus
/metricspull endpoint, and there was no distributed tracing atall — so operators running a mixed-signal stack (OTEL Collector →
Grafana/Tempo/Honeycomb/Datadog/etc.) had to bolt on a Prometheus-scrape bridge
and had no way to see the spawner → Task → agent-pod pipeline as a single trace.
Everything here is opt-in and backward-compatible: with no OTLP endpoint
configured, behavior is identical to today (Prometheus
/metricsonly). SettingOTEL_EXPORTER_OTLP_ENDPOINT(or the Helmobservability.otlp.endpointvalue)turns on OTLP export.
This PR folds in the distributed-tracing scope from #797 alongside the
metrics/traces scope of #1003, per maintainer agreement to consolidate the two
into one feature.
What it adds:
internal/observability):Setup()installs globalTracer/Meter providers with OTLP exporters when an endpoint is configured, and
returns a graceful shutdown. It honors the standard OTEL environment variables
(
OTEL_EXPORTER_OTLP_ENDPOINT,..._PROTOCOL,..._HEADERS,OTEL_RESOURCE_ATTRIBUTES,OTEL_SERVICE_NAME) and selects grpc orhttp/protobuf. It fails fast if an endpoint is set but an exporter cannot
be built, rather than silently degrading to no export.
metric, the existing controller-runtime
metrics.Registry(which bothbinaries already register on) is bridged into an OTLP
PeriodicReader. Everymetric already scraped from
/metricsis also pushed over OTLP, and the pullendpoint keeps working unchanged.
reconcile.<Kind>span (all ninereconcilers instrumented uniformly). The spawner emits a
spawner.discoverspan per discovery cycle with a child
spawner.create_taskspan per Task.Task's
kelos.dev/traceparentannotation; the Task controller extracts it soits reconcile joins the same trace; and the agent pod receives it as the
standard
TRACEPARENTenv var so OTEL-aware agents can continue the trace.The controller also mirrors its OTLP env onto the spawner Deployments it
launches so they export to the same collector.
Which issue(s) this PR is related to:
Fixes #1003
Folds in and supersedes #797 (OpenTelemetry distributed tracing) — the tracing
design from #797 is implemented here.
Special notes for your reviewer:
Opt-in / zero migration:
Setup()and all propagation helpers are no-opswhen no OTLP endpoint is configured; the Prometheus endpoint is untouched. The
Helm chart renders no OTEL env unless
observability.otlp.endpointis set(verified via
helm template).Prometheus bridge, not re-instrumentation: metrics export reads the shared
sigs.k8s.io/controller-runtime/pkg/metricsregistry viago.opentelemetry.io/contrib/bridges/prometheus, so no metric call siteschanged and there is no risk of metric drift between the two surfaces.
Reconcile spans are added as a uniform two-line insert per reconciler
(
startReconcileSpanininternal/controller/tracing.go). Spans resolvethrough the global TracerProvider, so they are cheap no-ops until
Setupinstalls a provider.
Dependency churn: adding OpenTelemetry v1.44 pulls forward several
transitive deps (grpc,
golang.org/x/*, genproto, protobuf).go mod tidyis clean and
make verifypasses.Scope / follow-ups: OTLP logs export (issue item 4) is intentionally
left as a follow-up. Per-step spans inside a reconcile (e.g.
task.check_dependencies,task.build_job) can be layered on later; this PRestablishes the per-reconcile span and the cross-resource trace so those are
additive.
In-pod tracing is scoped to one-shot Task pods (important — follow-up
planned): The
TRACEPARENTenv injection assumes the identity one pod =one Task = one trace, which only holds for the one-shot agent Job created per
Task. It does not cover long-lived, multiplexed pods:
JobBuilder.Buildfrom a synthetic Taskthat carries no trace annotation, so no
TRACEPARENTis injected. (This isthe correct outcome — a session pod handles many turns, so a single static
traceparent would incorrectly fuse unrelated turns into one unbounded
trace.)
WorkerPoolReconciler.buildStatefulSetandnever go through
JobBuilder.Build, so they receive noTRACEPARENTatall.
Controller-side coverage is unaffected: Session/SessionSpawner/WorkerPool
reconciles still emit their own
reconcile.<Kind>spans (one per reconcile),and metrics are aggregated on the controller/spawner registries independent of
pod lifetime. Only in-pod trace continuation is limited to the Task path.
The correct model for long-lived pods is per-operation runtime propagation —
each session turn / each task pickup carries and continues its own trace
context (via the Session/Task record or dispatch payload) rather than a
process-wide env var — and requires instrumenting the session/worker runtime
itself. This is deliberately deferred to a follow-up PR.
Tests: added coverage for
Enabled(), the disabled no-op path,traceparent inject/extract round-trip,
TRACEPARENTpod-env injection(present and absent), and controller→spawner OTLP env propagation (on and
off). Docs added under
docs/reference.md.Ran
make verifyandmake test— both pass.Does this PR introduce a user-facing change?
Summary by cubic
Adds native OpenTelemetry (OTLP) export for metrics and traces across the controller and spawner. It’s opt-in and keeps the Prometheus
/metricsendpoint unchanged by default, with secure Secret-based header propagation to spawner pods.New Features
internal/observability.Setup()installs global OTEL Tracer/Meter when an endpoint is set; honors standard OTEL env; supports gRPC and HTTP/protobuf; fails fast on misconfig.go.opentelemetry.io/contrib/bridges/prometheus) while the/metricsendpoint stays.reconcile.<Kind>), plus spawner spans (spawner.discover,spawner.create_task).kelos.dev/traceparent; Task reconcile continues it; agent Jobs getTRACEPARENTenv.observability.otlp.headersSecretName(and optionalheadersSecretKey) so the controller and spawner readOTEL_EXPORTER_OTLP_HEADERSviasecretKeyRefwithout copying tokens in plaintext.observability.otlpvalues (endpoint,protocol,headers,headersSecretName,headersSecretKey,resourceAttributes). Docs and tests added.Migration
OTEL_EXPORTER_OTLP_ENDPOINTor Helmobservability.otlp.endpoint. For bearer tokens, useobservability.otlp.headersSecretNameinstead of plaintextheaders.Written for commit 4310b85. Summary will update on new commits.