PromQL 实战指南:从指标类型到查询模板的云原生监控核心技能
做云原生监控的人最后都会回到同一个问题指标拿到了怎么问出想要的数据Prometheus 负责采集和存储真正把数据变成答案的是 PromQL。很多人学 PromQL 卡在“函数太多、语法记不住、写出来不生效”其实是因为没搞懂它背后的设计逻辑。本文不准备把文档抄一遍而是挑最常用的内置指标、内置函数和聚合操作结合云原生场景里的实际写法讲清楚每个查询为什么这么写以及我踩过的坑。1. 搞清楚 PromQL 的底层思维再动手查1.1 指标类型决定了查询姿势Prometheus 的数据模型核心是“时序”——每个指标由指标名加一组标签label唯一定义每个样本值都贴着时间戳。你查询的时候本质上是在问在一段时间内这些时序的样本值发生了什么变化。PromQL 最常见的四个指标类型决定了你能对它做什么操作类型含义典型场景常用函数Counter只增不减的计数器请求数、CPU 累计时间、网卡字节数rate、increase、irateGauge可增可减的瞬时值内存使用量、当前连接数、磁盘剩余avg、max、min、sum、topkHistogram分桶统计的柱状分布请求延迟、响应大小histogram_quantile、rateSummary客户端预计算的分位数请求延迟预聚合直接查 quantile 标签拿 Counter 举例。node_cpu_seconds_total 是经典的 Counter它记录的是从系统启动到现在 CPU 在 user、system、idle 等模式下累计消耗的秒数这个数字本身没什么可读性但用 rate 求每秒变化量之后就变成了“CPU 时间占比”。不少新手直接对这个指标画图看到的是只涨不跌的折线以为是数据错了其实是选错了操作。Gauge 就简单直接比如 node_memory_MemAvailable_bytes 查到的就是当前可用内存不需要做变化率计算直接画图或者做百分比运算都行。Histogram 和 Summary 稍复杂细节放在后面单独展开。1.2 标签label才是查询的灵魂PromQL 的过滤语法很容易记混淆尤其是 、!、~、!~ 这四个。我平时这么理解:精确等于字符串完全匹配!:精确不等于~:正则匹配常用于排除或批量选择!~:正则不匹配实际场景里~ 用得非常多。比如查物理机网络流量时通常要排除回环口和虚拟网卡可以这样写rate(node_network_receive_bytes_total{device!~lo|veth.*|docker.*}[5m])在 Kubernetes 环境里cadvisor 会为每个容器产生大量时序想查所有非 pause 容器的 CPU 使用率就用container!以及正则排除 pause 容器来做过滤。标签查询是 PromQL 的基础能力任何复杂的聚合都是在标签基础上完成的先学会用标签把数据范围圈对再考虑算子。另一个需要养成习惯的是Prometheus 内部会自动附加一些标签比如__name__表示指标名job表示抓取任务名instance表示目标地址。在告警规则里很多人会把 instance 透传到告警消息里但如果用了聚合instance 标签可能被去掉后续排查就会少一个关键维度。这个坑我在工作中见过不止一次。2. 内置指标与内置函数速查按类型拆开讲2.1 Counter 型指标的三大金刚rate、increase、iraterate计算区间内平均每秒变化率是处理 Counter 的第一选择它的标准写法是带上时间窗口rate(node_cpu_seconds_total[5m])方括号里是“区间向量”的范围表示取最近 5 分钟内的样本计算平均速率。这个 5m 的取值很有讲究一般要覆盖至少 2 到 4 个抓取周期。如果 Prometheus 每 15 秒抓一次5 分钟就是 20 个样本点平滑效果够用如果窗口太短比如 30s只有 2 个点哪怕一次轻微抖动也会让速率曲线看起来像锯齿。increase 表示区间内增加了多少。它和 rate 的关系是increase(x[5m]) rate(x[5m]) * 300但实际计算时 Prometheus 会对 Counter 的复位比如进程重启、机器重启做自动修正所以不是简单的首尾相减。看业务维度的增长量时比如“过去 5 分钟订单量”用 increase 更直观sum(increase(http_requests_total{path/api/order}[5m]))irate 是“瞬时速率”它只取区间内最后两个样本计算变化率反应快适合看短时间内的突发波动。但正因为它只看两个点曲线会非常不稳定不适合做告警和趋势判断。我的习惯是看毛刺用 irate看趋势用 rate告警一律 rate。2.2 Gauge 型指标的常规操作聚合与 topkGauge 型指标最常用的是聚合运算sum、avg、min、max、count、topk、bottomk。这里有三个容易踩的坑。第一聚合时如果不加 by会把所选标签全部合并成一个值。例如sum(node_memory_MemTotal_bytes)这个结果自然没问题。但如果同一台机器有多个 job 在抓取或者有重复抓取配置sum 会把重复量再加一遍导致数值虚高。所以我在做聚合时几乎总是带上 by明确按什么维度合并。第二topk 和 bottomk 不是聚合函数而是选择函数。它们不会改变标签集合只是筛选出 value 最大的 N 个时序。很多人以为 topk 会返回求和后的 TopN不是的它返回的就是原始时序。要按分组 TopN需要先聚合再 topk。例如按实例查 CPU 使用率最高的 3 台机器topk(3, 100 - (avg by (instance) (rate(node_cpu_seconds_total{modeidle}[5m])) * 100))第三Gauge 可以直接做四则运算。比如磁盘使用率、内存使用率本质就是两个 Gauge 相除不需要任何函数(node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes) * 100这里要注意当 MemTotal_bytes 这类分母为 0 或缺失时结果会出现 NaN 或者直接没有值。在 Grafana 里看到断线多半是分母在某些时间点没有样本。对此我习惯用 clamp_min 把结果限制在合理范围clamp_min((node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes) * 100, 0)2.3 Histogram/Summary 型指标quantile 和 histogram_quantileHistogram 是云原生监控里最能体现 PromQL 威力的指标类型。它把观测值按设定好的边界分桶存储比如http_request_duration_seconds_bucket{le0.1}就表示“耗时小于等于 0.1 秒的请求有多少个”。每个_bucket指标都是 Counter所以求一组桶的变化率再传给 histogram_quantile就能得到百分位数。计算 P95 耗时的标准写法histogram_quantile(0.95, sum by (le) (rate(http_request_duration_seconds_bucket[5m])))拆开看每一步rate(...[5m])是算每个桶每秒增量sum by (le)是按桶边界合并多个实例的数据最后用 histogram_quantile 从这批累计分布数据中估算出分位数。这里必须强调两个要点。第一histogram_quantile 是估算不是精确值。桶的数量和边界决定精度如果 le 的边界跨度太大P95 可能和真实值偏差明显。比如 0.1s 到 1s 之间没有中间桶那么算出的 P95 可能始终接近 1s。调桶时要在业务敏感区间多放几个桶。第二le 标签在聚合后必须保留。如果丢掉了 lehistogram_quantile 会直接报错因为无法还原累计分布。我见过不少同事写完sum(rate(...))后忘记按 le 分组然后一脸疑惑地看报错。Summary 类型则不用 histogram_quantile它已经分位数打在标签里比如http_request_duration_seconds{quantile0.95}直接查就行。Summary 的缺点是不能跨实例聚合所以多实例场景我一般选 Histogram。2.4 Prometheus 自身运行指标也要会看除了业务和机器指标Prometheus 自身也内置了一批指标在排查“为什么查询慢了”“为什么内存涨了”时非常有用。prometheus_tsdb_head_series当前内存中的时序数量用来判断基数压力。prometheus_engine_queries当前正在执行的查询数量太高说明并发查询堆积。prometheus_engine_query_duration_seconds查询耗时直方图配合histogram_quantile看 P99 查询延迟。scrape_duration_seconds每次抓取耗时。如果某个 target 耗时接近抓取间隔说明该 exporter 性能告急。up这个太常用了表示目标是否在线。up 0就是抓取失败是所有告警规则里最常见的判断条件。排查 Prometheus 自身问题时有一个实用组合查询topk(10, prometheus_tsdb_head_series - (prometheus_tsdb_head_series offset 5m))这能看出哪些指标的 series 数量在 5 分钟内增幅最大快速定位高基数源头。3. 云原生场景的常用查询模板与实操3.1 CPU 与内存监控从机器到容器物理机 CPU 使用率最经典的公式是拿 idle 模式的累计时间算变化率再用 100 减100 - (avg by (instance) (rate(node_cpu_seconds_total{modeidle}[5m])) * 100)必须选 idle 而不是算其他模式的总和因为 idle 模式占 CPU 全部核数的累计时间直接用 100 减去它的占比就得到整体使用率。如果用 user 和 system 相加会漏掉 iowait、steal 这些不可忽略的部分。容器场景配合 cadvisorCPU 使用率这样算sum by (pod) (rate(container_cpu_usage_seconds_total{container!}[5m]))这里为什么要container!因为 cadvisor 会给每个 pod 产生一个container的聚合层时序它把 Pod 内所有容器包括 pause的用量又加了一遍如果不排除会导致重复计算数值动不动就超过核数上限。内存方面机器层用node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes * 100容器层用 working set更贴近实际可回收内存sum by (pod) (container_memory_working_set_bytes{container!})一个实操经验判断容器是否内存超限不要直接拿 RSS 和 Limits 比而要用 working_set因为内核回收页、页缓存会算到 RSS 里但实际可回收不直接导致 OOM。3.2 网络与磁盘监控别漏过滤条件网络流量经典查询sum by (instance) (rate(node_network_receive_bytes_total[5m]))看到全量网卡数据很乱我通常会加过滤把回环口、虚拟网卡排除sum by (instance) (rate(node_network_receive_bytes_total{device!~lo|veth.*|docker.*|flannel.*}[5m]))不排除 veth 会导致容器网络占用机器网卡曲线的假高尤其是跑了大量 Pod 的节点。磁盘相关几个指标node_filesystem_avail_bytes是可用空间node_filesystem_size_bytes是总大小。按挂载点看使用率(node_filesystem_avail_bytes{mountpoint/} / node_filesystem_size_bytes{mountpoint/}) * 100想做提前量预测可以用内置函数 predict_linear它基于过去一段时间的变化趋势做线性回归预测。比如预测根分区 24 小时后会不会满predict_linear(node_filesystem_avail_bytes{mountpoint/}[6h], 86400) 0这个查询我一直在告警规则里用效果比固定阈值灵敏得多。3.3 Kubernetes 工作负载监控kube-state-metrics 的关键指标Kubernetes 场景离不开 kube-state-metrics 导出的内置指标。拿 Pod 状态举例kube_pod_status_phase{phaseRunning}它在每个 Pod 上都有当前阶段标签查非 Running 的 Pod 可以直接判断不稳定工作负载sum by (namespace, pod) (kube_pod_status_phase{phase!Running,namespace~$namespace})工作负载副本数更要细心。kube_deployment_status_replicas表示期望副本数kube_deployment_status_replicas_available表示可用副本数最常用的告警表达式kube_deployment_status_replicas_available / kube_deployment_status_replicas 1但直接相除会遇到一个问题分母为 0 的 Deployment比如副本数设为 0 的暂停部署会算出 Inf告警会误报。我一般改成(kube_deployment_status_replicas - kube_deployment_status_replicas_available) 0 and kube_deployment_status_replicas 0先过滤掉副本数为 0 的情况再判断是否有副本缺口。3.4 业务接口耗时与可用性监测业务监控最能体现 PromQL 的组合能力。比如某服务接口 P95 耗时超过 500ms 要告警histogram_quantile(0.95, sum by (le) (rate(http_request_duration_seconds_bucket{jobapi-server,handler/v1/order}[5m]))) 0.5接口成功率一般用 2xx/5xx 的关系sum by (service) (rate(http_requests_total{status~2..}[5m])) / sum by (service) (rate(http_requests_total[5m]))有人问如果业务没有按 status 区分只有总量和错误量怎么办那就查错误量和总量sum(rate(http_requests_total{status~5..}[5m])) / sum(rate(http_requests_total[5m]))这类表达式在生产环境很有用不过长期放在 Grafana 面板上可能成为性能负担建议用 Recording rules 提前聚合再查结果指标。4. 聚合运算、标签操作与向量匹配的进阶用法4.1 聚合操作符的使用细节PromQL 聚合操作符用by和without控制维度。一定记住by是“保留哪些标签去重聚合”without是“丢掉哪些标签后聚合”。两者最终效果是互补的。举个例子sum by (job) (...)和sum without (instance) (...)在只有 job、instance 两种标签时结果一致。但一旦你后面又 drop 了某些标签without 就可能导致聚合维度超出预期。我遇到过一次线上告警不收敛就是因为规则里用without (instance)结果指标里还有 pod 标签告警最终按 namespace pod 各发了一条直接刷屏。聚合时一个常用的细节是用count统计时序数量。比如发现某个组件的实例数变化count(up{jobapi-server}) by (job)还有group操作用来保证某个标签的组合一定存在常用于跨指标关联时做条件过滤在值班看板上能看到“有数据”还是“没有数据”很实用。4.2 二进制运算与向量匹配两个查询结果做四则运算时PromQL 不是简单按值算而是会做向量匹配。默认是一对一匹配Prometheus 会找相同标签集的时序进行运算。最常踩的坑出现在一对多、多对一场景。比如用 node_uname_info 的 label 关联 node_load1 按机房划分如果两侧标签不一致查询常常直接报 empty result。这时候需要on或ignoring来指定匹配键node_load1 * on(instance) group_left(datacenter) node_uname_infogroup_left(datacenter)表示左侧向量多保留一个右侧的 datacenter 标签。反过来如果左侧标签多用group_right。还有个细节bool修饰符。默认比较运算会做过滤例如node_load1 1它返回所有大于 1 的样本小于等于 1 的直接消失。如果想让比较结果直接输出 0 或 1 而不是过滤就加 boolnode_load1 bool 1告警规则里常用这种写法把多条件布尔结果合到一起。我见过有人不写 bool导致 Grafana 面板上符合条件的点本身就被过滤掉了图上断断续续排了半天才发现。4.3 标签操作label_replace 与 label_joinlabel_replace 是重度实用函数。它主要是做标签重写比如把 instance 里的 IP:端口拆出 IPlabel_replace(node_load1, ip, $1, instance, (.*):.*)这个函数语法有点绕四个参数分别是新标签名、替换模板、来源标签、来源标签的正则匹配。新的值会把模板里的 $1、$2 替换成正则匹配到的分组。我在多集群合并可视化的场景经常这么做两个集群的 job 名相同想在图上区分就加一个集群标签label_replace(up{clusterprod}, source, prod, __name__, .*)又如想设计“请求成功率”按 cluster 维度对比时可以先给不同集群打上 source 标签再用sum by (source)聚合。这样一来后期接入新集群就不需要改一堆复用查询模板。label_join 相对冷门它的作用是把多个标签拼成一个新标签。比如把 instance 和 job 拼起来用label_join(node_load1, id, _, job, instance)生成的新值类似node_exporter_192.168.1.1:9100很适合做图例展示。不过搞清楚它的存在比记住具体语法更有意义见到奇怪的标签拼接就能反应过来。5. 避坑指南与常见问题排查实录5.1 时间窗口选错了查询结果全失真PromQL 每个查询都会带一个区间向量方括号里的值决定了计算基于多长时间的数据。我见过最多的错误是选了过短窗口特别是查 rate 时使用[1m]如果抓取周期是 30 秒那数据点太少每次查询曲线的抖动都非常剧烈。经验法则[5m]在多数场景够用但同样一批指标如果底层抓取周期是 1 分钟窗口最好开到[10m]以上才稳定。在 Grafana 里直接使用模板变量$__rate_interval可以自动适配抓取周期省掉手改的麻烦rate(node_cpu_seconds_total[$__rate_interval])5.2 速率计算常见的理解误区Counter 重置是一个常见困惑点。比如一个进程重启后累计计数从大值归零rate 会怎么算Prometheus 内部会检测到这种回退自动补上差值所以不会出现负速率。看起来很简单但如果抓取周期内发生了多次重启或者 Counter 在两次抓取之间的增量超过它的类型上限就会计算出异常偏大的速率。这类场景不好排查我通常结合日志和重启时间一起看。deriv 和 delta 也是容易用错的函数。deriv 是计算 Gauge 在区间内的“每秒变化率”用的是线性回归不是纯粹首尾差delta 计算区间内变化量对重启用例也做了处理。举例监控内存增长速度可以用deriv(node_memory_MemAvailable_bytes[10m])这个值是负数说明可用内存在下降配合告警阈值可以提前预判 OOM 风险。5.3 分位数为什么总感觉不准确Histogram 桶边界设置不合理是 P95 不准的最大原因。曾经有一个服务HTTP 耗时桶只设了 0.01、0.1、1、10 四档P95 无论怎么调都在 1 秒上下跳动。真正耗时是 200ms 左右但中间没有桶Linear interpolation 只能落在大桶附近。解决方法是让桶在业务敏感区间足够密。如果接口耗时大多在 50ms 到 500ms 之间至少要配置 0.05、0.1、0.2、0.3、0.5 这几档。对于已经采集好的数据桶已经固定再改也要等数据重新积累所以这种问题越早发现越好。5.4 高基数High Cardinality风险PromQL 查询慢、内存飙升八成是高基数问题。基数指一个指标下的标签组合数量。比如 label 里带了 request_id、user_id 这种唯一值很高的标签series 数量直接爆炸Prometheus 内存也会跟着爆。定位高基数的常用手段已经提过即对比一段时间内 series 数量的变化。另外一个更直接的预防方法是告警指标和面板指标尽量少用业务侧高变化的标签只保留少量稳定维度。之前某团队把一个带container_id的指标用作 alert 的查询源结果每个容器重启都会生成一个新 series把 Prometheus 的存储和查询节奏全打乱了。5.5 常见报错信息速查报错信息原因解决方案parse error: unexpected character语法错误比如引号没闭合在 Prometheus 自带的表达式浏览器里逐段排查vector cannot contain metrics with the same labelset聚合时两个不同 series 得到相同标签集调整 by 或 without确保结果唯一Error executing query: invalid parameter函数参数类型不匹配检查是否把瞬时向量传给区间向量函数empty result after applying PromQL过滤条件太严格或时间范围无数据检查 [5m] 窗口、正则表达式、标签拼写[15m] invalid because no samples found窗口太短没有覆盖到样本扩大窗口到至少两倍抓取周期还有一个容易忽视的问题Grafana 面板查询时会自动加一层time和$__interval的参数如果 PromQL 里写死方括号窗口有时会和面板自动调整冲突。我建议在模板变量存在时尽量使用$__rate_interval和$__interval而不是写死 [5m]。6. 我平时怎么用 PromQL 排查线上问题最后分享一下我的固定排查顺序权当给大家一个参考。第一反应是看 up 和相关 exporter 指标确认采集链路是好的。如果某个服务挂了up 会变成 0但注意 up 只能代表“抓取成功”不代表“业务健康”。所以第二步马上看业务自身的 RED 指标Rate请求速率、Errors错误数、Duration耗时。这三个维度能快速定位“流量有没有异常、错误率有没有升高、响应有没有变慢”。第三步是关联资源指标。如果接口变慢顺手看 CPU、内存、磁盘 IO以及 GC 相关指标。很多时候问题不是业务代码而是底层资源出现瓶颈。比如 CPU 使用率接近 100%但请求量并没有明显上涨那大概率是 GC 线程或者某个死循环在消耗资源。最后再看历史趋势。用offset 7d可以对比上周同期数据比如查当前 5 分钟成功率对比上周(sum(rate(http_requests_total{status~2..}[5m] offset 7d)) / sum(rate(http_requests_total[5m] offset 7d)))这个写法对比同一指标时间偏移后的结果做“同比”很方便。夜间值班时看曲线突然掉下去我第一动作就是拉这个对比。PromQL 的学习曲线确实存在但真正用顺手的核心点就两个一是理解不同指标类型有不同操作方式二是熟练使用标签过滤和聚合控制维度。这几点掌握了大多数云原生监控需求都能覆盖。剩下的内置函数遇到具体需求再查就行不需要背诵。