如果你也是按节奏在 Kubernetes 上交付 Dubbo 服务,前三天我们多半已经处理了集群基础、注册中心、配置中心和服务编排,到了第四天,真正躲不开的一个环节就是监控交付。
这篇文章是这个系列的第二天,但核心不变:把一个能真正帮到你排查问题的 Dubbo 监控体系落到 K8s 里。不是装一个 Grafana 看个心跳图就完事,而是要让 Dubbo 的接口级别指标、Pod 实例状态、线程池压力和告警链路全部打通,并且所有配置都能被后面的交付复制。适合已经完成服务部署、想补齐监控的交付同学,也适合刚接触 K8s 监控的新手从零理解为什么要这样搭。
1. 交付 Dubbo 监控前,先把方案骨架搭起来
1.1 监控组件怎么选:一条链路解决三件事
很多人一想到监控,第一反应是“先装个 Grafana”,这个顺序其实容易翻车。Grafana 只是展示层,真正决定监控是否好用的是指标从哪来、如何采集、如何触发告警。我的选择很固定:Prometheus 负责采集和存储,Grafana 负责展示,AlertManager 负责通知。这三个组件在 K8s 生态里配合最成熟,和 Dubbo 的指标暴露方式也天然兼容。
Dubbo 本身支持将运行时指标导出为 Prometheus 格式,这意味着不需要在业务代码里埋太多自定义埋点,只要打开指标开关,服务就会在某个端口吐出类似dubbo_provider_requests_total这样的数据。Prometheus 按“拉取”模式工作,会定期主动访问这些端口。相比让服务自己把数据“推”到监控平台,拉取模式在 K8s 这种实例随时增减的环境里更可靠:新 Pod 起来后,只要你给它打上正确标签,Prometheus 就能自动发现;实例下线后,目标自动消失,不用担心告警误报。
你可能也会看到有人用老式的“Dubbo Monitor 中心”,它靠服务把统计数据上报到中心,再由中心存库展示。这个方案在固定机房里能用,但到了 K8s 上,服务实例动不动就扩容缩容,上报链路要维护的点太多,数据也存在单点库风险。我的观点很直接:除非你还在维护古董版本,否则这次交付就把监控底座统一到 Prometheus 体系上来,省掉后面一堆中间层。
1.2 监控到底该看哪些指标
先想清楚看什么,再动手配,否则告警规则无从写起。对 Dubbo 服务来说,核心是四类指标:
- 请求量:每个 provider 服务的 RPC 调用量,通常按接口或方法维度区分,能帮你判断业务流量是否上涨、某条链路是否被大量调用。
- 延迟:Dubbo 调用耗时,尤其要看 P99、P95,而不是只看平均值。平均值会被慢调用拉平,一条超时链路可能被其他正常请求掩盖。
- 成功率:错误请求占总请求的比例。这个指标比告警“进程挂没挂”更灵敏,因为进程存活时业务可能已经在大量报错。
- 饱和度:线程池活跃数、等待队列大小、IO 使用率。Dubbo 服务崩溃之前,往往先出现线程池打满、请求排队。
这四类指标不是平级的。请求量是基础,延迟和成功率决定用户体验,饱和度决定系统还能扛多久。我习惯在交付时先让这四个维度都裸奔可看,再根据业务诉求订告警阈值,而不是一上来就堆几十个面板。
1.3 采集链路全景:从 Metrics 到 Grafana 面板
整条链路并不复杂,脑子里要装下这样一条路径:
每个 Dubbo 服务通过 Spring Boot 或独立 RPC 启动后,暴露/metrics端点;K8s 里每个提供 RPC 的服务都有一个 Service,Prometheus 通过 ServiceMonitor 找到符合条件的 Service,进而拿到背后的 Pod 地址;抓回来的指标存进 Prometheus,规则引擎按预定义表达式生成告警并推给 AlertManager;Grafana 从 Prometheus 查数据并渲染面板。
这条路径里最容易出问题的点在“ServiceMonitor 能否匹配到正确的 Service”这一步。K8s 中 Pod IP 是动态的,Prometheus 不能配置一个固定的 target 列表,必须通过标签选择器动态发现。所以交付监控的本质工作,是把服务、ServiceMonitor、Prometheus 三者用标签串起来。
2. 让 Dubbo 服务先把指标吐出来
2.1 在服务里开启 Metrics
我遇到不少案例,服务部署得漂漂亮亮,最后监控面板一片空白,原因很简单:应用根本没开指标暴露端口。所以交付监控的第一件事,不是搭 Prometheus,而是回服务代码里把 Dubbo Metrics 打开。
以 Dubbo 3.x 为例,引入依赖并开启配置后,服务就会在一个独立端口或 RPC 端口旁暴露指标。以下是我在项目中常用的一种配置方式:
<dependency> <groupId>org.apache.dubbo</groupId> <artifactId>dubbo-metrics-prometheus</artifactId> </dependency>对应的 application.yml 配置大致是:
dubbo: application: name: order-provider metrics: enable: true prometheus: enable: true port: 20880不同 Dubbo 版本里的配置项名称可能有差异,有的版本用protocol里的 exporter 配置,有的版本通过dubbo.metrics.port指定端口。我的建议是不要照搬这一段,先去 Dubbo 官方文档确认你那个版本是否支持指标导出,如果支持,直接访问/metrics能看到 Prometheus 格式的指标即可。
这里有一个容易踩的坑:很多人把 metrics 端口和 RPC 端口混在一起。理论上可以,但我建议单独开一个端口,比如 20880 继续做 RPC,指标端口用 20881 或者干脆用独立的管理端口。原因很简单:Prometheus 的访问负载和业务 RPC 流量分开后,不会因为指标抓取影响线上调用;另外告警时排查问题也更清楚,看到某个端口流量异常,能立刻判断是业务流量还是监控流量。
2.2 用注解把采集目标“钉”到工作负载上
Dubbo 服务部署到 K8s 后,Prometheus 需要知道从哪里抓数据。最常见的一种方式是在 Service 上打注解,Prometheus 自带的prometheus.io自动发现规则会去读取这组注解。
我实际使用的 Service 配置类似下面这样:
apiVersion: v1 kind: Service metadata: name: order-provider namespace: mall annotations: prometheus.io/scrape: "true" prometheus.io/path: "/metrics" prometheus.io/port: "20881" labels: app: order-provider component: dubbo-provider spec: selector: app: order-provider ports: - name: dubbo port: 20880 targetPort: 20880 - name: metrics port: 20881 targetPort: 20881注意prometheus.io/port是字符串,不是数字。这个取值必须和 Service 里的端口名或端口号对应。如果配成 20881,但 Pod 里指标端口实际是 8081,那 Prometheus 抓取就会超时。
注解方式适合小团队、监控基础设施还没上 Prometheus Operator 的场景。它简单直观,在 Service 上加了注解,Prometheus 就能靠自带的 Kubernetes 服务发现机制找到目标。缺点是每个服务都要手动管这组注解,服务一多容易漏,而且注解只解决“发现”,不解决“规则”。
2.3 ServiceMonitor 绑定:动态发现还是直接抓取,取决于你的运维模式
如果你们的 K8s 环境已经用 Prometheus Operator,我更推荐用 ServiceMonitor。ServiceMonitor 是一个自定义资源,专门用来描述“Prometheus 应该抓哪些 Service 的哪些端口”。它比注解更工程化:所有监控对象的定义都是 YAML,可以做版本管理,也可以批量生成。
下面是一个能直接套用的 ServiceMonitor 示例:
apiVersion: monitoring.coreos.com/v1 kind: ServiceMonitor metadata: name: dubbo-provider-monitor namespace: mall labels: team: delivery spec: selector: matchLabels: component: dubbo-provider namespaceSelector: matchNames: - mall endpoints: - port: metrics path: /metrics interval: 30s这段配置告诉 Prometheus:在mall这个命名空间里,找出所有带component: dubbo-provider标签的 Service,然后抓取它们名为metrics的端口。
这里有个很容易被忽视的点:ServiceMonitor 的selector匹配的是 Service 的标签,不是 Pod 的标签。我把服务全都打上component: dubbo-provider,而不是具体应用名,就是为了让后续新增服务时能自动被监控覆盖。如果你用应用名做标签,比如app: order-provider,那每新增一个服务都要再写一个 ServiceMonitor,维护成本立刻上来了。
Prometheus 实例本身还要配置serviceMonitorSelector,也就是只认带某些标签的 ServiceMonitor。这个机制相当于给监控对象做了一个白名单。假如你有多个团队共用同一个 Prometheus,各团队自己维护的 ServiceMonitor 都会被采集,除非通过命名空间隔离或标签隔离。我习惯在所有 ServiceMonitor 上打team标签,再让 Prometheus 只选自己需要的那部分。
3. Prometheus 与告警规则的一次性交付
3.1 一个最小的 Prometheus 落地方案
如果你的环境还没有 Prometheus,我通常建议直接基于社区公开的 kube-prometheus-stack 来部署,它能一次带来 Prometheus、AlertManager、Grafana 以及一堆 Kubernetes 基础监控规则。就交付 Dubbo 服务这个目标而言,不需要从头写一堆 Operator,只要保证最终有一个可用的 Prometheus 实例即可。
部署完成后,先确认 Prometheus 能访问 ServiceMonitor。假如 Prometheus 部署在monitoring命名空间,ServiceMonitor 在mall命名空间,则需要让 Prometheus 的serviceMonitorNamespaceSelector允许跨命名空间选择。这个配置我见过不少人漏掉,表现就是 Prometheus 的 Targets 页面里一直没有任何 target,而 ServiceMonitor 本身却明明被创建成功。
一个最小可用的 Prometheus 自定义资源配置大概是这个样子:
apiVersion: monitoring.coreos.com/v1 kind: Prometheus metadata: name: prometheus namespace: monitoring spec: serviceAccountName: prometheus serviceMonitorSelector: matchLabels: team: delivery serviceMonitorNamespaceSelector: matchNames: - mall - monitoring resources: requests: memory: 2Gi limits: memory: 4Gi抓取策略里,interval我建议不要设太短。Dubbo 接口级别的指标变化通常以秒为粒度,30 秒一次已经足够。如果设成 5 秒一次,几十个服务同时抓取,Prometheus 自身的 CPU 和内存会涨得很快,但带来的监控精度提升并不明显。资源有限的情况下,宁可延长抓取间隔,也要保证规则和查询性能稳定。
3.2 告警规则怎么写才不“狼来了”
写告警规则是最见功力的地方。规则写得太粗,比如请求失败率超过 5% 就告警,在低峰期可能根本触发不了,高峰期又因为阈值过低疯狂报警。我一般按服务重要程度分层:核心交易链路的接口告警阈值更严格,周边服务的阈值放宽。
还要注意告警表达式的分母。错误率计算必须用一段时间窗口内的错误请求数除以总请求数,不能直接查瞬时值,否则一次抖动就可能造成误报。
以下是一个典型的 Dubbo 服务错误率告警规则示例:
apiVersion: monitoring.coreos.com/v1 kind: PrometheusRule metadata: name: dubbo-rules namespace: monitoring spec: groups: - name: dubbo-provider.rules rules: - alert: DubboProviderHighErrorRate expr: | sum(rate(dubbo_provider_requests_total{status="error"}[5m])) by (service, interface) / sum(rate(dubbo_provider_requests_total[5m])) by (service, interface) > 0.05 for: 5m labels: severity: page annotations: summary: "Dubbo provider {{ $labels.service }} error rate over 5%" description: "Interface {{ $labels.interface }} error rate exceeds threshold."注意dubbo_provider_requests_total这个指标名不一定和你们环境完全一致。不同 Dubbo 版本的指标命名存在差异,我每次交付时都会先到/metrics端点确认一下实际指标名,再套到规则里。拿一个不存在的指标写规则,Prometheus 不会报错,但规则永远不触发,这类问题最难排查。
告警表达式里for: 5m的含义是“持续五分钟才触发”。这个参数是抗抖动的关键。没有它,一次瞬时网络抖动就会让告警轰炸你这个小时剩余时间。
3.3 AlertManager 通知链路
告警规则生成后,最终要有人真正看到。AlertManager 支持邮件、企业微信、钉钉、Slack 和通用 Webhook。我习惯用 Webhook 中转,因为告警既要发给运维群,还要同步给相关开发,一个通用 Webhook 比较灵活。
AlertManager 的配置核心是路由。同一套规则的不同 severity,应路由到不同接收人。比如severity=page的告警要立即通知值班群,severity=warning只进入告警记录,不打扰人。如果你把所有告警都捅给同一个群,很快这个群就会被淹没,真正重要的告警反而没人看。
有一点值得提醒:AlertManager 本身也是一个需要交付在 K8s 里的组件。如果部署的命名空间和应用不同,要注意网络策略。否则规则触发后告警进不了 AlertManager,排查时又得多绕一个弯。
4. Grafana 面板:把监控数字变成可读的业务信号
4.1 数据源与看板配置
Grafana 配数据源很简单,重点在于数据源地址别写错。Prometheus 实例如果是通过 Operator 部署的,通常会有一个名为prometheus-operated的 Service,Grafana 的数据源地址应填这个 Service 的完整域名。
我推荐用 Grafana Provisioning 来管理数据源和看板,而不是每次都在页面上手工操作。Provisioning 的好处是可以把配置放进 Git,整个监控交付随之可复现。
一份数据源配置可以写成:
apiVersion: v1 kind: ConfigMap metadata: name: grafana-datasources namespace: monitoring data: datasources.yaml: | apiVersion: 1 datasources: - name: Prometheus type: prometheus access: proxy url: http://prometheus-operated.monitoring:9090看板的 JSON 也可以放进 ConfigMap,配合 Grafana 的 dashboard sidecar 自动加载。如果你暂时不想折腾 Provisioning,直接在 Grafana 页面导入 JSON 也能用,只是后续变更要靠手动复制,时间一长容易漂移。
4.2 四个面板先安排上
新交付的 Dubbo 监控,我建议第一版 Grafana 只放四个面板,不要贪多。
第一个面板是 QPS,按 Dubbo 接口维度展示每秒调用次数。这个面板能回答“当前服务是否真的有流量,流量是否撑起来了”。第二个面板是延迟分布,重点看 P99 和 P95。第三个面板是成功率,按服务或接口聚合展示错误率。第四个面板是线程池活跃度,这个最直观的展现就是 Dubbo 线程池 active count。
对应查询表达式可以类似这样:
sum(rate(dubbo_provider_requests_total[1m])) by (interface)延迟 P99 的查询则是:
histogram_quantile(0.99, sum(rate(dubbo_provider_duration_milliseconds_bucket[5m])) by (le, interface) )面板里的时间范围我建议默认 6 小时,不要设 1 小时,否则告警来了想看趋势时数据量太少。另外给面板加一列“当前值 vs 昨天同期”,这种对比比绝对值更能暴露出问题。
5. 交付过程中常见的坑与排查思路
5.1 最常见的问题速查
我整理了一份自己交付时反复遇到的坑,做成速查表供你对照:
| 现象 | 可能原因 | 排查动作 |
|---|---|---|
| Prometheus Targets 页面没有目标 | ServiceMonitor 没被 Prometheus 选中 | 检查 Prometheus 的 serviceMonitorSelector 和 serviceMonitorNamespaceSelector |
| 目标显示 down | 端口号或路径不对 | 进 Pod 执行 curl 访问 /metrics |
| 面板部分服务有数据、部分没数据 | Service 标签没匹配全 | 确认 Service 的 labels 是否包含 ServiceMonitor selector 所需标签 |
| 有指标但告警不触发 | 指标名写错或阈值太高 | 到 Prometheus 查询表达式,看结果是否一直为 0 |
| 告警总在半夜误报 | 阈值没有按流量基线划分 | 调高 for 时长,或按 service 拆分规则 |
| Prometheus 内存涨得快 | 抓取间隔太短、指标基数太大 | 延长 interval,排查是否需要减少高基数标签 |
5.2 一套排查顺序值得背下来
监控面板没数据的排查顺序,我建议固定下来。按这个顺序走,你很少会绕远路。
先看 Prometheus 的 Targets 页面,确认目标状态是 up 还是 down。如果是 down,问题大概率出在采集配置;如果目标根本不存在,问题大概率出在 ServiceMonitor 匹配或注解。第二步直接进到 Pod 内部,用curl访问指标地址,验证应用确实已经把指标吐出来。第三步检查 Service 的标签和 ServiceMonitor 的 selector 是否对上,这里最容易犯一个低级错误:标签键写对了、值写错了。第四步查 Prometheus 的 serviceMonitorSelector,确认它认这个 ServiceMonitor。最后再去 Grafana 里看面板查询是否有语法错误。
这套顺序的核心逻辑是从“数据是否存在”到“数据是否被发现”,再到“数据是否能查询”。跳过前两步直接改 Grafana 查询,常常是在为一个不存在的数据源浪费时间。
5.3 指标缺失时,先分“采集问题”还是“指标源问题”
接口级指标偶尔会丢一部分,比如 80% 的服务有请求量数据,剩下 20% 始终没有。遇到这种情况,不要急着改全局配置。先在 Prometheus 里查询出确实有数据的服务,再和缺数据的服务对照,一般差异出在两类:
一类是采集问题,比如某些 Pod 被调度到了没有网络能的节点,或 Service 端口没有额外指定 metrics 端口。另一类是指标源问题,比如这套服务用的 Dubbo 版本里,部分接口没有开启 metrics exporter。
我建议在交付时做一个小脚本,遍历所有 provider 的 Pod,逐一请求/metrics,把返回结果记录下来。这样能够快速确认采集链路是否通了。否则你连“是应用没吐数据”还是“Prometheus 没抓到”都分不清,排查就无从谈起。
6. 影响范围:监控“上线”后,交付流程能跟着改什么
监控交付完成后,它不只是多了一个可视化工具,而是直接影响后续整个交付工作的判断方式。
首先是发布决策。以前我们靠“看起来还行”来判断服务是否可以放量,现在可以直接看 QPS 和错误率曲线:如果新版本上线后错误率没上升、P99 延迟没有明显变长,就有底气继续推进下一步。相反,如果看到一个接口的错误率在渐进上涨,就要立刻回滚。
其次是容量规划。Dubbo 服务在 K8s 上扩容很容易,问题是什么时候扩。以前等到用户投诉才发现线程池打满,现在通过线程池活跃数、请求排队时长,可以在流量还没完全进入之前预判资源缺口。
第三是跨团队协作。接口级监控数据和告警一旦接入统一告警平台,业务研发、运维、平台三方看的是同一套指标。告警发生时责任边界更清晰,不需要反复截图解释“这个指标是什么意思”。
从交付视角看,监控不应该排在所有服务上线之后,而应该作为交付的最后一道验收关卡。服务上线不代表交付完成,监控数据和告警链路都验证通过,才是一个可交付的状态。
最后聊一点个人体会。我刚开始做这类交付时,最常犯的毛病是总想一次性把所有面板、所有告警、所有服务都配齐,结果配置一多,哪里出错都看不出来。后来我养成一个习惯:每次交付只先接通一个服务的完整链路,从 metrics 到 Prometheus 再到 Grafana 面板,全部验证通过后再批量铺开。这个笨办法看着慢,实际上最稳。毕竟监控这个东西一旦没配好,它不像业务漏洞那么容易暴露,等到真出问题时,你会发现你看不见问题,那才是真正的问题。