Repository navigation
feat(otel-ci): add vLLM model-server metrics solution - #398
Merged
Merged
Conversation
7 of 8 tasks
miconeilaws
previously approved these changes
Oct 13, 2026
Scrape vLLM's own Prometheus metrics (vllm:* and http_*) from the agent on each node. Servers are found without configuration: the kserve-container of a KServe InferenceService's predictor pods, or any container running a vLLM image. The chart reads no scrape labels or annotations of its own from pods; on KServe pods the serving runtime's prometheus.kserve.io/port and path are honoured. Series are attributed to the pod and workload. service.name is the InferenceService (from KServe's own pod label, as extracted by k8sattributes), else the workload, else the pod. Every series carries aws.service.type = ai_inference.
Aakash-Dantre
force-pushed
the
otel-ci-vllm-metrics
branch
from
October 13, 2026 13:49
feccb20 to
8a99401
Compare
7 of 8 tasks
Aakash-Dantre
force-pushed
the
otel-ci-vllm-metrics
branch
from
October 13, 2026 14:11
dda055d to
8a99401
Compare
7 of 8 tasks
Collaborator
Author
7 of 8 tasks
miconeilaws
approved these changes
Oct 13, 2026
9 tasks done
9 tasks done
9 of 10 tasks
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.
Summary
Adds
otelContainerInsights.solutions.vllm: the vLLM model server's own Prometheus metrics (vllm:*engine metrics and the API server'shttp_*), collected by the CloudWatch agent DaemonSet and sent to CloudWatch over OTLP. On by default; it finds vLLM servers without configuration and does nothing on clusters that have none.Part of the LLM model-serving solutions, alongside KServe control-plane metrics (#389) and Knative Serving metrics (#390). End-to-end tests: aws/amazon-cloudwatch-agent-test#779.
Background: how LLMs are served on Kubernetes
Three separate open-source projects are involved. This PR scrapes the first; #389 and #390 cover the other two.
/v1/chat/completions); exposes/metricson the same portkserve-controller-managerqueue-proxysidecar, scales on request concurrency, including to zeroqueue-proxyand, when scaled to zero, the activatorAn InferenceService is a KServe custom resource the customer applies to say "serve this model":
KServe and Knative are installed once per cluster by the platform team. The InferenceService does not create them; it is a request their (already running) controllers fulfil.
Creation path vs. request path
vllm serve <model>, imagevllm/vllm-openai), and requests go Service → vLLM.Either way the vLLM pod is the customer's workload in the customer's namespace. The chart never deploys it; it only scrapes it.
Where it runs, and which agent scrapes what
This PR scrapes only the vLLM process (marked #353 in the diagram): the
kserve-containerin KServe pods, and vLLM containers elsewhere. The DaemonSet agent on each node scrapes the vLLM pods on its own node. KServe's controller and Knative's components and queue-proxy are covered by #389 and #390.Questions this answers
Is this scraping random customer application containers? No. It scrapes only customer model-server containers that can be identified:
kserve-container, in the predictor pods it creates for an InferenceService (labelsserving.kserve.io/inferenceserviceandcomponent: predictor);vllm.No other containers are touched, and the chart reads no scrape labels or annotations of its own from customer pods. A vLLM server built from a custom-named image has no reliable signal, so it is not scraped automatically; a PodMonitor (feat(amazon-cloudwatch-observability): ServiceMonitor/PodMonitor scraping via the Target Allocator #376) is the general way to opt such workloads in.
Does vLLM have a control plane? No. Each vLLM process serves on its own. The control planes in this picture belong to KServe and Knative.
Why the node agent and not the cluster-scraper? vLLM pods run on many (GPU) nodes. Each node agent watches only its own node's pods, using an API-server field selector, so the load is spread out and grows with pods per node, not per cluster.
What is scraped
Which containers. A container is a target if either applies:
kserve-containerin a pod with the labelsserving.kserve.io/inferenceserviceandcomponent: predictor, any imageprometheus.kserve.io/port(set by the serving runtime), else the declared port, else 8080prometheus.kserve.io/path, else/metricsvllm(vllm/vllm-openai, mirrors, CPU builds)/metricskserve-container. Thecomponent: predictorlabel restricts the match to the model server, excluding transformers and explainers.kserve-containeris scraped, never the queue-proxy sidecar, which proxies the model server's/metrics.prometheus.kserve.io/*, and only on pods already matched. They pick the port and path; they don't select pods.metricResolution(default 30s).Which metrics. Kept:
vllm:*(scheduler state, KV cache, prompt and generation tokens, request outcomes, TTFT, inter-token, queue and end-to-end latency histograms) andhttp_*(request counts and latency by handler and status). Dropped:python_*,process_*,*_created, and summaries, which the CloudWatch OTLP endpoint does not accept.Pipeline
metrics/cw_k8s_ci_v0_vllmon the node agent:prometheus/cw_k8s_ci_v0_vllm: discovery and relabelling as above; target labelspodandnamespace.filter/cw_k8s_ci_v0_scrape_metadata,filter/cw_k8s_ci_v0_vllm_keep: drop scrape metadata and the families above.transform/cw_k8s_ci_v0_set_unit,metricstarttime/cw_k8s_ci_v0,transform/cw_k8s_ci_v0_set_cluster_name: shared Container Insights steps.transform/cw_k8s_ci_v0_vllm_promote: copiespodandnamespaceto resource attributes, so the next step can associate the pod, and setsaws.service.type.k8sattributes/cw_k8s_ci_v0_pod: pod, ReplicaSet and Deployment metadata.resourcedetection,cloud.resource_id, node metadata, scope, workload: shared steps.set_workloadresolvesk8s.workload.nameto the Deployment (or StatefulSet, DaemonSet, Job), not the ReplicaSet.transform/cw_k8s_ci_v0_vllm_service_name: setsservice.nameandservice.namespace.awsattributelimit,batch, thenotlphttp/cw_k8s_ci_v0_metrics_dest(SigV4) to the CloudWatch OTLP endpoint.Resulting series
github.com/vllm-project/vllm, withcloudwatch.pipeline = vllm.service.name: the InferenceService name (KServe's own pod label, read ask8s.pod.label.serving.kserve.io/inferenceservice), else the workload, else the pod. The InferenceService comes first because a Serverless revision's Deployment name changes on every redeploy (<isvc>-predictor-00004-deployment). No custom attribute key is added for it.aws.service.type = ai_inference, set by the chart on every series. This is the CloudWatch resource attribute that classifies the source, which the CloudWatch (Omni) experience uses to identify AI inference telemetry. vLLM only serves inference, including when it generates rollouts for a training loop, so the value is fixed and nothing is read from the pod.k8s.pod.name,k8s.namespace.name,k8s.workload.*,k8s.node.name, and pod labels such ask8s.pod.label.serving.kserve.io/inferenceservice) and cloud (cloud.region,cloud.availability_zone,cloud.resource_id,host.*of the node) attributes, plus vLLM's own labels (model_name,finished_reason,handler, ...).Configuration
Testing
tests/vllm_pipeline_shape.sh(make helm-vllm-shape, part ofmake all) renders the chart and checks, on the parsedotelConfig:prometheus.io/*,cloudwatch.aws/*oraws.service.typefrom podsaws.service.type, andservice.namefrom KServe's pod label with no custominferenceservicekeymake helm-lint helm-flag-matrix helm-yaml-injection helm-neuron-shapepass.test/otel/solutions/vllmin test(otel): add vLLM, KServe and Knative solution integration tests aws/amazon-cloudwatch-agent-test#779, on an EKS cluster with KServe Serverless, a CPU vLLM InferenceService, a plain vLLM Deployment, two annotated pods that must not be scraped (a vLLM mock from a custom-named image and a non-vLLM exporter) and load. The 9 tests cover:aws.service.typevllm/vllm-openaiStatefulSet and an annotated pod. Verified in CloudWatch: both servers are scraped with one series per pod, the annotated pod is not scraped, a pod labelaws.service.type: ai_trainingis ignored, and there are no agent errors.Resource cost
A/B on a 4-node cluster (3 vLLM servers), same nodes and load, fresh agent pods, 15–20 min averages. Solutions off → on:
No measurable difference: the deltas go both ways and are within run-to-run noise. Relabel rules run once per discovered target when discovery refreshes, not per sample.
PR Checklist
makechecks pass locally (helm-lint, flag matrix, YAML injection, shape tests)test/otel/solutions/vllm)tests/vllm_pipeline_shape.sh)otelContainerInsights.solutions.vllmvalue, on by default