Design: managed metrics collection - #363
Conversation
Collecting a deployment's metrics is hand-wired: an operator writes a PodMonitor per deployment, keeps it in sync with the serving shape (leader/worker, prefill/decode), and removes it on teardown. This design has Modelplane compose the collection instead, per source it owns, the engine and the endpoint picker where present, on by default with an opt-out. It covers modelplaneai#269, with a section per source and a diagram. Signed-off-by: Dennis Ramdass <dennis@upbound.io>
|
|
||
| ## Interaction with #264 | ||
|
|
||
| The [#264](https://github.com/modelplaneai/modelplane/issues/264) example |
There was a problem hiding this comment.
In Release notes we need to add a line telling existing users to delete their hand-written podmonitor.yaml, or they get a duplicate scrape job against the same pods after upgrade.
There was a problem hiding this comment.
Yes, without deleting it they'd get a duplicate scrape of the same pods after upgrade. Called it out as a release note in the #264 interaction section. Pushed.
|
|
||
| - **Port name.** `http` (the serving port that also serves `/metrics`) versus | ||
| `metrics`. Leaning `http`, since it's the one serving port. | ||
| - **Configurability.** `interval` and `path` are fixed (30s, `/metrics`) for now. |
There was a problem hiding this comment.
wonder if 30s is too Long If it comes to KV Cache utilization e.g.
There was a problem hiding this comment.
For routing this interval doesn't matter: the EPP scrapes engine /metrics on its own fast internal loop (~100ms) to make routing decisions, so routing isn't gated on it. This PodMonitor feeds Prometheus for dashboards and alerting, where 30s is a normal default. If finer KV series are wanted, interval is a knob on the metrics object (open question). Made that distinction explicit in What gets scraped.
| example). Nothing owns that wiring. The operator builds it by hand and keeps it in | ||
| sync with the deployment's shape. They delete it on teardown. | ||
|
|
||
| Instead, Modelplane composes the collection, per source: |
There was a problem hiding this comment.
is Monitoring for Cache / PVC here out of scope?
There was a problem hiding this comment.
Out of scope here, yes, this doc is serving metrics (engine + EPP). ModelCache PVC and hydration observability is a separate concern that belongs with the ModelCache work. Added a scope line to the summary.
|
|
||
| ### What gets scraped | ||
|
|
||
| The `PodMonitor` ingests everything the engine exposes on `/metrics`. What the |
There was a problem hiding this comment.
what are the expected series-per-pod ? is the Prometheus Stack configured with rolling / retention of the storage ?
There was a problem hiding this comment.
Order of dozens of series per engine pod (request/latency histograms, KV-cache, throughput), plus the llm_d_epp_* set wherever an EPP runs. Retention and storage sizing are the serving stack's Prometheus config in compose-serving-stack, not this composition, worth a sane default there but out of scope for this doc. Noted it in What gets scraped.
| replicas: 1 | ||
| template: | ||
| spec: | ||
| metrics: |
There was a problem hiding this comment.
consider observability/monitoring as the object name if it's going to grow past scraping... later
There was a problem hiding this comment.
Agree, and it lines up with #77 (ModelService.observability.traces). Renamed to spec.template.spec.observability.metrics.enabled so traces and logs can slot beside metrics later. (Metrics live on the deployment since we scrape its pods; #77's traces live on ModelService, different resource, same observability umbrella.) Pushed.
There was a problem hiding this comment.
Superseded by the reframe: after Nic's review the doc drops the per-deployment opt-out field entirely. Collection is now always-on at every layer (engine, EPP, serving-stack) with no opt-in or opt-out, feeding a central Modelplane Prometheus. So there's no longer a field to name under observability. Leaving this for context; the observability grouping question may come back if we add a deployment-level knob later.
| matchLabels: | ||
| modelplane.ai/serving: <replica-name> | ||
| podMetricsEndpoints: | ||
| - port: http |
There was a problem hiding this comment.
is http the right name here?
There was a problem hiding this comment.
It's an open question in the doc (http vs metrics). Leaning http since it's the one serving port that also serves /metrics, but happy to go metrics if that reads clearer.
Align the opt-out field with modelplaneai#77 by nesting it at observability.metrics.enabled, so traces and logs can join it later. Clarify that the 30s scrape is an observability default and not a routing input (the EPP scrapes engines on its own fast loop), that cache/PVC observability is out of scope, and that an existing hand-written PodMonitor must be deleted on upgrade. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Dennis Ramdass <dennis@upbound.io>
|
@dennis-upbound I think we need to step back and think about what we want to help folks monitor. There's a few things you could monitor in Modelplane:
I think MD authors will care about 1. The rest feel more like platform team concerns. This proposal helps with 1, but it feels incomplete. If I understand correctly, it configures the Prometheus instance we already deploy to each cluster to scrape ModelReplica (i.e. vLLM, EPP) metrics, but what then? How does the MD author see and consume those metrics? If the platform team needs to setup plumbing to actually expose the metrics somewhere the MD author can use them (e.g. a dashboard), why ask the MD author to opt-in (or out)? My hunch is Modelplane should do something like:
This'd give you one central Prom instance you can scrape to get your entire Modelplane deployment's metrics. Metrics you'd presumably feed onwards into your monitoring and alerting system of choice. |
Adopt Nic's direction: step back to what's worth monitoring (data plane, substrate, control plane, fleet roll-up), make PodMonitor collection always-on at every layer instead of a per-deployment opt-out, and add a central Modelplane Prometheus that the per-cluster instances feed and that also scrapes the control plane. Drops the observability.metrics.enabled toggle. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Dennis Ramdass <dennis@upbound.io>
|
Completely agree, reframed the doc around this. It now opens with the four things to monitor (data plane / substrate / control plane / fleet roll-up), makes The per-deployment opt-out field is dropped (now the first rejected alternative). Open questions left: remote-write vs federation, central retention sizing, and phasing (data-plane + substrate first, control-plane + roll-up after). |
@dennis-upbound did you forget to push? 🤔 Doc looks unchanged to me. |
Pull the design back from a central aggregated Prometheus to what this layer should own: always-on PodMonitors inside each InferenceCluster and a Prometheus URL on the cluster status, so the platform team can scrape or federate without reaching into Modelplane internals. Leave cross-cluster aggregation and control-plane monitoring to the platform team, as the main open question. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Dennis Ramdass <dennis@upbound.io>
|
Agree — pulled the scope to exactly this. The doc now composes collection always-on inside each IC (engines, EPPs, serving-stack), no MD-author opt-in/out, and exposes the cluster's Prometheus URL on the (Also: the doc genuinely hadn't updated when I said it had — I'd pushed to the wrong remote. Fixed now, the diff reflects the change.) |
| An MD author cares about (1), and the platform team owns the rest. A per-deployment | ||
| opt-in would cover only (1), and even then the platform team has to plumb the metrics | ||
| somewhere the author can read them. So collection is always on inside each cluster, | ||
| with no toggle for an MD author to manage. Aggregating across clusters and monitoring | ||
| the control plane stay with the platform team, and the end of this document returns to | ||
| them. |
There was a problem hiding this comment.
Nit: the mention of per-deployment opt-in feels like it's a reaction to the earlier iteration of the design. You already cover it in alts considered. I think you can drop it here.
There was a problem hiding this comment.
Dropped it — the opt-in only lives in Alternatives now.
| ### One cluster-wide PodMonitor from the serving stack | ||
|
|
||
| `compose-serving-stack` could install a single `PodMonitor` selecting all serving pods | ||
| (`modelplane.ai/serving` Exists). It would have no per-deployment lifecycle: it wouldn't | ||
| come and go with a deployment. Composing the engine monitor per replica ties that | ||
| collection to the thing it observes, while the serving-stack monitors, which are | ||
| per-cluster, are composed once with the stack. |
There was a problem hiding this comment.
Would this work? i.e. Just matching stuff to monitor as it comes and goes?
If so, what's the benefit of the current proposal over this alternative?
There was a problem hiding this comment.
Agreed, and switched to it. Now that collection is always-on with no opt-out, the per-replica composition earns nothing over one cluster-wide monitor — so the serving stack composes a single PodMonitor matching modelplane.ai/serving (Exists) for engines, plus one for EPPs and one for the substrate, composed once with the stack. The per-replica version is now the first rejected alternative.
| ```yaml | ||
| status: | ||
| metrics: | ||
| prometheusURL: http://prometheus-operated.modelplane-system.svc:9090 |
There was a problem hiding this comment.
How will we expose Prometheus so it can be scraped from outside the cluster?
There was a problem hiding this comment.
Made it explicit: Modelplane only publishes the in-cluster URL on the InferenceCluster status — it doesn't expose Prometheus outside the cluster. Reaching it across clusters stays the platform team's job through whatever cross-cluster path they already run (remote-write, a federation scrape, a peered network), the same way they reach any in-cluster service. Exposing it publicly would be a security surface we don't want to own; publishing where it is gets them what they need.
Agree the per-replica composition earns nothing once collection is always-on, so compose one cluster-wide PodMonitor per source in the serving stack instead. State the proposal and what approving it covers up front, answer how the per-cluster Prometheus is reached from outside (the platform team's existing cross-cluster path; Modelplane only publishes the URL), and drop the opt-in mention that was reacting to the earlier draft. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Dennis Ramdass <dennis@upbound.io>
Description of your changes
A design doc, not code yet — opening as a draft for direction before implementation.
Collecting a deployment's metrics is hand-wired today (#269): vLLM exposes
/metricson its serving port, the serving stack runs a Prometheus per workload cluster with openPodMonitordiscovery, and an operator writes aPodMonitorby hand, keeps it in sync with the serving shape (leader/worker, prefill/decode), and removes it on teardown (the #264 example).This proposes having Modelplane compose the collection, per source it owns, on by default with an opt-out (
spec.template.spec.metrics.enabled):compose-model-replicacomposes aPodMonitorselecting the replica's serving pods, scraping the engine's/metricsby a named port. Naming the port fixes a latent prefill/decode gap: the decode engine serves on 8001 (the pd-sidecar takes 8000), so the manualtargetPort: 8000scrapes the sidecar, not the engine.llm_d_epp_*metrics too. Since we own the EPP Deployment (llm-d-router-endpoint-picker:v0.9.0), we set--metrics-endpoint-auth=falseand scrape:9090with a plainPodMonitor— no ClusterRole, token, or TLS.Managed lifecycle and shape-syncing fall out of the existing serving label and open Prometheus discovery.
Where I'd like judgment (in the doc): the metrics field shape and default-on, the engine port name (
httpvsmetrics), and disabling EPP metrics auth vs composing RBAC. Two open questions are flagged.design/metrics.mdonly; no code.I have:
Run(design doc only, no code paths)nix flake check(or./nix.sh flake check) and made sure it passes.Added or updated tests covering any composition function changes.(design doc only)git commit -s.