Prometheus 切换到 VictoriaMetrics 统一告警的部署记录
记录时间:2026-08-27
环境:RKE 信创 ARM64 双生产集群(clusterA / clusterB)+ 公司测试集群 / victoria-metrics-cluster 0.42.0(v1.143.0-cluster)/ victoria-metrics-operator 0.67.2(v0.74.0)/ kube-prometheus-stack 58.7.2 / PrometheusAlert v4.9.2
一、事情的由头
起因是一条 Swap 告警风暴:clusterA 一台主机 Swap 使用率 critical 告警 10 分钟一循环,规则是瞬时值直接比 80%,恢复侧没滞后,指标贴着阈值穿插,就变成 firing、resolved、再 firing。修好之后我想,两集群数据反正都写进 VM 了,告警为什么不也统一到 VM 评估,一处发。
| 目标 | 起始 | 完成 |
|---|---|---|
| 数据汇聚 | 两集群 Prometheus 已写 VM | 不变 |
| 统一告警引擎 | 各集群 PrometheusRule 各自评估 | vmalert + VMRule 一处评估 |
| 告警分发 | clusterA 的 Alertmanager + PAC | 复用现有 PAC,不加跨集群依赖 |
1.1 参考文章对照
后来翻到一篇”百万级指标切 VM 集群版”(WAKE UP技术),几个要点跟我的选择有差异有重合:
| 文章做法 | 我的选择 | 差异原因 |
|---|---|---|
| vmagent 替换 Prometheus 采集 | Prometheus 继续采集 remoteWrite 进 VM | 我停在九阶段第 4 步稳态,采集、短期存储、旧告警兜底都留 Prometheus |
| replicationFactor 2 | 1,无副本 | 存储是集中式 SSD NFS,两份副本写同一阵列买不到独立故障域 |
| 集群版 2+2+2 | 同款 | 扩容原理一致:vminsert 按标签哈希分片、vmselect 广播聚合 |
| k8s-stack chart 全家桶 | 裸 operator chart 加 CR 逐步搭 | 不动现有 Prometheus 体系;遇到的是另一边的坑:prometheus-converter 自动转换 |
| NodePort Service | Ingress 域名 + AM hostAliases 跨集群解析 | 我要跨集群访问 dyck PAC,NodePort 解决不了域名路由 |
| Grafana 填 /select/0/prometheus/ | 同款 | 一致 |
二、方案我前后翻了三版
2.1 第一版:victoria-metrics-alert 裸 chart,被否
起手用官方 victoria-metrics-alert chart,改一条规则要 helm upgrade 整个 release,站不住。改成独立 ConfigMap 挂载加 -configCheckInterval 热加载,又发现所有规则堆一个 yaml 会膨胀,而 ConfigMap 卷只能点名挂载,加新业务域文件还得改 values。绕 ConfigMap 模拟 CRD,总归有缺口。
2.2 第二版:全 operator
把现有 VM 全改造成 operator 行不行?VMAlert、VMAlertmanager、VMRule 全部 CR 化。VMRule 的 groups 语法跟 PrometheusRule 完全兼容,迁移几乎改个 kind,按业务域一文件、apply 秒级热加载,正是想要的体验。
2.3 第三版(终态):存储层不动,只上告警链
阶段二收编(helm 版 VM 集群改 VMCluster CR)动手前夜我掂量了一句:”helm 版跑了这么久不是更好吗,为什么一定要收编?”算完账认账:零功能收益的形态统一,代价是没演练过的 PVC 重绑加停机窗口。终态:告警链走 operator,存储层留 helm。 中间还试过裸 chart 装 vmalert 配 VMRule,推演后否了:operator 的 VMRule 动作链是 watch 规则、渲染配置、挂到它自己建的 vmalert,野生 vmalert 不在流程里,要 VMRule 的爽就必须让 operator 建 vmalert,没有中间态。
2.4 采集层也按兵不动:为什么不用 VMAgent 和 VMServiceScrape
VM operator 给抓取层也准备了一套等价 CRD:VMServiceScrape 对标 ServiceMonitor、VMPodScrape 对标 PodMonitor,还有 VMNodeScrape、VMProbe、VMStaticScrape,由 VMAgent 消费。VMServiceScrape 必须配 VMAgent 才有意义,Prometheus 不认,VMAgent 也不看 ServiceMonitor。operator 默认的 prometheus-converter 会自动转换 ServiceMonitor→VMServiceScrape、PrometheusRule→VMRule,测试环境我见过它把 kube-prometheus-stack 规则全量转换、日志刷屏,生产配了 disable_prometheus_converter: true。
采集层不换 VMAgent,三个理由:采集层没坏(换等于重写全部采集配置)、零功能收益(跟收编同理)、Prometheus 本地短期存储加 WAL 兜底(remoteWrite 断链自动补写,VM 挂了不全瞎)。哪天真要统一(省内存或全面替换),converter 或改 kind 就能迁,成本不高。没坏的东西别动它。
三、测试环境从零部署
生产是双集群,动手前先在测试集群完整跑一遍。目录按环境分组:xc-test/ 下分 operator/、cluster/、alerting/。
3.1 组件清单
| 组件 | 形态 | 关键配置 |
|---|---|---|
| victoria-metrics-operator | helm chart 0.67.2 | disable_prometheus_converter: true |
| VMCluster | CR | 2+2+2,retention 180d |
| VMAuth | CR + Secret | 统一入口,/select 与 /insert 分发,basic auth |
| VMAlert | CR | datasource 指 vmselect,selectAllByDefault: true |
| VMAlertmanager | CR + Secret | configSecret 模式,webhook 指测试 PAC |
| VMRule | CR | 每业务域一个文件 |
3.2 部署命令
helm upgrade --install victoria-metrics-operator victoria-metrics-operator-0.67.2.tgz -n monitoring -f operator/values.yaml
kubectl apply -f cluster/vmcluster.yaml
kubectl apply -f cluster/vmauth.yaml
kubectl apply -f alerting/vmalertmanager.yaml
kubectl apply -f alerting/vmalert.yaml
kubectl apply -f alerting/vmrule-node-exporter.yaml
3.3 测试环境遇到的六个问题
- prometheus-converter 批量转换。日志大片
failed to reconcile VMRule,是 converter 把 kube-prometheus-stack 的规则全量转成 VMRule、ServiceMonitor 转成 VMServiceScrape,配selectAllByDefault迟早告警风暴。处理:disable_prometheus_converter: true,删掉已转的 48 个对象。 - 命名是前缀式。svc 名
vmauth-vmauth、vmselect-victoria-metrics-cluster,跟 helm 后缀式(victoria-metrics-cluster-vmselect)相反,我按 helm 习惯写地址,vmauth 报 backend unavailable。operator 系资源名一律记组件名-<CR名>。 - VMAuth 的 Secret key 必须是
config.yaml。我按 AM 的习惯写了auth.yaml,Pod 卡 ContainerCreating 报 key 不存在。两个 CR 的 configSecret key 约定不一样,各记各的。 - Alertmanager 不认
loggerTimezone。VM 组件专属 flag 配给 prom/alertmanager,启动即退出 unknown long flag,AM 时区用 TZ 环境变量。 - sts 的 volumeClaimTemplates 不可变。换 storageClassName apply 显示 configured 就是不生效,换存储类只能删 CR 加 PVC 重建。
- remoteWrite 变更走热加载。加完 remoteWrite Pod 没滚动,我一度以为没生效,实际 config-reloader 直接 reload 进程,Pod AGE 不变是正常的。
验收:vmrule 放永真规则(expr: vector(1)),1 到 2 分钟测试企业微信群收到通知,vmalert → AM → PAC → 微信全通。
四、生产阶段一投产
阶段一只上告警链,datasource 直接指现有 helm 版 vmselect,VM 集群本体不碰,零风险;阶段二收编后来挂起(见 2.3)。
4.1 通知出口,来回最多的一块
第一版想 clusterB 本地装新 PAC。再查 dyck 的 PAC 配置发现它写 ES(alert_to_es=1),我初版关成 0,这不是平移是降级。又提启用 dyck ES 的 NodePort 让 clusterB 直连写,又否了:为一个记录功能暴露 ES 不值。最后定:PAC 用 dyck 的,AM 跨集群 webhook 指过去。
4.2 hostAliases 查证,我错了三次
复用 dyck PAC 要 clusterB 的 AM 解析 dyck ingress 域名,用 hostAliases 试试。我一开始不信,查了三处:chart 的 crd.yaml 没有、集群 CRD grep 没有、主 types 文件没有,三处都没有就下了结论不支持。翻官网文档一看明明白白支持,再翻源码,字段藏在嵌入的共享结构 CommonAppsParams 里,CRD 对它是 schemaless 处理不显式列出。schema 查不到不等于不支持。测试集群 patch 一下,hostAliases 立刻出现在生成的 StatefulSet 里,当场打脸。这条记死了:CR 字段要查嵌入结构,静态查证永远留余量,实测才是金标准。
4.3 生产专属的两个集群级问题
- ResourceQuota 强制所有容器带 limits。operator 注入的 config-init 和 config-reloader 边车没配资源,vmalert Pod FailedCreate(
must specify limits.cpu for: config-init,config-reloader)。解法是 CR 的configReloaderResources字段,同样藏在共享结构里。 - 边车镜像默认走 Docker Hub。config-reloader 镜像名没 registry 前缀,信创环境拉不下来。解法是 operator chart 的
global.image.registry。注意这个 chart 的 global 对边车生效,跟 victoria-metrics-alert chart 的 global 不生效是两回事,各自验证。
4.4 两个配置字段错误
- AM webhook 的 TLS 层级。
tls_config直接挂 webhook 条目下,operator 拒绝创建 AM,报field tls_config not found in type webhook.plain。正确是http_config.tls_config,六处全改。 - vmalert remoteWrite URL 双路径。日志全是 400,路径是
/insert/0/prometheus/api/v1/write/api/v1/write,vmalert 自己会拼/api/v1/write,我 URL 又写了一遍。删掉重写那截,400 消失。这个错直接导致 ALERTS 序列写不进 VM,Grafana 查不到新链告警历史。
4.5 端到端验收
生产 vmalert 加永真测试规则后,企业微信收到”告警链路连通性测试”通知,时间戳 +08:00 正确。整条路径:vmalert(clusterB,读 VM 汇聚数据)→ VMAlertmanager(hostAliases 跨集群解析加 https)→ dyck PAC → 企业微信。
五、规则迁移分批落地
5.1 顺序与铁律
四个批次:redis 13 条练手,kafka 18 条,node-exporter 33 条,k8s 拣选 37 条收尾。每批守同一条铁律:先 dyck 侧 PrometheusRule 下线再本侧 VMRule 启用,同一告警绝不允许两条链同时发。
业务三批转换手法一致:仓库 PrometheusRule 全文转 VMRule,groups 语法原样,labels 模板引用行(systemName: "{{ $labels.systemName }}")删掉(告警标签 = 序列全部标签,无需再声明),按业务域一目录一文件放 alerting/rules/<域>/,apply 即生效。
5.2 k8s 自带规则只拣选不搬全量
自带 128 告警加 85 记录,全量搬不现实。clusterB 的 19 个自带 PrometheusRule 无通知出口原样留;dyck 侧按”实际触发加运维价值”拣选 37 条:Pod 反复崩溃重启、Pod 长时间未就绪、HPA 副本到上限、Deployment 副本不一致、配额接近上限与已超限、PVC 空间严重不足、节点 NotReady 与不可达、Kubelet/APIServer/CM/Scheduler 不可用、etcd 全组 15、Alertmanager 自监控 8。告警名文案全中文化,拆六个文件(apps/resources/storage/control-plane/etcd/alertmanager)。
提取手法跟业务批不同:helm template 渲染 chart 后脚本按告警名提取变体,校验每条 expr 与渲染产物逐字符一致。中间有个反复处理的点:脚本默认序列化把多行 expr 写成带转义符的引号串("(\n 这种),改成块标量写法,多行字符串直接按块输出,干净了。
两个适配点:AM 自监控组的 job 选择器从 dyck 老 AM 的 pc-kube-prometheus-stack-alertmanager 换成 vmalertmanager-alertmanager(终态活着的 AM 只有它);etcd 组待命(依赖 job=~".*etcd.*" 抓取,当前没配 etcd 证书,规则不误报,配了自动生效)。
5.3 两个数据前提先补齐
- kubelet 的 volume 指标。KubePersistentVolumeFillingUp 匹配
metrics_path="/metrics",原 cadvisor 抓取是/metrics/cadvisor,路径对不上规则永远没数据。additionalScrapeConfigs 里给 cadvisor 加metrics_path标签补成/metrics,再加一个抓 kubelet 主端点的 job(rancher-kubelet-metrics)专取kubelet_volume_stats_*。两集群配置对齐后 PVC 规则才有数据。 - VMAlertmanager 指标抓取。AM 自监控 8 条规则的数据源是 AM 自身指标,先有 ServiceMonitor 把
vmalertmanager-alertmanager抓进 clusterB Prometheus。文件按”一服务一目录”归到 stack 目录other/vmalertmanager/,跟 cadvisor、kafka 平级。
5.4 dyck 侧收尾:全量下线加 AM 退役
业务三批逐个删 PrometheusRule(kubectl delete prometheusrule redis-rules kafka-rules node-exporter-rules)。k8s 批原计划 defaultRules.disabled 按名禁用,后来一步到位:两集群 values 都改 defaultRules.create: false,整块规则不渲染。验证时 kubectl get prometheusrule -n monitoring 返回 No resources found,比禁用更彻底,monitoring 下所有 PrometheusRule 对象全量回收,dyck 本地规则评估清零。
dyck 的 Alertmanager 同时退役:alertmanager.enabled: false,升级后 sts、deploy、ingress 清场,只剩一条 AM 的 PVC(nflog 和 silence 数据,10Gi nfs-dyck),无人消费,留还是删先记账。
终态达成:告警唯一源 = clusterB vmalert(101 条),经 VMAlertmanager 发 dyck PAC 到企业微信。kubectl get vmrule 全部 operational,微信收到中文版测试告警。
六、告警怎么看,收尾怎么收
6.1 告警查看入口
vmalert UI 的 Alerts 页一度让我以为是版本 bug:API(/api/v1/alerts)有 firing 数据,页面点 Alerts 是空的,F12 里 Network 和 Console 也干净。折腾半天最后换浏览器试出真相:Chrome 一切正常,Firefox 就是空白,是该版本 UI 跟 Firefox 兼容性差。日常看告警换 Chrome,另外两个入口:Grafana 大屏(ALERTS{alertstate="firing"} 六面板,按严重级别和业务域分布)、API 直查。
触发时间两处来源:ALERTS_FOR_STATE 样本值就是进入状态的 Unix 时间戳,time() - ALERTS_FOR_STATE 直接得持续时长;Grafana 里乘 1000 配 Datetime 显示。
6.2 收尾动作
- 删除测试规则组。
- run.sh 定稿终态运维手册;规则迁移章节标完成态,新规则走”复制现有 VMRule 改 name/groups 再 apply”。
- 目录整理:
cluster/放现行 helm 集群配置,阶段二收编 CR 改名cluster.后续/挂起,废弃裸 chart 目录删除。 - 文档同步:README 更新终态(clusterA 采集加 PAC 所在地、迁移进度表全完成、AM 退役),memory 归档。
- 待办挂账:退役 AM 的 PVC 残留处置(10Gi nfs-dyck)。
七、遇到的问题速查表
| 现象 | 根因 | 解法 |
|---|---|---|
| operator 启动大量转换日志 | prometheus-converter 默认开启 | disable_prometheus_converter: true |
| vmauth 转发 backend unavailable | svc 名写成 helm 后缀式 | 全部改前缀式 组件-<CR名> |
| vmauth Pod 卡 ContainerCreating | Secret key 错 | key 必须是 config.yaml |
| AM 启动即退出 | 配了 VM 专属的 loggerTimezone | 删掉,TZ env 足够 |
| 换存储类不生效 | sts volumeClaimTemplates 不可变 | 删 CR 加 PVC 重建 |
| Prometheus 加 remoteWrite 后 Pod 没滚 | 配置变更走热加载 | 正常现象,直接验证数据 |
| vmalert Pod FailedCreate | ResourceQuota 要求边车带 limits | 配 configReloaderResources |
| 边车镜像拉不下来 | 默认 Docker Hub 无前缀 | operator chart 配 global.image.registry |
| AM 创建被拒 unmarshal error | tls_config 层级错 | 用 http_config.tls_config |
| remoteWrite 报 400 双路径 | URL 写全了 api/v1/write | 写到 /insert/0/prometheus 为止 |
| Grafana 查不到新链告警 | 上一个问题导致 ALERTS 没入库 | 同上修复后即恢复 |
| vmalert UI Alerts 页空(火狐) | 该版本 UI 与 Firefox 兼容性差 | 换 Chrome,或 AM UI / Grafana 大屏 |
| AM 自监控规则无数据 | ServiceMonitor 未 apply | 先 apply other/vmalertmanager/ 下的 ServiceMonitor |
| 脚本生成的 expr 带转义符 | 序列化默认写引号串 | 块标量写法 |
| 迁移规则后疑似双发 | 忘记先下线 dyck 侧旧规则 | 铁律:先下线再启用 |
八、注意事项
- 迁移规则防双发是最高优先级纪律。同一告警两条链同时发,值班群被打扰两遍,还会掩盖”到底哪条链在报”。
- vector(1) 测试规则是字面量,不查数据源。只能验收”评估到通知”这一段,datasource 连通性和 remoteWrite 入库要单独验证(看 Rules 页 lastError、查 VM 里 ALERTS 序列)。
- 同一个 global 字段,换 chart 先实测。victoria-metrics-alert 的 global 不生效要组件级显式配,victoria-metrics-operator 的 global 对边车生效。
- 测试与生产的存储层是两种管理形态(测试 operator、生产 helm),存储层变更不能两边照搬,告警链同构可以先行。