feat(controller): opt-in Prometheus scrape annotations on inference pods - #1366
feat(controller): opt-in Prometheus scrape annotations on inference pods#1366brandonliuli wants to merge 1 commit into
Conversation
Add --emit-scrape-annotations (chart: metrics.emitScrapeAnnotations, default off). When set, the operator adds prometheus.io/scrape, /path=/metrics, and /port to every inference Pod, with port resolved from spec.endpoint.port so annotation-based scrapers (e.g. Grafana Alloy) discover the runtime /metrics without a per-InferenceService podAnnotations block or a PodMonitor. A user's spec.podAnnotations value always wins. The port is the app port (endpoint port), whose container-port name is 'http' -- not a metrics-named port most annotation-scrape regexes match -- so setting prometheus.io/port explicitly is what makes discovery work. Signed-off-by: brandonliuli <brandon@ybor.ai>
|
Design discussion + in-depth problem writeup (both options): #1367 |
Defilan
left a comment
There was a problem hiding this comment.
brandonliuli, welcome to LLMKube, and thanks for two solid PRs on your first day! The two-option framing here is genuinely useful and Option A is the right call. The opt-in wiring is clean end to end (chart to flag to reconciler to buildPodAnnotations), correctly off by default, and using a reconciler flag instead of a CRD field is the right pattern (no CRD regen needed). DCO and the PR-body AI disclosure both meet our policy.
One blocker, and it's small: the new endpointPort() helper reinvents port resolution and gets it wrong, which undercuts the PR's own thesis. The real container port is resolved in constructDeployment (deployment_builder.go:251-255) as ContainerPort -> Endpoint.Port -> backend.DefaultPort(), and DefaultPort() is runtime-specific: 80 for TGI, 8000 for vLLM, 30000 for SGLang, 8998 for personaplex, and only 8080 for llama.cpp/generic. endpointPort() (deployment_builder.go:310-315) ignores spec.containerPort and hardcodes 8080, so the zero-config path emits a wrong prometheus.io/port for every non-llama.cpp runtime, and mis-annotates any service using spec.containerPort, the exact footgun this PR exists to fix.
The fix is elegant: the fully-resolved port local already exists at deployment_builder.go:251 and is in scope at the buildPodAnnotations call (:342). Thread it through, buildPodAnnotations(isvc, r.EmitScrapeAnnotations, port), and delete endpointPort(). Please also add test cases for (a) a non-llama.cpp backend default and (b) spec.containerPort set, both currently produce a wrong annotation, so they'll pin the fix. And a note in values.yaml that this is an alternative to the PodMonitor, not to run alongside it against the same Prometheus (double-scrape). Happy to merge once the port comes from the resolved value.
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
Upstream LLMKube proposal — inference-pod metrics discoverable by annotation scrapers
Problem
LLMKube runtimes expose Prometheus metrics on the inference container's app port
(llama.cpp
llamacpp:*, vLLMvllm:*, TGItgi_*) at/metrics. But making acluster-wide metrics agent scrape them today requires either:
inferencePodMonitor(needs prometheus-operator, which many clusters don't run), orprometheus.io/*annotations to every InferenceService'sspec.podAnnotations.There's also a real footgun: the inference container's metrics port is named
http(the app port), which does not match the port-name regexes mostannotation-scrape configs use (
metrics|http-metrics|metrics-port). So evenannotation-based discovery misses it unless
prometheus.io/portis set explicitly.Net: no zero-config path to inference metrics without prometheus-operator.
Proposal — two shapes (seeking maintainer preference)
Both extend the existing operator-default pattern on
InferenceServiceReconciler(cf.
DefaultFSGroup,ModelCacheMode) and the merge pointbuildPodAnnotationsin
internal/controller/deployment_builder.go. In both, a user's explicitspec.podAnnotationsvalue always wins.Option A — opt-in scrape-annotation emission (reference PR implements this)
A
--emit-scrape-annotationsflag (chartmetrics.emitScrapeAnnotations, defaultoff). When on, the operator adds
prometheus.io/scrape=true,path=/metrics, andport=<resolved spec.endpoint.port>to every inference pod.prometheus.io/portis alwayscorrect — this dissolves the
http-port-name footgun at the source, across allruntimes. Zero per-CR boilerplate.
prometheus.io-annotation scraping (though it's ano-op unless explicitly enabled).
Option B — generic operator-level default pod annotations
A
--default-pod-annotations key=val,...flag (chartdefaultPodAnnotations) thatseeds
buildPodAnnotationsat lowest precedence. The operator sets nothing metrics-specific; the platform supplies the
prometheus.io/*values (including a port).use different
endpoint.portvalues; the platform has to know/guess the port. Doesnot solve the port-name footgun on its own.
Recommendation
Option A — it fixes the actual discovery problem (the port) at the one place that
authoritatively knows the port, and stays a no-op until enabled. Option B is a nice
generic primitive but leaves the port problem to the caller. (They're not mutually
exclusive; A could later sit on top of a generic B.)
Reference PR
Implements Option A: reconciler field +
--emit-scrape-annotationsflag + chartvalue/schema/arg + table-driven unit test (
TestBuildPodAnnotations: emit off,explicit port, default-8080 fallback, user-port-wins).
go build,go vet,gofmt,and the targeted test are green locally (envtest-gated tests need
setup-envtestbinaries, unrelated).
AI-assisted: this change and text were drafted with an AI coding agent (Claude) and
are being reviewed, tested, and submitted by a human who takes DCO accountability for
the result.