VictoriaMetrics Operator: VMAgent, VMSingle, VMServiceScrape
Published: 2026-03-06
The VictoriaMetrics Operator is an alternative to the Prometheus Operator. It uses the same CRDs in spirit (VMServiceScrape instead of ServiceMonitor, VMRule instead of PrometheusRule) but the storage backend is VictoriaMetrics — which means better compression, higher cardinality tolerance, and a simpler single-binary deployment.
Why both kube-prom-stack and VM Operator
The infra runs kube-prom-stack on most clusters (mainly for Grafana and the Alertmanager ecosystem). VictoriaMetrics is used as the central long-term storage: spoke Prometheus instances remote_write to a VMSingle on the infra cluster, giving 6 months of retention without disk pressure on spoke clusters.
Architecture:
- Spoke clusters:
kube-prom-stack→ collects metrics, evaluates alerts →remote_writeto central VMSingle - Infra cluster: VMSingle stores long-term data, Grafana queries both local Prometheus and remote VMSingle
VMSingle
yamlapiVersion: operator.victoriametrics.com/v1beta1
kind: VMSingle
metadata:
name: victoria-metrics
namespace: observability
spec:
retentionPeriod: "6" # months
storage:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 50Gi
storageClassName: yc-network-ssd
resources:
requests:
cpu: 500m
memory: 1Gi
limits:
cpu: 2
memory: 3Gi
extraArgs:
selfScrapeInterval: "10s"
# Enable deduplication for remote_write from multiple Prometheus replicas
dedup.minScrapeInterval: "30s"
VMSingle is a single-process deployment — no Thanos, no compactor, no querier. For 6 clusters with ~100k active series total, a single node handles it comfortably.
The dedup.minScrapeInterval option deduplicates samples from multiple Prometheus replicas that write the same series. Set it to match your scrape interval.
VMAgent
VMAgent replaces prometheus as the scraper. It's lighter, can shard, and sends data via remote_write only — it has no query engine:
yamlapiVersion: operator.victoriametrics.com/v1beta1
kind: VMAgent
metadata:
name: vmagent
namespace: observability
spec:
selectAllByDefault: true
remoteWrite:
- url: "http://victoria-metrics-victoria-metrics.observability.svc.cluster.local:8428/api/v1/write"
# Optional: queue config for high-volume writes
queueConfig:
capacity: 10000
maxSamplesPerSend: 2000
resources:
requests:
cpu: 200m
memory: 256Mi
limits:
cpu: 500m
memory: 512Mi
selectAllByDefault: true means VMAgent automatically discovers all VMServiceScrape and VMPodScrape objects in the cluster.
VMAgent has a built-in WAL (write-ahead log) — if the remote endpoint is down, it buffers data locally and replays when the connection comes back. Default buffer: 1h. Configure with remoteWrite.urlRelabelConfig to filter what gets forwarded.
VMServiceScrape
Drop-in replacement for ServiceMonitor:
yamlapiVersion: operator.victoriametrics.com/v1beta1
kind: VMServiceScrape
metadata:
name: apisix
namespace: ingress-apisix
spec:
selector:
matchLabels:
app.kubernetes.io/name: apisix
endpoints:
- port: prometheus
interval: 30s
path: /apm/prometheus
# Relabel to add cluster label
metricRelabelConfigs:
- sourceLabels: [__name__]
action: keep
regex: "apisix_.*"
The VictoriaMetrics Operator converts this to a Prometheus scrape config internally.
VMPodScrape
For pods without a Service:
yamlapiVersion: operator.victoriametrics.com/v1beta1
kind: VMPodScrape
metadata:
name: my-app
namespace: app
spec:
selector:
matchLabels:
app: my-app
podMetricsEndpoints:
- port: metrics
interval: 30s
VMRule for alerts
yamlapiVersion: operator.victoriametrics.com/v1beta1
kind: VMRule
metadata:
name: node-alerts
namespace: observability
spec:
groups:
- name: node
rules:
- alert: NodeDiskFull
expr: |
(node_filesystem_avail_bytes{mountpoint="/"} /
node_filesystem_size_bytes{mountpoint="/"}) < 0.1
for: 10m
labels:
severity: warning
annotations:
description: "Node {{ $labels.instance }} disk < 10%"
- record: node:disk_usage:ratio
expr: |
1 - (node_filesystem_avail_bytes{mountpoint="/"} /
node_filesystem_size_bytes{mountpoint="/"})
VMRule objects are picked up by VMAlert for evaluation and Alertmanager integration.
Grafana datasource
yamldatasources:
- name: VictoriaMetrics
type: prometheus
url: http://victoria-metrics-victoria-metrics.observability.svc.cluster.local:8428
isDefault: false
jsonData:
timeInterval: "30s"
- name: Local-Prometheus
type: prometheus
url: http://kube-prom-stack-prometheus.monitoring.svc.cluster.local:9090
isDefault: true
jsonData:
timeInterval: "30s"
Long-term dashboards (30d, 90d range) query VictoriaMetrics. Real-time dashboards query the local spoke Prometheus. This split keeps spoke Prometheus disks small while providing historical data on demand.
Comparing VictoriaMetrics vs Prometheus
| Feature | Prometheus | VictoriaMetrics |
|---|---|---|
| Storage compression | ~3-5x | ~10-20x |
| Query engine | PromQL | MetricsQL (PromQL superset) |
| Long-term storage | Needs Thanos/Cortex | Built-in |
| HA mode | Federation/Thanos | VMCluster |
| Remote write support | Yes | Yes |
| Single binary | No (multi-component) | VMSingle |
For most setups < 1M active series, VMSingle is simpler and more disk-efficient than a Prometheus + Thanos stack.
Debugging
bash# Check VMAgent status
kubectl describe vmagent vmagent -n observability
# Check remote_write queue
kubectl port-forward -n observability svc/vmagent 8429:8429
curl -s http://localhost:8429/metrics | grep vm_remotewrite_queue
# Query VMSingle directly
kubectl port-forward -n observability svc/victoria-metrics-victoria-metrics 8428:8428
curl -s 'http://localhost:8428/api/v1/query?query=up' | jq '.data.result | length'