目标:在 K8s 集群中,于两个相同规格 Node 上分别部署 Prometheus 和 VictoriaMetrics 单机版,相同工作负载下系统性压测,量化两者在写入、查询、资源占用、压缩效率等维度的差异,为生产选型提供数据支撑。
本文档所有 YAML 均为实际部署并跑通的配置(UCloud UK8s 集群),结果章节留空,待测试完成后回填。
01零、SRE 性能指标科普
做压测、看监控、定 SLO 之前,先把这套术语体系理清。这些指标分四类:流量/吞吐、延迟、可靠性、资源,再加 TSDB 专属指标。
0.1 流量与吞吐类
| 指标 | 全称 | 含义 | 典型场景 |
|---|---|---|---|
| PV | Page View | 页面浏览量,用户每打开/刷新一个页面记一次,不去重 | 网站流量统计 |
| UV | Unique Visitor | 独立访客数,按用户标识(cookie/账号/IP)去重 | 网站活跃用户评估 |
| QPS | Queries Per Second | 每秒查询数,衡量"读"频率 | API 网关、数据库查询、Prometheus 查询 |
| RPS | Requests Per Second | 每秒请求数,比 QPS 更通用(一个请求未必是一次查询) | HTTP 服务通用指标 |
| TPS | Transactions Per Second | 每秒事务数,一次事务通常含多步操作(读+写+提交),比 QPS 重 | 数据库事务、支付下单 |
| Throughput | 吞吐量 | 单位时间处理量,可以是 req/s、MB/s、samples/s,取决于语境 | 通用 |
| Concurrency | 并发数 | 同一时刻在处理中的请求/任务数 | 压测核心参数 |
QPS vs TPS 的区别:QPS 侧重"查询"(读为主),TPS 侧重"事务"(含写与一致性保证)。一次下单事务 = 查库存 + 扣库存 + 生成订单,算 1 TPS 但可能算多次查询。本压测中数据写入类似 TPS(落盘 + WAL),查询类似 QPS。
0.2 延迟类
| 指标 | 含义 |
|---|---|
| RT (Response Time) | 响应时间,从客户端发出请求到收到完整响应的端到端时间(含网络 + 排队 + 处理) |
| Latency | 延迟,通常指服务端处理耗时,比 RT 小(不含客户端网络) |
| TTFB | Time To First Byte,首字节时间,衡量首包快慢 |
| P50 / 中位数 | 50% 的请求快于该值,反映"典型体验" |
| P95 | 95% 的请求快于该值,反映"大多数用户的下限" |
| P99 | 99% 的请求快于该值,反映"长尾用户体验" |
| P999 | 99.9 分位,反映"极端长尾" |
| Tail Latency | 尾延迟,高分位延迟,往往是性能瓶颈最先暴露处 |
⚠️ 永远不要只看平均值:平均延迟会掩盖长尾。1000 个请求里 999 个 10ms、1 个 10s,平均 ~20ms 看着挺好,但那个 10s 的用户体验已经崩了。所以监控必须看 P95/P99。
0.3 可靠性类
| 指标 | 含义 |
|---|---|
| SLI | Service Level Indicator,服务水平指标——你测量的具体数值(如 P99 延迟、可用率、错误率) |
| SLO | Service Level Objective,服务水平目标——你对 SLI 设的内部目标(如 P99 < 500ms,可用率 99.9%) |
| SLA | Service Level Agreement,服务水平协议——跟客户签的合同条款,违反要赔偿,通常比 SLO 宽松 |
| Availability(可用性) | 服务可用时间占比,用"几个 9"衡量 |
| Error Rate | 错误率,错误请求 / 总请求 |
| MTBF | Mean Time Between Failures,平均故障间隔时间(两次故障之间)——越大越稳定 |
| MTTF | Mean Time To Failure,平均无故障时间(不可修复系统到首次故障) |
| MTTR | Mean Time To Repair/Recover,平均恢复时间——故障发生到服务恢复,越小越好 |
| MTTD | Mean Time To Detect,平均检测时间——故障发生到被告警发现 |
可用性的"几个 9"对照表(一年为周期):
| 可用率 | 年停机预算 | 典型定位 |
|---|---|---|
| 99%(2 个 9) | 3.65 天 | 准生产 |
| 99.9%(3 个 9) | 8.76 小时 | 一般生产 |
| 99.99%(4 个 9) | 52.6 分钟 | 核心生产 |
| 99.999%(5 个 9) | 5.26 分钟 | 电信/金融核心 |
核心公式:可用性 = MTBF / (MTBF + MTTR)。要提升可用性,要么让故障更少(↑MTBF),要么让恢复更快(↓MTTR)。本压测的"重启恢复时间"场景(T8)测的就是 MTTR。
0.4 资源类(USE 方法)
USE = Utilization(利用率)/ Saturation(饱和度)/ Errors(错误),对每种资源都看这三件事:
| 资源 | Utilization 利用率 | Saturation 饱和度 | Errors |
|---|---|---|---|
| CPU | CPU% | 运行队列长度、负载 | - |
| 内存 | 已用/总量 | OOM、swap、换页 | OOM kill 次数 |
| 磁盘 | 已用/总量 | IOPS、队列深度 | 读写错误 |
| 网络 | 带宽% | 丢包、重传 | 错误包 |
0.5 TSDB(时序数据库)专属指标
本压测最关心的就是这些——它们决定一个监控后端能不能扛住生产规模:
| 指标 | 含义 | 为什么重要 |
|---|---|---|
| Ingestion rate (samples/s) | 每秒写入样本数 | 写入跟不上采集 = 数据丢失/堆积 |
| Active series | 当前活跃时间序列数 | 直接决定内存占用,是 OOM 主因 |
| Cardinality(基数) | 不同 label 组合数 | 高基数 = 内存爆炸 + 查询变慢,TSDB 头号杀手 |
| Churn rate | 序列流失率,旧 series 消失、新 series 产生的速率 | 高 churn 导致索引膨胀、压缩失效 |
| bytes/sample | 每个样本平均占用字节数 | 衡量压缩效率,决定存储成本 |
| WAL | Write-Ahead Log,预写日志 | 写入持久化保障,影响崩溃恢复 |
| Query latency | 查询延迟(P50/P99) | Dashboard 响应、告警时效 |
| IOPS | 每秒 I/O 操作数 | 磁盘性能瓶颈,判断是否要 NVMe |
02一、压测维度总览
| 维度 | 关键指标 | 意义 |
|---|---|---|
| 写入性能 | samples/s 写入速率、写入延迟、写入丢弃率 | 能否跟上采集量 |
| 查询性能 | p50/p95/p99 延迟、QPS | Dashboard 响应速度与告警时效 |
| 内存 | RSS 峰值/均值、OOM 风险 | 决定机器规格与成本 |
| CPU | 平均/峰值利用率 | 决定核心数与成本 |
| 磁盘空间 | 总占用、bytes/sample 压缩比 | 决定存储成本与保留周期 |
| 磁盘 I/O | 读写带宽峰值、IOPS | 判断是否需要 NVMe |
| 高基数/churn | 高 churn 下的内存膨胀与查询退化 | 生产中最常见的故障模式 |
| 长周期查询 | 大时间范围查询的延迟与资源消耗 | 影响排查体验 |
| 恢复速度 | 重启后数据加载时间、查询可用时间 | 影响故障恢复 MTTR |
03二、实验环境
2.1 K8s 集群拓扑
UCloud UK8s 集群。通过
nodeSelector将 Prometheus 和 VictoriaMetrics 分别固定到不同 Node,消除资源竞争;监控栈与负载生成器调度到第三个工作节点,不抢被测实例资源。
┌──────────────────────────────────────────────────────────────────┐
│ UK8s Cluster │
│ │
│ ┌─────────────────────────┐ ┌─────────────────────────────┐ │
│ │ Node: bench-node-1 │ │ Node: bench-node-2 │ │
│ │ (label: role=prom) │ │ (label: role=vm) │ │
│ │ │ │ │ │
│ │ ┌───────────────────┐ │ │ ┌───────────────────────┐ │ │
│ │ │ Prometheus v3.12 │ │ │ │ VictoriaMetrics │ │ │
│ │ │ (被测实例 A) │ │ │ │ 单机版 (被测实例 B) │ │ │
│ │ │ PVC: 40Gi SSD │ │ │ │ PVC: 40Gi SSD │ │ │
│ │ └───────────────────┘ │ │ └───────────────────────┘ │ │
│ └─────────────────────────┘ └─────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ Node: bench-node-3 (无 role 标签) + 其他工作节点 │ │
│ │ ┌─────────────┐ ┌──────────┐ ┌──────────┐ ┌───────────┐ │ │
│ │ │ vmagent │ │ vmalert │ │ Monitor │ │ Grafana │ │ │
│ │ │ (写入负载) │ │(查询负载)│ │Prometheus│ │ Dashboard │ │ │
│ │ └─────────────┘ └──────────┘ └──────────┘ └───────────┘ │ │
│ │ + 50 个 benchmark-node-exporter (指标生成) │ │
│ └─────────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────┘2.2 节点规格与资源约束
用 Pod 的
resources.limits把被测实例锁死在 2 vCPU / 3500Mi,模拟 2C4G 节点(留 ~500MB 给系统/kubelet)。即使宿主机规格更大,压测也只看 limit 内的 2C4G 表现,保证公平。
| 项目 | 规格 | 说明 |
|---|---|---|
| 被测实例 CPU | 2 vCPU(limit) | resources.limits.cpu=2 |
| 被测实例内存 | 3500Mi(limit) | 留 ~500Mi 给系统,4G 下 Prometheus 更易 OOM |
| 磁盘 | 40 Gi SSD | storageClassName: ssd-csi-udisk |
| K8s | v1.30.14 | UK8s |
| 容器运行时 | containerd 1.6+ |
⚠️ 踩过的坑:原计划用 RSSD 盘(
csi-udisk-rssd),但 VM 节点所在可用区 RSSD 云盘缺货(no available set exist)。最终两边统一改用ssd-csi-udisk,保证磁盘维度公平可比。
2.3 Node 标签与隔离
# 给两个 Node 打标签,用于 nodeSelector 调度
kubectl label node <bench-node-1> role=prom
kubectl label node <bench-node-2> role=vm
# benchmark namespace 放开 Pod Security Admission,允许 Prometheus v3 以 root 写 PVC
kubectl label namespace benchmark pod-security.kubernetes.io/enforce=privileged --overwrite为什么 namespace 要 privileged:Prometheus v3 镜像默认以
nobody(UID 65534) 运行,写 PVC 时报permission denied。需要securityContext.runAsUser=0,而默认的restrictedPSA 会剥离该字段,必须显式放开。
2.4 软件版本与镜像
全部使用 UCloud 内网镜像仓库
uhub.service.ucloud.cn/prometheusv3/,拉取快、免外网。
| 组件 | 镜像 | 部署方式 |
|---|---|---|
| Prometheus(被测) | uhub.service.ucloud.cn/prometheusv3/prometheus:v3.12.0 |
StatefulSet + PVC |
| VictoriaMetrics 单机版(被测) | uhub.service.ucloud.cn/prometheusv3/victoria-metrics:latest |
StatefulSet + PVC |
| vmagent(写入负载) | uhub.service.ucloud.cn/prometheusv3/vmagent:latest |
Deployment |
| vmalert(查询负载) | uhub.service.ucloud.cn/prometheusv3/vmalert:latest |
Deployment |
| node-exporter(指标源) | uhub.service.ucloud.cn/prometheusv3/node-exporter:latest |
Deployment × 50 |
| Monitoring Prometheus | uhub.service.ucloud.cn/prometheusv3/prometheus:v3.12.0 |
Deployment + emptyDir |
| Grafana | uhub.service.ucloud.cn/prometheusv3/grafana:11.3.0 |
Deployment |
2.5 监控方案
用一套独立的轻量 Prometheus 采集两个被测实例 + kubelet/cAdvisor,Grafana 连这个监控 Prometheus 做可视化。监控栈调度到非测试 Node,不抢被测实例资源。监控 Prometheus 同时采集 5 类目标:
| scrape job | 目的 |
|---|---|
prometheus-under-test |
采集 Prometheus 被测实例的自监控指标(prometheus_tsdb_* 等) |
victoriametrics-under-test |
采集 VM 被测实例的自监控指标(vm_* 等) |
vmagent |
采集 vmagent 的采集/发送指标 |
kubernetes-cadvisor |
容器级 CPU/内存(container_*) |
kubernetes-nodes |
节点级 kubelet 指标 |
04三、工作负载设计
3.1 数据源:真实 node_exporter 指标
部署 50 个 node-exporter Pod 作为采集目标,用 vmagent 通过 remote_write 同时把数据写进 Prometheus 和 VM——两边接收完全相同的写入流,这是公平性的基石。
3.2 压力档位
⚠️ 资源有限,默认 50 目标起步。Prometheus 在 3500Mi 内存下,目标数上去后更易 OOM。
| 场景 | 目标数 | scrape_interval | 预估 samples/s | 预估 active series | 说明 |
|---|---|---|---|---|---|
| 轻量 | 50 | 15s | ~4K | ~60K | 小集群,两者均无压力 |
| 中等 | 100 | 15s | ~8K | ~120K | 中型集群,Prometheus 开始承压 |
| 重度 | 200 | 15s | ~16K | ~240K | 4G 下 Prometheus 可能 OOM |
每个 node_exporter 约产生 ~1200 个时间序列。调整目标数用
kubectl scale deployment benchmark-node-exporter --replicas=N。
3.3 Churn 模拟
生产中 Label 变化(Pod 重建、CI/CD)导致 series churn,需单独测试:
| Churn 场景 | 配置 | 说明 |
|---|---|---|
| 低 churn | 0%(baseline) | 稳定环境 |
| 中 churn | 5%/10min | 正常 K8s 集群 |
| 高 churn | 20%/5min | 频繁滚动更新 |
3.4 查询负载
用 vmalert 跑告警规则生成查询负载,同时对 Prometheus 和 VM 两个数据源发查询,模拟真实读操作(range query + instant query)。
05四、测试场景矩阵
4.1 核心对比场景
| 编号 | 场景 | 目标数 | Churn | 查询负载 | 运行时长 | 核心观测 |
|---|---|---|---|---|---|---|
| T1 | 基线对比 | 100 | 5%/10min | alerting rules | 7d | 全量指标基线 |
| T2 | 高写入压力 | 200 | 5%/10min | alerting rules | 7d | 写入吞吐 & 内存 |
| T3 | 高 Churn | 100 | 20%/5min | alerting rules | 7d | 内存膨胀 & 压缩比 |
| T4 | 查询压力 | 100 | 5%/10min | alerting + 20 QPS range | 7d | 查询延迟 & CPU |
4.2 极限/边界场景
| 编号 | 场景 | 方法 | 运行时长 | 核心观测 |
|---|---|---|---|---|
| T5 | 写入极限 | 逐步增加目标至写入失败 | ~3h | 2C4G 下最大 samples/s |
| T6 | OOM 边界 | 固定 3500Mi,增加目标数 | ~4h | OOM 时的 series 数 |
| T7 | 磁盘瓶颈 | SSD → HDD PVC 对比 | 24h | I/O 对查询的影响 |
| T8 | 重启恢复 | delete pod → 重建,记录恢复时间 | 每次 ~30min | 数据加载 & 可用时间(MTTR) |
| T9 | 长周期查询 | 1d/7d range query | 单次 | 查询延迟 & 内存峰值 |
06五、关键指标采集方法
以下 PromQL 均在监控 Prometheus上执行(它带
job标签区分两边)。已按实际暴露的指标名校对。
5.1 Prometheus 侧指标
# 写入速率 (samples/s)
sum(rate(prometheus_tsdb_head_samples_appended_total{job="prometheus-under-test"}[5m]))
# Active series
max(prometheus_tsdb_head_series{job="prometheus-under-test"})
# 写入丢弃 (out-of-order / out-of-bounds)
sum(rate(prometheus_target_scrapes_sample_out_of_order_total{job="prometheus-under-test"}[5m]))
+ sum(rate(prometheus_target_scrapes_sample_out_of_bounds_total{job="prometheus-under-test"}[5m]))
# 查询延迟 p99
histogram_quantile(0.99,
sum by (le) (rate(prometheus_http_request_duration_seconds_bucket{job="prometheus-under-test",handler=~"/api/v1/query.*"}[5m])))
# 查询 QPS
sum(rate(prometheus_http_requests_total{job="prometheus-under-test",handler=~"/api/v1/query.*"}[5m]))
# 内存 / CPU
process_resident_memory_bytes{job="prometheus-under-test"}
rate(process_cpu_seconds_total{job="prometheus-under-test"}[5m])
# 磁盘占用
sum(prometheus_tsdb_storage_blocks_bytes{job="prometheus-under-test"})
# 压缩比 (bytes/sample)
sum(prometheus_tsdb_storage_blocks_bytes{job="prometheus-under-test"})
/ clamp_min(sum(prometheus_tsdb_head_samples_appended_total{job="prometheus-under-test"}), 1)5.2 VictoriaMetrics 侧指标
# 写入速率 (rows/s ≈ samples/s)
sum(rate(vm_rows_inserted_total{job="victoriametrics-under-test"}[5m]))
# Active series(缓存条目近似)
max(vm_cache_entries{job="victoriametrics-under-test",type=~"storage/.*"})
# 写入丢弃 (ignored + invalid)
sum(rate(vm_rows_ignored_total{job="victoriametrics-under-test"}[5m]))
+ sum(rate(vm_rows_invalid_total{job="victoriametrics-under-test"}[5m]))
# 查询延迟 p99
histogram_quantile(0.99,
sum by (le) (rate(vm_http_request_duration_seconds_bucket{job="victoriametrics-under-test",path=~"/api/v1/query.*"}[5m])))
# 查询 QPS
sum(rate(vm_http_requests_total{job="victoriametrics-under-test",path=~"/api/v1/query.*"}[5m]))
# 内存 / CPU
process_resident_memory_bytes{job="victoriametrics-under-test"}
rate(process_cpu_seconds_total{job="victoriametrics-under-test"}[5m])
# 磁盘占用
vm_data_size_bytes{job="victoriametrics-under-test"}
# 压缩比 (bytes/sample)
vm_data_size_bytes{job="victoriametrics-under-test"}
/ clamp_max(vm_rows{job="victoriametrics-under-test",type="metric"}, 1)⚠️ VM 的查询延迟指标是
vm_http_request_duration_seconds_bucket(带path标签),不是vm_request_duration_seconds_bucket(后者只有 count/sum 没有 bucket,无法算分位)。这是实测踩坑点。
5.3 容器级指标(cAdvisor)
# 容器内存 working set(K8s 实际统计、OOM 判定依据)
container_memory_working_set_bytes{namespace="benchmark",pod="prometheus-under-test-0",container="prometheus"}
container_memory_working_set_bytes{namespace="benchmark",pod="victoriametrics-under-test-0",container="victoria-metrics"}
# 容器 CPU (cores)
rate(container_cpu_usage_seconds_total{namespace="benchmark",pod="prometheus-under-test-0",container="prometheus"}[5m])
rate(container_cpu_usage_seconds_total{namespace="benchmark",pod="victoriametrics-under-test-0",container="victoria-metrics"}[5m])5.4 vmagent 采集状态
# 采集目标 up/down 数
vm_promscrape_targets{job="vmagent",status="up"}
vm_promscrape_targets{job="vmagent",status="down"}
# 抓取 samples/s
sum(rate(vm_promscrape_scraped_samples_sum{job="vmagent"}[5m]))
# remote_write 队列堆积(按 url)
vmagent_remotewrite_pending_data_bytes{job="vmagent"}
# remote_write 错误
vmagent_remotewrite_errors_total{job="vmagent"}5.5 K8s 运维命令
kubectl top pods -n benchmark # Pod 实时资源
kubectl top nodes # Node 实时资源
kubectl get pvc -n benchmark # PVC 磁盘使用07六、K8s 部署方案
全部部署在
benchmarknamespace。以下 YAML 均为实际跑通的配置。
6.1 Namespace 与 PSA
kubectl create namespace benchmark
kubectl label namespace benchmark pod-security.kubernetes.io/enforce=privileged --overwrite6.2 Prometheus 被测实例(固定到 role=prom)
# prometheus-under-test.yaml
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: prometheus
namespace: benchmark
---
apiVersion: v1
kind: ConfigMap
metadata:
name: prometheus-config
namespace: benchmark
data:
prometheus.yml: |
global:
scrape_interval: 15s
evaluation_interval: 15s
# 仅接收 remote_write,不做本地采集
# 启动参数已开启 --web.enable-remote-write-receiver
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: prometheus-under-test
namespace: benchmark
spec:
serviceName: prometheus-under-test
replicas: 1
selector:
matchLabels:
app: prometheus-under-test
template:
metadata:
labels:
app: prometheus-under-test
spec:
serviceAccountName: prometheus
securityContext:
runAsUser: 0 # v3 默认 nobody,写 PVC 会 permission denied
fsGroup: 0
nodeSelector:
role: prom
containers:
- name: prometheus
image: uhub.service.ucloud.cn/prometheusv3/prometheus:v3.12.0
args:
- --config.file=/etc/prometheus/prometheus.yml
- --storage.tsdb.path=/var/lib/prometheus
- --storage.tsdb.retention.time=7d
- --web.enable-remote-write-receiver
- --web.enable-lifecycle
ports:
- containerPort: 9090
name: http
resources:
limits:
cpu: "2"
memory: 3500Mi
requests:
cpu: "2"
memory: 3500Mi
volumeMounts:
- name: config
mountPath: /etc/prometheus
- name: data
mountPath: /var/lib/prometheus
volumes:
- name: config
configMap:
name: prometheus-config
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: [ReadWriteOnce]
storageClassName: ssd-csi-udisk
resources:
requests:
storage: 40Gi
---
apiVersion: v1
kind: Service
metadata:
name: prometheus-under-test
namespace: benchmark
spec:
type: ClusterIP
ports:
- name: http
port: 9090
targetPort: 9090
selector:
app: prometheus-under-testv3 启动参数注意:v3 中
--storage.tsdb.path和--storage.tsdb.retention.time仍可用(retention.time标记 deprecated 但仍生效)。不要写成--storage.path(不存在)或--storage.tsdb.retention(缺.time)。
6.3 VictoriaMetrics 被测实例(固定到 role=vm)
# victoriametrics-under-test.yaml
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: victoriametrics-under-test
namespace: benchmark
spec:
serviceName: victoriametrics-under-test
replicas: 1
selector:
matchLabels:
app: victoriametrics-under-test
template:
metadata:
labels:
app: victoriametrics-under-test
spec:
nodeSelector:
role: vm
containers:
- name: victoria-metrics
image: uhub.service.ucloud.cn/prometheusv3/victoria-metrics:latest
args:
- -storageDataPath=/var/lib/victoria-metrics-data
- -retentionPeriod=7d
ports:
- containerPort: 8428
name: http
resources:
limits:
cpu: "2"
memory: 3500Mi
requests:
cpu: "2"
memory: 3500Mi
volumeMounts:
- name: data
mountPath: /var/lib/victoria-metrics-data
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: [ReadWriteOnce]
storageClassName: ssd-csi-udisk
resources:
requests:
storage: 40Gi
---
apiVersion: v1
kind: Service
metadata:
name: victoriametrics-under-test
namespace: benchmark
spec:
type: ClusterIP
ports:
- name: http
port: 8428
targetPort: 8428
selector:
app: victoriametrics-under-test6.4 指标源:node-exporter + Headless Service
# benchmark-targets.yaml
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: benchmark-node-exporter
namespace: benchmark
spec:
replicas: 50 # 起步 50,按场景 scale
selector:
matchLabels:
app: benchmark-node-exporter
template:
metadata:
labels:
app: benchmark-node-exporter
spec:
containers:
- name: node-exporter
image: uhub.service.ucloud.cn/prometheusv3/node-exporter:latest
ports:
- containerPort: 9100
name: metrics
resources:
limits:
cpu: 50m
memory: 64Mi
---
# Headless Service:vmagent 用 endpoints 发现必须依赖它
apiVersion: v1
kind: Service
metadata:
name: benchmark-node-exporter
namespace: benchmark
spec:
clusterIP: None
ports:
- name: metrics
port: 9100
targetPort: 9100
selector:
app: benchmark-node-exporter为什么必须 Headless Service:vmagent 用
kubernetes_sd_configs: role: endpoints发现目标,需要 Service 生成 Endpoints 对象。用clusterIP: None的 Headless Service 最干净,每个 Pod IP 都进 endpoints。
6.5 写入负载:vmagent + RBAC
⚠️ 关键坑:vmagent 的
remote_write不能写在 promscrape config 文件里(strict parse 会报field remote_write not found)。必须用-remoteWrite.url命令行参数传,可重复多次指向多个后端。
# vmagent.yaml
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: vmagent
namespace: benchmark
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: benchmark-vmagent
rules:
- apiGroups: [""]
resources: [pods, nodes, services, endpoints]
verbs: [get, list, watch]
- apiGroups: ["discovery.k8s.io"]
resources: [endpointslices]
verbs: [get, list, watch]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: benchmark-vmagent
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: benchmark-vmagent
subjects:
- kind: ServiceAccount
name: vmagent
namespace: benchmark
---
apiVersion: v1
kind: ConfigMap
metadata:
name: vmagent-config
namespace: benchmark
data:
vmagent.yml: |
global:
scrape_interval: 15s
scrape_configs:
- job_name: node_exporter
kubernetes_sd_configs:
- role: endpoints
namespaces:
names: [benchmark]
relabel_configs:
- source_labels: [__meta_kubernetes_service_name]
action: keep
regex: benchmark-node-exporter
- source_labels: [__meta_kubernetes_endpoint_port_name]
action: keep
regex: metrics
# 注意:remote_write 不写这里!用 -remoteWrite.url 命令行参数
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: vmagent
namespace: benchmark
spec:
replicas: 1
selector:
matchLabels:
app: vmagent
template:
metadata:
labels:
app: vmagent
spec:
serviceAccountName: vmagent
containers:
- name: vmagent
image: uhub.service.ucloud.cn/prometheusv3/vmagent:latest
args:
- -promscrape.config=/etc/vmagent/vmagent.yml
# remote_write 用命令行参数,同时写两个后端
- -remoteWrite.url=http://prometheus-under-test.benchmark.svc.cluster.local:9090/api/v1/write
- -remoteWrite.url=http://victoriametrics-under-test.benchmark.svc.cluster.local:8428/api/v1/write
ports:
- containerPort: 8429
name: http
resources:
limits:
cpu: "1"
memory: 1Gi
requests:
cpu: "1"
memory: 1Gi
volumeMounts:
- name: config
mountPath: /etc/vmagent
volumes:
- name: config
configMap:
name: vmagent-config
---
apiVersion: v1
kind: Service
metadata:
name: vmagent
namespace: benchmark
spec:
ports:
- name: http
port: 8429
targetPort: 8429
selector:
app: vmagentvmagent 会自动协商协议:对 Prometheus 降级用标准 remote_write 协议(Prometheus 不支持 VM 原生协议),对 VM 用原生协议。两边 0 错误即写入正常。
6.6 监控栈:monitoring-prometheus + RBAC + cAdvisor
# monitoring-stack.yaml
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: monitoring-prometheus
namespace: benchmark
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: monitoring-prometheus
rules:
- apiGroups: [""]
resources: [nodes, nodes/proxy, nodes/metrics]
verbs: [get, list, watch]
- apiGroups: [""]
resources: [services, endpoints, pods]
verbs: [get, list, watch]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: monitoring-prometheus
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: monitoring-prometheus
subjects:
- kind: ServiceAccount
name: monitoring-prometheus
namespace: benchmark
---
apiVersion: v1
kind: ConfigMap
metadata:
name: monitoring-prometheus-config
namespace: benchmark
data:
prometheus.yml: |
global:
scrape_interval: 10s
scrape_configs:
- job_name: prometheus-under-test
static_configs:
- targets: ['prometheus-under-test.benchmark.svc.cluster.local:9090']
- job_name: victoriametrics-under-test
metrics_path: /metrics
static_configs:
- targets: ['victoriametrics-under-test.benchmark.svc.cluster.local:8428']
- job_name: vmagent
static_configs:
- targets: ['vmagent.benchmark.svc.cluster.local:8429']
# kubelet cAdvisor — 容器级 CPU/内存
- job_name: kubernetes-cadvisor
scheme: https
tls_config:
ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
insecure_skip_verify: true
bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
kubernetes_sd_configs:
- role: node
relabel_configs:
- action: labelmap
regex: __meta_kubernetes_node_label_(.+)
- target_label: __address__
replacement: kubernetes.default.svc:443
- source_labels: [__meta_kubernetes_node_name]
regex: (.+)
target_label: __metrics_path__
replacement: /api/v1/nodes/${1}/proxy/metrics/cadvisor
# kubelet 自身指标 — 节点级
- job_name: kubernetes-nodes
scheme: https
tls_config:
ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
insecure_skip_verify: true
bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
kubernetes_sd_configs:
- role: node
relabel_configs:
- action: labelmap
regex: __meta_kubernetes_node_label_(.+)
- target_label: __address__
replacement: kubernetes.default.svc:443
- source_labels: [__meta_kubernetes_node_name]
regex: (.+)
target_label: __metrics_path__
replacement: /api/v1/nodes/${1}/proxy/metrics
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: monitoring-prometheus
namespace: benchmark
spec:
replicas: 1
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 0 # 资源紧,避免新旧共存调度不下
maxUnavailable: 1
selector:
matchLabels:
app: monitoring-prometheus
template:
metadata:
labels:
app: monitoring-prometheus
spec:
serviceAccountName: monitoring-prometheus
securityContext:
runAsUser: 0
fsGroup: 0
containers:
- name: prometheus
image: uhub.service.ucloud.cn/prometheusv3/prometheus:v3.12.0
args:
- --config.file=/etc/prometheus/prometheus.yml
- --storage.tsdb.path=/var/lib/prometheus
- --storage.tsdb.retention.time=7d
- --web.enable-lifecycle
ports:
- containerPort: 9090
name: http
resources:
limits:
cpu: 500m
memory: 1Gi
requests:
cpu: 200m
memory: 512Mi
volumeMounts:
- name: config
mountPath: /etc/prometheus
- name: data
mountPath: /var/lib/prometheus
volumes:
- name: config
configMap:
name: monitoring-prometheus-config
- name: data
emptyDir: {} # 监控数据不持久化,重启可丢
---
apiVersion: v1
kind: Service
metadata:
name: monitoring-prometheus
namespace: benchmark
spec:
ports:
- name: http
port: 9090
targetPort: 9090
selector:
app: monitoring-prometheus6.7 Grafana + 数据源自动 provisioning
# grafana.yaml
---
apiVersion: v1
kind: ConfigMap
metadata:
name: grafana-datasources
namespace: benchmark
data:
datasources.yaml: |
apiVersion: 1
datasources:
- name: Prometheus
uid: prom # 仪表盘用这个 uid 引用
type: prometheus
access: proxy
url: http://monitoring-prometheus.benchmark.svc.cluster.local:9090
isDefault: true
editable: true
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: grafana
namespace: benchmark
spec:
replicas: 1
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 0
maxUnavailable: 1
selector:
matchLabels:
app: grafana
template:
metadata:
labels:
app: grafana
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: role
operator: NotIn
values: [prom, vm]
containers:
- name: grafana
image: uhub.service.ucloud.cn/prometheusv3/grafana:11.3.0
ports:
- containerPort: 3000
name: http
env:
- name: GF_SECURITY_ADMIN_PASSWORD
value: admin
- name: GF_AUTH_ANONYMOUS_ENABLED
value: "true"
resources:
limits:
cpu: 500m
memory: 512Mi
requests:
cpu: 100m
memory: 256Mi
volumeMounts:
- name: config
mountPath: /etc/grafana/provisioning/datasources
volumes:
- name: config
configMap:
name: grafana-datasources
---
apiVersion: v1
kind: Service
metadata:
name: grafana
namespace: benchmark
spec:
ports:
- name: http
port: 3000
targetPort: 3000
selector:
app: grafana6.8 Ingress(统一入口)
# ingress.yaml
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: benchmark-ingress
namespace: benchmark
spec:
ingressClassName: nginx
rules:
- host: prom-test.benchmark.local
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: prometheus-under-test
port: { number: 9090 }
- host: vm-test.benchmark.local
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: victoriametrics-under-test
port: { number: 8428 }
- host: prom-monitor.benchmark.local
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: monitoring-prometheus
port: { number: 9090 }
- host: grafana.benchmark.local
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: grafana
port: { number: 3000 }访问前在本地 /etc/hosts 加:
<集群入口IP> prom-test.benchmark.local vm-test.benchmark.local prom-monitor.benchmark.local grafana.benchmark.local6.9 查询负载:vmalert(待部署)
vmalert 负责对两个后端发查询,产生查询负载,让"查询性能"维度有数据。当前尚未部署,正式压测前需补上。
# vmalert.yaml
---
apiVersion: v1
kind: ConfigMap
metadata:
name: vmalert-rules
namespace: benchmark
data:
rules.yml: |
groups:
- name: benchmark-queries
interval: 15s
rules:
- alert: HighCPU
expr: 100 - (avg by(instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) > 80
for: 5m
- alert: HighMemory
expr: (1 - node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes) * 100 > 85
for: 5m
- alert: DiskFull
expr: (1 - node_filesystem_avail_bytes / node_filesystem_size_bytes) * 100 > 85
for: 5m
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: vmalert
namespace: benchmark
spec:
replicas: 1
selector:
matchLabels:
app: vmalert
template:
metadata:
labels:
app: vmalert
spec:
containers:
- name: vmalert
image: uhub.service.ucloud.cn/prometheusv3/vmalert:latest
args:
- -rule=/etc/vmalert/rules.yml
- -datasource.url=http://prometheus-under-test.benchmark.svc.cluster.local:9090
- -notifier.url=http://blackhole.default.svc.cluster.local
volumeMounts:
- name: rules
mountPath: /etc/vmalert
resources:
limits:
cpu: 500m
memory: 512Mi
volumes:
- name: rules
configMap:
name: vmalert-rules⚠️ vmalert 一个实例只能配一个
-datasource.url。要同时压测两个后端,部署两个 vmalert 实例(各指向一个数据源,用同一份 rules),或分两次跑。-notifier.url指向一个不存在的地址即可(告警发不出,只为触发查询)。
08七、Grafana 仪表盘
已通过 Grafana API 导入对比仪表盘,uid=prom-vm-bench,数据源引用 uid=prom。访问:http://grafana.benchmark.local/d/prom-vm-bench。
仪表盘 JSON 单独存于 prometheus-vs-victoriametrics-dashboard.json,可直接在 Grafana UI 用
Import上传,或用下方 API 导入。
仪表盘统一配色:Prometheus = 蓝色,VictoriaMetrics = 橙色,同面板双查询叠加,扫一眼即知谁高谁低。
| 行 | 面板 | 查询要点 |
|---|---|---|
| ① 概览 | 实例状态 / 写入速率 / Active Series | up、rate(...samples_appended_total) vs rate(vm_rows_inserted_total) |
| ② 写入性能 | 写入吞吐对比 / 写入丢弃 | out-of-order vs ignored+invalid |
| ③ 资源占用 | 进程内存 RSS(带 3500Mi OOM 阈值线)/ 进程 CPU / 磁盘占用 / 压缩比 | process_resident_memory_bytes / process_cpu_seconds_total |
| ④ 查询性能 | 查询延迟 p99 / 查询 QPS | prometheus_http_request_duration_seconds_bucket vs vm_http_request_duration_seconds_bucket |
| ⑤ K8s 容器资源 | 容器内存 working set(带 OOM 阈值)/ 容器 CPU | cAdvisor container_* 指标 |
导入方式(仪表盘 JSON 见仓库配套文件,或用 Grafana UI Import):
# 通过 Grafana HTTP API 导入
python3 -c "import json; d=json.load(open('dashboard.json')); print(json.dumps({'dashboard':d,'overwrite':True}))" > /tmp/payload.json
kubectl cp /tmp/payload.json benchmark/<grafana-pod>:/tmp/payload.json
kubectl exec <grafana-pod> -n benchmark -- curl -s -X POST http://localhost:3000/api/dashboards/db \
-H "Content-Type: application/json" -u admin:admin -d @/tmp/payload.json09八、执行流程
8.1 每个场景的标准流程
┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ 1. 清空数据 │───▶│ 2. 重新部署 │───▶│ 3. 预热30min │───▶│ 4. 持续压测 │───▶│ 5. 数据采集 │
│ 删 PVC/重建 │ │ kubectl apply│ │ 稳态确认 │ │ 按场景运行 │ │ 导出指标 │
└──────────────┘ └──────────────┘ └──────────────┘ └──────────────┘ └──────────────┘8.2 初始部署
# 0. namespace + PSA
kubectl create namespace benchmark
kubectl label namespace benchmark pod-security.kubernetes.io/enforce=privileged --overwrite
kubectl label node <bench-node-1> role=prom
kubectl label node <bench-node-2> role=vm
# 1. 被测实例
kubectl apply -f prometheus-under-test.yaml
kubectl apply -f victoriametrics-under-test.yaml
# 2. 指标源 + 写入负载
kubectl apply -f benchmark-targets.yaml
kubectl apply -f vmagent.yaml
# 3. 监控栈 + 入口
kubectl apply -f monitoring-stack.yaml
kubectl apply -f grafana.yaml
kubectl apply -f ingress.yaml
# 4. 查询负载(正式压测前补)
kubectl apply -f vmalert.yaml
# 5. 等 Pod 就绪
kubectl get pods -n benchmark -w8.3 每场景重置脚本
#!/bin/bash
# reset_scenario.sh — 场景间清空数据重新开始
NS=benchmark
echo "=== 重置场景 ==="
kubectl delete statefulset prometheus-under-test -n $NS
kubectl delete statefulset victoriametrics-under-test -n $NS
kubectl delete pvc data-prometheus-under-test-0 -n $NS
kubectl delete pvc data-victoriametrics-under-test-0 -n $NS
kubectl apply -f prometheus-under-test.yaml
kubectl apply -f victoriametrics-under-test.yaml
kubectl rollout status statefulset/prometheus-under-test -n $NS --timeout=300s
kubectl rollout status statefulset/victoriametrics-under-test -n $NS --timeout=300s
echo "预热 30 分钟..."
sleep 1800
# 确认写入正常
echo "Prometheus 写入速率:"
kubectl exec -n $NS prometheus-under-test-0 -- \
wget -qO- 'http://localhost:9090/api/v1/query?query=sum(rate(prometheus_tsdb_head_samples_appended_total[5m]))'
echo "VM 写入速率:"
kubectl exec -n $NS victoriametrics-under-test-0 -- \
wget -qO- 'http://localhost:8428/api/v1/query?query=sum(rate(vm_rows_inserted_total[5m]))'
echo "=== 重置完成,可开始压测 ==="8.4 调整场景参数
# 调整目标数
kubectl scale deployment benchmark-node-exporter -n benchmark --replicas=1008.5 极限场景执行方法
# T5: 写入极限 — 逐步增加目标数
for targets in 50 100 150 200 250 300; do
echo "=== 测试 ${targets} 目标 ==="
kubectl scale deployment benchmark-node-exporter -n benchmark --replicas=${targets}
sleep 3600
# 检测写入堆积/失败见监控仪表盘
done
# T8: 重启恢复测试(测 MTTR)
# Prometheus
START_TS=$(date +%s%N)
kubectl delete pod prometheus-under-test-0 -n benchmark
kubectl wait --for=condition=Ready pod/prometheus-under-test-0 -n benchmark --timeout=600s
# 轮询直到首次成功查询...
END_TS=$(date +%s%N)
echo "Prometheus 恢复时间: $(( (END_TS - START_TS) / 1000000 )) ms"
# VictoriaMetrics 同理10九、结果记录
测试完成后回填。每个场景(T1-T9)独立记录一张表,最后汇总。
9.1 核心结果表(T_x 场景)
| 指标 | Prometheus | VictoriaMetrics | VM 优势倍数 | 备注 |
|---|---|---|---|---|
| 场景 / 目标数 / 时长 | ||||
| 写入速率 (samples/s) | ||||
| Active series | ||||
| 写入丢弃 (samples/s) | ||||
| 内存均值 (Mi) | ||||
| 内存峰值 (Mi) | ||||
| CPU 均值 (cores) | ||||
| CPU 峰值 (cores) | ||||
| 磁盘占用 (Gi) | ||||
| bytes/sample | ||||
| 查询 p50 (ms) | ||||
| 查询 p95 (ms) | ||||
| 查询 p99 (ms) | ||||
| 查询 QPS | ||||
| 重启恢复时间 (s) | ||||
| OOM 次数 |
9.2 分场景汇总
| 场景 | 写入 samples/s (P/VM) | 内存峰值 Mi (P/VM) | 磁盘 Gi (P/VM) | 查询 p99 ms (P/VM) | 结论 |
|---|---|---|---|---|---|
| T1 基线 (100) | |||||
| T2 高写入 (200) | |||||
| T3 高 Churn | |||||
| T4 查询压力 | |||||
| T5 写入极限 | |||||
| T6 OOM 边界 | |||||
| T7 磁盘瓶颈 | |||||
| T8 重启恢复 | |||||
| T9 长周期查询 |
9.3 关键观察与结论
(测试后填写:哪个场景下谁明显占优、是否出现 OOM、资源瓶颈在哪、生产选型建议)
11十、预期结论(基于已有基准测试)
根据 VictoriaMetrics 官方和社区已发布的基准测试,预期结果:
| 维度 | 预期 VM 优势 | 依据 |
|---|---|---|
| 磁盘空间 | 3-7x 更少 | 改进 Gorilla 压缩,0.3 vs 1.15-2.1 bytes/sample |
| 内存 | 1.7-5x 更少 | 更高效的内存索引结构 |
| 查询延迟 p50 | 10-16x 更快 | 更优的数据布局与合并策略 |
| 查询延迟 p99 | 1.9-2x 更快 | 减少尾部延迟 |
| CPU | 基本持平 | 写入路径相似 |
| 写入极限 | 1.5-2x 更高 | 更高效的 WAL 和合并 |
| 重启恢复 | 2-5x 更快 | 更快的数据加载 |
⚠️ 以上为已有基准的预期值(基于 4C16G 环境)。本实验在 2C4G 下的实际结果可能因资源更紧张而差异更大——4G 内存下 Prometheus 更容易 OOM,VM 的内存优势会更显著。实验的核心价值在于你自己的环境和负载下的真实数据。
12十一、注意事项
11.1 公平性保障
- ✅ 两个 Node 硬件一致,
nodeSelector固定 Prometheus→role=prom、VM→role=vm - ✅ 两者均通过 vmagent
remote_write接收完全相同的写入流(非本地采集) - ✅ 保留时间一致(7d)
- ✅ StorageClass 一致(
ssd-csi-udisk) - ✅ 资源 limit 一致(2C / 3500Mi)
- ✅ 监控栈与负载生成器不调度到测试 Node
11.2 实测踩过的坑
| 坑 | 现象 | 解决 |
|---|---|---|
| Prometheus v3 PVC 权限 | open queries.active: permission denied → panic |
securityContext.runAsUser=0, fsGroup=0 |
| PSA 剥离 securityContext | runAsUser:0 被静默移除,Pod 仍以 nobody 起 |
namespace 打 pod-security.kubernetes.io/enforce=privileged |
| v3 启动参数名 | --storage.path / --storage.tsdb.retention 不存在 → Exit 1 |
用 --storage.tsdb.path + --storage.tsdb.retention.time |
| RSSD 云盘缺货 | VM 节点 PVC ProvisioningFailed: no available set exist |
两边统一用 ssd-csi-udisk |
| vmagent config 写 remote_write | field remote_write not found strict parse 报错 |
remote_write 改用 -remoteWrite.url 命令行参数 |
| vmagent 无 RBAC | pods is forbidden: User default cannot list |
建 ServiceAccount + ClusterRole/Binding |
| vmagent Pod IP 不可达 | no route to host(部分网络策略) |
改用 endpoints 发现 + Headless Service |
| 资源不足调度失败 | monitoring-prometheus/grafana Pending | 降 requests、设 maxSurge:0 滚动更新 |
11.3 结果可信度
- 每个核心场景至少运行 7 天(短期无法反映合并、压缩真实效果)
- 极限场景运行 至少 1 小时 后采集
- 关键指标取 稳态区间(去掉前 30 分钟预热)
- 重复 2-3 次取中位数
- 场景切换前必须删 PVC 重建,避免历史数据干扰
13十二、快速启动(最小可行实验)
# 0. 准备
kubectl create namespace benchmark
kubectl label namespace benchmark pod-security.kubernetes.io/enforce=privileged --overwrite
kubectl label node <bench-node-1> role=prom
kubectl label node <bench-node-2> role=vm
# 1. 被测实例
kubectl apply -f prometheus-under-test.yaml
kubectl apply -f victoriametrics-under-test.yaml
# 2. 指标源 + 写入负载
kubectl apply -f benchmark-targets.yaml
kubectl apply -f vmagent.yaml
# 3. 监控栈
kubectl apply -f monitoring-stack.yaml
kubectl apply -f grafana.yaml
kubectl apply -f ingress.yaml
# 4. 等就绪 + 预热 30min
kubectl get pods -n benchmark -w
# 5. 验证写入
kubectl exec -n benchmark prometheus-under-test-0 -- \
wget -qO- 'http://localhost:9090/api/v1/query?query=sum(rate(prometheus_tsdb_head_samples_appended_total[5m]))'
kubectl exec -n benchmark victoriametrics-under-test-0 -- \
wget -qO- 'http://localhost:8428/api/v1/query?query=sum(rate(vm_rows_inserted_total[5m]))'
# 6. 打开 Grafana 看对比仪表盘
# http://grafana.benchmark.local/d/prom-vm-bench (admin/admin)
# 数据源已自动 provisioning(uid=prom → monitoring-prometheus)