Skip to content

feat(monitoring): scrape KubeVirt VMI metrics from virt-handler - #4219

Draft
Andrei Kvapil (kvaps) wants to merge 1 commit into
mainfrom
feat/monitoring-kubevirt-vmi-scrape
Draft

Andrei Kvapil (kvaps) wants to merge 1 commit into
mainfrom
feat/monitoring-kubevirt-vmi-scrape

Conversation

@kvaps

@kvaps Andrei Kvapil (kvaps) commented Sep 11, 2026 •

Copy link
Copy Markdown
Member

What this PR does

Makes every Monitoring application collect the resource usage of the VirtualMachineInstances it is responsible for.

KubeVirt exports kubevirt_vmi_* (CPU, memory, storage and network usage of every VMI) from virt-handler in cozy-kubevirt, not from the VMI's own pod, so no per-application scrape object can collect it. virt-operator would create a ServiceMonitor for virt-handler, but only when the monitorAccount ServiceAccount (prometheus-k8s by default) exists in the monitorNamespace (tenant-root), and Cozystack never creates that account. As a result no Monitoring application received VM metrics at all: the cluster-wide vmagent in cozy-monitoring has no virt-handler target, and a tenant Monitoring has nothing to select.

The monitoring chart now renders a VMPodScrape for virt-handler in every Monitoring release:

  • a tenant Monitoring keeps only the series whose namespace is its own namespace or a sub-tenant below it (tenant-a keeps tenant-a and tenant-a-*), which is what the namespace.cozystack.io/monitoring label already promises for a sub-tenant without a Monitoring of its own;
  • the root Monitoring keeps everything, as the cluster-wide vmagent does for every other target.

The tenant chart already opens the network path for this: the <tenant>-egress-virt-handler policy (#2199) lets the tenant vmagent reach virt-handler on 8443 whenever monitoring is enabled, but nothing used it until now.

Verified on a v1.6.3 cluster in the root mode: with the rendered VMPodScrape applied in tenant-root, the cluster-wide vmagent reports the three virt-handler endpoints as up, and kubevirt_vmi_* series of VMs from two tenants appear in vmselect-shortterm with the VMI's namespace, name and node labels intact. The tenant mode was run on the same cluster with the chart's rendered VMAgent and this VMPodScrape applied in a child tenant (with one-replica VMClusters in place of the application's own, which does not fit that cluster): the tenant vmagent reaches all three virt-handlers through the egress-virt-handler policy, its vmselect holds only the VMI series of that tenant, and nothing without a namespace label gets past the keep filter. The chart unit test covers both render modes.

Screenshots

Not applicable.

Downstream repositories

Walked the trigger map against the diff: the change is a new template plus its unit test in packages/system/monitoring, with no values key, schema, package list or version pin touched, so nothing on the website, provider or installer side is affected.

Release note

feat(monitoring): Monitoring applications now collect KubeVirt VMI metrics (kubevirt_vmi_*) from virt-handler; a tenant Monitoring keeps the series of its own namespace and sub-tenants, the root Monitoring keeps all of them

KubeVirt exports the resource usage of every VirtualMachineInstance
(kubevirt_vmi_*) from virt-handler, not from the VMI's own pod, so no
per-application scrape object collects it. virt-operator would create a
ServiceMonitor for it, but only when the monitorAccount ServiceAccount
exists in the monitorNamespace, and Cozystack never creates that
account, so no Monitoring application received VM metrics at all.

Every Monitoring application now scrapes virt-handler through a
VMPodScrape. A tenant Monitoring keeps only the series of its own
namespace and of the sub-tenants below it, which is what the
namespace.cozystack.io/monitoring label already promises for a
sub-tenant without a Monitoring of its own; the root Monitoring keeps
everything. The tenant chart already opens the network path for this
(the per-tenant egress-virt-handler policy).

Assisted-by: LLM
Signed-off-by: Andrei Kvapil <[email protected]>
@coderabbitai

coderabbitai Bot commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added size/M This PR changes 30-99 lines, ignoring generated files area/monitoring Issues or PRs related to the monitoring stack (vlogs, vmstack, grafana, workloadmonitor) kind/feature Categorizes issue or PR as related to a new feature labels Sep 11, 2026

@lllamnyp Timofei Larkin (lllamnyp) left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for chasing this down, Andrei Kvapil (@kvaps). The diagnosis is right: no Monitoring receives kubevirt_vmi_* today. The tenant-subtree semantics are carefully worked out too. Since this is still a draft, I'd like to raise a placement concern before more goes into it.

Core monitoring shouldn't know about pluggable components

system/monitoring is the chart every Monitoring application renders. With this PR it hard-codes KubeVirt's namespace, pod label, port and TLS quirks. The test I'd apply: if the FoundationDB app, or an app in an external repository, wanted its metrics collected, would its author have to patch system/monitoring? Today the answer is no. Each package ships its own scrape objects and rules, and the platform picks them up by convention:

  • apps: kafka (templates/podscrape.yaml), redis and valkey (templates/servicescrape.yaml), clickhouse (keeper scrape in templates/chkeeper.yaml), postgres (CNPG enablePodMonitor: true, converted by vm-operator);
  • tenant modules: extra/etcd (scrape plus rules), extra/ingress (nginx-scrape.yaml);
  • system components: etcd-operator, linstor, kubeovn-plunger, opencost, and rules in kafka-operator, postgres-operator, keda, seaweedfs, piraeus-operator;
  • system/monitoring itself scrapes only its own VictoriaMetrics components (vmcluster-scrape.yaml, vmalert-scrape.yaml).

The selection side is generic. A scrape object in a tenant namespace is picked up by that tenant's vmagent through the namespace.cozystack.io/monitoring namespace label, and the cluster-wide vmagent (selectAllByDefault: true) picks up everything. Neither needs to know what it is scraping. KubeVirt should stay on that contract rather than become the first component whose scrape lives inside the monitoring chart.

KubeVirt used to ship its own scrape

system/kubevirt-operator shipped a ServiceMonitor on KubeVirt's kubevirt-prometheus-metrics Service from #573 onwards. The v1.8.2 refresh (6af0bf7) dropped it, because the update recipe at the time ran rm -rf templates. The alert rule came back a few days later as templates/prometheusrule.yaml (7106f07), but the scrape did not. So what this PR fixes is a regression, and the scrape's natural home is next to the rule it feeds.

That rule is worth looking at in the same change:

  • It matches exported_namespace, which only exists when the scrape runs without honorLabels. With this PR's honorLabels: true it still never fires.
  • kubevirt_vm_info comes from virt-controller, which this PR doesn't scrape.
  • The kubevirt/kubevirt-control-plane dashboard in dashboards-infra.list also reads virt-controller and virt-api series, such as the workqueue metrics. A virt-handler-only scrape leaves it empty.

Two correctness issues with the current shape

  • Duplicates in root storage. The cluster-wide vmagent has selectAllByDefault: true and no namespace selector, so it also selects every tenant's copy of kubevirt-virt-handler. With T tenant Monitorings it scrapes each virt-handler T+1 times. Root storage gets each tenant's VMI series once from the tenant-root copy and again from the tenant's own, and once more per nesting level. vm-operator's default job is per scrape object (<namespace>/<name>, as in prometheus-operator), so the copies shouldn't dedup and root-side sums would double-count; I haven't confirmed that on a live cluster. cozystack/community#94 reads VM series from tenant-root and assumes they are scraped once.
  • The cozy-monitoring release. The monitoring source also installs this chart as monitoring in cozy-monitoring, which vmagent.yaml and dashboards.yaml treat as root-equivalent. There this template renders keep with cozy-monitoring(-.*)?, which drops every series.

A smaller point: tenant-a(-.*)? also keeps the series of a sub-tenant that has its own Monitoring. A tenant vmagent otherwise skips such a namespace, so the filter doesn't match the label semantics described in the template comment.

Proposal

  1. Move the scrape to system/kubevirt-operator, next to prometheusrule.yaml. Make it a VMServiceScrape, or a ServiceMonitor since the converter is on, selecting prometheus.kubevirt.io: "true" in cozy-kubevirt: port metrics, https, insecureSkipVerify as upstream's own ServiceMonitor does, and honorLabels: true. That keeps namespace and name on the VMI, which is what dashboards/vm/virtual-machines.json already queries. It covers virt-handler, virt-controller and virt-api in one object.

  2. Fix the alerts in the same move. Switch the PrometheusRule from exported_namespace to namespace, and pin the scrape in tests/vendored_manifest_test.yaml the way prometheusrule.yaml already is, so a future refresh can't drop it again.

  3. Drop the per-tenant fan-out from this PR. The cluster-wide vmagent then scrapes each virt-handler exactly once into tenant-root. Scoping a shared platform exporter's series to a tenant is a read-path problem, and cozystack/community#94 designs that for exactly these series, with a forced namespace matcher in front of tenant-root's vmselect.

  4. If tenant stores must hold these series, for example for tenant vmalert rules on VM metrics, make that a generic monitoring capability rather than a KubeVirt one. For instance, a tenant Monitoring could also select scrape objects that carry an opt-in label from platform namespaces, and drop any series whose namespace is a tenant namespace outside its subtree. KubeVirt would opt in by labelling its own scrape. Two caveats:

    • vm-operator ANDs a VMAgent's selector and namespace selector per kind, so the opt-in channel needs its own kind (for example VMScrapeConfig through scrapeConfigSelector) or a second agent.
    • Every tenant vmagent would pull each virt-handler's full payload. virt-handler gathers domain stats from the virt-launchers at scrape time, so the cost grows with tenants × nodes.

    That seems worth a short community proposal rather than this PR. The tenant chart's egress-virt-handler policy (#2199) couples core to KubeVirt in the same way. If the fan-out does happen, the allow rule could move into the KubeVirt package as an ingress rule on virt-handler.

Writing the contract down

As far as I can find, the "ship your own scrape objects and rules" convention isn't documented in this repository. Writing it down would make the FoundationDB question above easy to answer for the next contributor. It would cover:

  • where scrape objects and rules live;
  • that one in a tenant namespace reaches both the tenant's Monitoring and root, and one in a system namespace reaches root only;
  • that series should keep namespace naming the workload's namespace.

Dashboards are the one place a package still has to edit core: dashboards.list and dashboards-infra.list, plus the JSON under dashboards/ baked into the grafana-dashboards image. That's a separate follow-up, but it belongs on the same list.

Happy to help with the relocation if useful.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/monitoring Issues or PRs related to the monitoring stack (vlogs, vmstack, grafana, workloadmonitor) kind/feature Categorizes issue or PR as related to a new feature size/M This PR changes 30-99 lines, ignoring generated files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants