Repository navigation
atecontroller: the :8080 metrics listener follows OTEL_METRICS_EXPORTER - #1976
Eric Curtin (ericcurtin) wants to merge 7 commits into
Conversation
Its registry leaves over OTLP, so nothing scrapes :8080. Drop the Service and port too.
|
Jeff Luo (@JeffLuoo) Lior Lieberman (@LiorLieberman) PTAL when you get a chance. Thank you! |
|
Thank you for this PR, and for the cleanup in managerOptions. I suggest that we hold this PR until #2067 decides the metrics policy. |
|
Sounds good, holding until #2067 is decided. Will rebase after. |
|
Merged main and resolved the conflict. Still holding for #2067. |
|
Thank you for holding this. #2067 has decided the policy, and task 3 there replaces this PR. |
|
Jeff Luo (@JeffLuoo) Understood, thanks. I will hold until the OTel env var parsing lands and then rework this. |
|
The change I mentioned is merged. |
|
Jeff Luo (@JeffLuoo) Reworked on top of #2190, PTAL. |
Jeff Luo (JeffLuoo)
left a comment
There was a problem hiding this comment.
The implementation and tests look good. Two comments on docs/observability.md inline.
| ### Scraping instead of pushing | ||
|
|
||
| ateapi, atelet, atenet-router and the credential provider also serve every instrument on their Prometheus `/metrics` endpoint. A cluster that scrapes those endpoints sets `OTEL_METRICS_EXPORTER=none` on the components, so each series reaches the backend once. With `none` the components install no OTLP metric reader and keep the Prometheus one; traces and logs are unaffected. atecontroller then registers its instruments (`ate.workerpool.*`) on controller-runtime's registry, so the manager's `:8080` serves them next to the controller-runtime families. ateom serves no endpoint of its own, so leave the variable unset on the worker pods, or it exports no metrics at all. The variable takes a comma-separated list, with the same rules as `OTEL_LOGS_EXPORTER`. `otlp` (the default) keeps the push, and `prometheus` or `none` stops it. `/metrics` stays on for each value. ateom serves no `/metrics`, so there `prometheus` is skipped as unknown with a warning, and the push stays on unless the variable is `none`. | ||
| ateapi, atelet, atenet-router and the credential provider also serve every instrument on their Prometheus `/metrics` endpoint. A cluster that scrapes those endpoints sets `OTEL_METRICS_EXPORTER=prometheus` on the components, so each series reaches the backend once. With `prometheus` the components install no OTLP metric reader and keep the Prometheus one; traces and logs are unaffected. `none` stops the push too, and leaves their `/metrics` on. ateom serves no endpoint of its own, so leave the variable unset on the worker pods, or it exports no metrics at all. The variable takes a comma-separated list, with the same rules as `OTEL_LOGS_EXPORTER`. `otlp` (the default) keeps the push, and `prometheus` or `none` stops it. ateom serves no `/metrics`, so there `prometheus` is skipped as unknown with a warning, and the push stays on unless the variable is `none`. |
There was a problem hiding this comment.
Two stale details in this paragraph:
"ateom serves no endpoint of its own, so leave the variable unset on the worker pods, or it exports no metrics at all"was written whennonewas the scrape-only setting. Two sentences later, this paragraph notes thatateomskipsprometheusas unknown and keeps the push on unless the variable isnone(andatecontrollerdoes not propagateOTEL_METRICS_EXPORTERto worker pods). Please remove that sentence or scope it tonone."otlp (the default) keeps the push"— withmetricsExporterssettingdef = Exporters{ExporterOTLP: true, exporterPrometheus: true}for components that serve/metrics, unset defaults tootlp,prometheus(otlponly onateom), which matters because the next paragraph contrastsUnsetwithotlp.
| ateapi, atelet, atenet-router and the credential provider also serve every instrument on their Prometheus `/metrics` endpoint. A cluster that scrapes those endpoints sets `OTEL_METRICS_EXPORTER=none` on the components, so each series reaches the backend once. With `none` the components install no OTLP metric reader and keep the Prometheus one; traces and logs are unaffected. atecontroller then registers its instruments (`ate.workerpool.*`) on controller-runtime's registry, so the manager's `:8080` serves them next to the controller-runtime families. ateom serves no endpoint of its own, so leave the variable unset on the worker pods, or it exports no metrics at all. The variable takes a comma-separated list, with the same rules as `OTEL_LOGS_EXPORTER`. `otlp` (the default) keeps the push, and `prometheus` or `none` stops it. `/metrics` stays on for each value. ateom serves no `/metrics`, so there `prometheus` is skipped as unknown with a warning, and the push stays on unless the variable is `none`. | ||
| ateapi, atelet, atenet-router and the credential provider also serve every instrument on their Prometheus `/metrics` endpoint. A cluster that scrapes those endpoints sets `OTEL_METRICS_EXPORTER=prometheus` on the components, so each series reaches the backend once. With `prometheus` the components install no OTLP metric reader and keep the Prometheus one; traces and logs are unaffected. `none` stops the push too, and leaves their `/metrics` on. ateom serves no endpoint of its own, so leave the variable unset on the worker pods, or it exports no metrics at all. The variable takes a comma-separated list, with the same rules as `OTEL_LOGS_EXPORTER`. `otlp` (the default) keeps the push, and `prometheus` or `none` stops it. ateom serves no `/metrics`, so there `prometheus` is skipped as unknown with a warning, and the push stays on unless the variable is `none`. | ||
|
|
||
| atecontroller's `:8080` listener follows the variable. Unset, or a list with both, it pushes and serves; `otlp` pushes and closes the listener; `prometheus` serves only, and registers its instruments (`ate.workerpool.*`) on controller-runtime's registry, so `:8080` serves them next to the controller-runtime families; `none` does neither. |
There was a problem hiding this comment.
When the variable is unset or otlp,prometheus, :8080 is open but does not serve the ate.workerpool.* instruments: because OTLP push is enabled, InitMetricsBridged keeps the OTel instruments off ctrlmetrics.Registry to avoid pushing a second copy through the bridge, and only registers them on :8080 under prometheus alone. Saying "it pushes and serves" makes a reader expect the full set on :8080 in the default mode. Please state which metrics :8080 serves in each mode:
| atecontroller's `:8080` listener follows the variable. Unset, or a list with both, it pushes and serves; `otlp` pushes and closes the listener; `prometheus` serves only, and registers its instruments (`ate.workerpool.*`) on controller-runtime's registry, so `:8080` serves them next to the controller-runtime families; `none` does neither. | |
| atecontroller's `:8080` listener follows the variable. When the variable is unset or `otlp,prometheus`, atecontroller pushes every metric over OTLP, and `:8080` serves only the controller-runtime families. With `otlp`, atecontroller pushes every metric and closes `:8080`. With `prometheus`, atecontroller does not push, and `:8080` serves the controller-runtime families and the `ate.workerpool.*` instruments. With `none`, atecontroller does not push and closes `:8080`. |
| } | ||
|
|
||
| // servePull follows the policy table: unset and the list keep both paths. | ||
| func TestInitMetricsBridgedServePull(t *testing.T) { |
There was a problem hiding this comment.
Please add a name field. The empty value row currently shows up as subtest #00.
| exporters := metricsExporters(ctx, true) | ||
| servePull = exporters.Has(exporterPrometheus) | ||
| switch { | ||
| case exporters.Has(ExporterOTLP): |
There was a problem hiding this comment.
In this branch :8080 misses ate.workerpool.*, so unset and otlp,prometheus are not following the policy. This can be a follow-up, but please open an issue to track this in that case.
There was a problem hiding this comment.
Tracked in #2402, thanks. A fix needs a separate registry to avoid pushing them twice.
| // own Prometheus registry. OTEL_METRICS_EXPORTER picks where they go: the | ||
| // OTLP push, which pads the bridged queue histograms so the Telemetry API | ||
| // accepts idle ones, and the manager's scrape listener. | ||
| mp, servePull, err := serverboot.InitMetricsBridged(ctx, serviceName, ctrlmetrics.Registry, padEmptyExponentialHistograms) |
There was a problem hiding this comment.
Let's log the resolved exporters and whether :8080 is on.
| func managerOptions(egressMITMCAPool types.NamespacedName, servePull bool) ctrl.Options { | ||
| metricsAddr := "0" // "0" disables the server. | ||
| if servePull { | ||
| metricsAddr = metricsserver.DefaultBindAddress |
There was a problem hiding this comment.
Nit: we could use 8080 as a const that we can reference in tests.
| for _, servePull := range []bool{true, false} { | ||
| opts := managerOptions(types.NamespacedName{Namespace: "ate-system", Name: "pool"}, servePull) |
There was a problem hiding this comment.
Let's rewrite as a tests := []struct{...} table with t.Run, following the repo's convention.
| ### Scraping instead of pushing | ||
|
|
||
| ateapi, atelet, atenet-router and the credential provider also serve every instrument on their Prometheus `/metrics` endpoint. A cluster that scrapes those endpoints sets `OTEL_METRICS_EXPORTER=none` on the components, so each series reaches the backend once. With `none` the components install no OTLP metric reader and keep the Prometheus one; traces and logs are unaffected. atecontroller then registers its instruments (`ate.workerpool.*`) on controller-runtime's registry, so the manager's `:8080` serves them next to the controller-runtime families. ateom serves no endpoint of its own, so leave the variable unset on the worker pods, or it exports no metrics at all. The variable takes a comma-separated list, with the same rules as `OTEL_LOGS_EXPORTER`. `otlp` (the default) keeps the push, and `prometheus` or `none` stops it. `/metrics` stays on for each value. ateom serves no `/metrics`, so there `prometheus` is skipped as unknown with a warning, and the push stays on unless the variable is `none`. | ||
| ateapi, atelet, atenet-router and the credential provider also serve every instrument on their Prometheus `/metrics` endpoint. A cluster that scrapes those endpoints sets `OTEL_METRICS_EXPORTER=prometheus` on the components, so each series reaches the backend once. With `prometheus` the components install no OTLP metric reader and keep the Prometheus one; traces and logs are unaffected. `none` stops the push too, and leaves their `/metrics` on. The variable takes a comma-separated list, with the same rules as `OTEL_LOGS_EXPORTER`. Unset is `otlp,prometheus`, or `otlp` alone on ateom. `otlp` keeps the push, and `prometheus` or `none` stops it. ateom serves no `/metrics`, so there `prometheus` is skipped as unknown with a warning, and the push stays on unless the variable is `none`. |
Name the table rows, make :8080 a const, log the resolved exporters, and mark the /metrics note temporary (agent-substrate#2067).
|
Jeff Luo (@JeffLuoo) Krisztian F (@krisztianfekete) Addressed the review, PTAL when you get a chance. Thank you! |
Jeff Luo (JeffLuoo)
left a comment
There was a problem hiding this comment.
Just a few more nits
There was a problem hiding this comment.
Here it still says "which the manager serves on an unscraped :8080". Now that :8080 is disabled under otlp/none and scraped under prometheus, could we update the text here to match line 338?
| serverboot.Fatal(ctx, "Failed to initialize metrics", err) | ||
| } | ||
| defer serverboot.ShutdownProvider("MeterProvider", mp.Shutdown) | ||
| slog.InfoContext(ctx, "Metrics listener", slog.Bool("enabled", servePull), slog.String("addr", metricsPullAddr)) |
There was a problem hiding this comment.
When servePull is false, managerOptions binds to metricsOffAddr ("0"), so logging addr=":8080" alongside enabled=false is misleading. Consider logging the resolved opts.Metrics.BindAddress (or omitting addr when disabled).
There was a problem hiding this comment.
Fixed in 5529baa, logs the resolved address.
| } | ||
| if metricsPushEnabled(ctx, true) { | ||
| exporters := metricsExporters(ctx, true) | ||
| slog.InfoContext(ctx, "Metrics exporters resolved", slog.String("exporters", exporters.String())) |
There was a problem hiding this comment.
nit - if we move this log into metricsExporters(ctx) (or newMeterProvider), InitMetrics and InitMetricsPushOnlyVia will also log the resolved exporters at startup, not just atecontroller.
There was a problem hiding this comment.
Fixed in 5529baa, moved into metricsExporters.
|
Jeff Luo (@JeffLuoo) Krisztian F (@krisztianfekete) Addressed, PTAL. The e2e failure was httpbin.org returning EOF, unrelated; the new push reruns it. |
The
:8080listener followsOTEL_METRICS_EXPORTER, per the policy in #2067. Unset keeps push and:8080,otlpcloses:8080,prometheusserves only,nonedoes neither.Fixes #933