当你的 Prometheus 内存一路飙升、查询响应从秒级变成分钟级,你大概率遇到了高基数(High Cardinality)问题。很多新手第一次听说“基数”这个词时一脸懵——简单说,一个指标的时间序列数量由它的标签组合决定。比如 http_requests_total{method="GET", endpoint="/api", status="200"} 就是一个系列。如果 endpoint 的值来自 URL 中的用户 ID(如 /user/12345),每多一个用户就多一个系列,几百个用户瞬间变成几万个系列——这就是高基数。
Prometheus 是为高基数而设计的?其实不是。它的内存使用与时间序列数量近似线性增长,单机通常只能稳定承载几百万个活跃序列。一旦基数失控,内存和查询性能会迅速恶化。很多团队的第一反应是“加机器”,但更优雅的解法是 relabeling——在数据进入存储之前就精简掉不必要的标签。
本文不讲复杂理论,直接解释 relabeling 是什么、为什么能解决高基数、以及怎么落地。你会看到可执行的配置,包含验证和回滚步骤,避免一改就崩。
1. 高基数到底“高”在哪
容易忽略的细节
先看一个真实场景:你用了 kube-state-metrics 采集 Kubernetes 集群状态,指标 kube_pod_info 会带上 pod、namespace、node、uid 等标签。如果每个 pod 都有一个唯一的 uid,那么每个新 pod 都会新增一个时间序列。假设集群有 200 个 namespace、每个 namespace 平均 50 个 pod,仅 kube_pod_info 就会产生 10000 个系列。如果还有 kube_pod_labels 这类自带自定义标签的指标,基数可能再乘 10 倍。
更危险的是那些“业务 label”,比如 user_id、session_id、request_id。这些值每次请求都不同,如果被采集进 Prometheus,几小时内就能把存储撑爆。
高基数的后果:
- 内存暴涨:每个系列需要大约 2-3KB 内存记录元数据和最近样本,100 万个系列就吃掉 2-3GB。
- 查询缓慢:PromQL 的聚合操作需要遍历大量系列,CPU 和磁盘 IO 飙升。
- 告警滞后:规则评估周期变长,重要告警可能延迟几分钟。
进阶阅读:此处可内链到“High性能优化”指南。
2. Relabeling 到底是什么
先看关键判断
Relabeling 是 Prometheus 在采集(scrape)前后对目标标签和目标指标进行改写、丢弃或重命名的一组规则。它发生在两个阶段:
- Scrape 阶段:在抓取目标之前,针对目标的元标签(以
__开头的)和最终标签进行处理。常用于服务发现后,根据元信息决定是否采集、如何打标签。 - Metric 阶段:在抓取到样本数据之后,针对指标名称和标签进行修改。这才是精简基数的主战场。
Relabeling 规则写在 scrape_configs 中的 relabel_configs 和 metric_relabel_configs 里。
核心动作:
- replace:修改标签值或创建新标签(默认动作)。
- keep:仅保留符合匹配条件的目标或指标。
- drop:丢弃符合匹配条件的目标或指标。
- labelmap:将匹配到的标签名映射为新的标签名(常用于保留元标签)。
- labeldrop:删除指定标签名。
- labelkeep:仅保留指定标签名,其余全部删除。
延伸阅读:此处可内链到“High配置案例”相关文章。
3. 哪几种 relabeling 能直接打掉高基数
对付高基数,常用三招:
3.1 丢弃整条指标(drop)
如果某个指标本身就带高基数标签,且它的数据你根本不需要——直接 drop 掉。比如 kube_pod_labels 中的 label_app_kubernetes_io_name 经常变化,但你只关心 namespace 级的聚合,完全可以 drop 这个指标:
metric_relabel_configs:
- source_labels: [__name__]
regex: 'kube_pod_labels'
action: drop
3.2 只保留必要标签(labelkeep)
有的指标不能丢,但它的标签太多了。比如 kube_pod_info 有 pod、namespace、node,其中 pod 是唯一的,但你可以换成保留 namespace 和 node,丢掉 pod:
metric_relabel_configs:
- regex: 'namespace|node'
action: labelkeep
这样 kube_pod_info 每个 namespace 和 node 组合只保留一个系列,基数从 pod 数量降到 namespace × node 数量。
3.3 用 labelmap 拉平并保留必要的元标签
有些 exporter 会把 SD(服务发现)元信息带进指标,比如 __meta_kubernetes_pod_label_app。如果你只想保留其中几个自定义标签,可以用 labelmap 把它们映射为普通标签:
metric_relabel_configs:
- regex: '__meta_kubernetes_pod_label_(app|version)'
action: labelmap
replacement: '$1'
这样 __meta_kubernetes_pod_label_app 变成 app,而其他 __meta_kubernetes_pod_label_* 不会被保留,避免引入高基数。
4. 实战:用 relabeling 救活一个内存爆掉的 Prometheus——High
假设你的 Prometheus 已经 OOM 了。先别急着重启,按以下步骤走:
4.1 识别高基数元凶
用以下 PromQL 查看指标系列数排名:
topk(10, count by (__name__)({__name__=~".+"}))
如果你现在 Prometheus 连查询都打不开,可以直接去 TSDB 状态页 /tsdb-status 查看。
4.2 一次性临时止损:强制 drop 高基数指标
在 prometheus.yml 的全局 scrape_configs 里加一个 metric_relabel_configs,先 drop 掉那个指标。比如是 kube_pod_labels:
scrape_configs:
- job_name: 'kubernetes-pods'
kubernetes_sd_configs:
- role: pod
metric_relabel_configs:
- source_labels: [__name__]
regex: 'kube_pod_labels'
action: drop
# 其他配置保持不变
然后 reload Prometheus 配置:kill -HUP <pid> 或者用 API POST /-/reload。几分钟后内存应该会回落。
4.3 精细调整:保留必要标签
稳定之后,再精细化配置。比如你仍然需要 kube_pod_labels 中的 app 标签,那就用 labelkeep 只保留 app:
metric_relabel_configs:
- regex: '__name__|instance|job|app'
action: labelkeep
注意:__name__、instance、job 是 Prometheus 内置标签,如果你 labelkeep 时没写它们,这些标签会被删除——导致指标无法识别。所以务必保留至少 __name__、instance、job。
4.4 验证配置是否正确
使用 promtool check config prometheus.yml 检查语法。然后用 promtool test rules 测试告警规则是否还正常。更直观的方法是修改后观察 Prometheus target 页面(/targets)看 UP 状态,以及用 count by (__name__)({__name__=~"kube_pod_labels"}) 检查系列数是否降低。
5. 必须注意的风险与回滚与High
实际操作要点
Relabeling 是“外科手术”,切错了可能让关键监控失灵。
- 误删必要标签:上面提过,注意保留
__name__、job、instance。此外,如果告警规则依赖某个 label,千万别 drop 或 labelkeep 去掉它。 - 回滚方案:每次修改前备份
prometheus.yml。如果改动后问题扩大(比如所有 target 都 down了),立刻拿出备份文件并 reload。也可以用版本管理工具(如 git)记录每次变化。 - 增量验证:不要一次性对 所有 job 应用同一个 metric_relabel_configs。先在一个测试 job 上试,确认 OK 再推广。
- 防止空指标:如果你用 drop 动作,确保正则匹配精确,不要一棍子打死同类指标。例如
kube_pod_labels和kube_pod_info,别写成kube_pod.*然后误杀。
补充参考:此处可内链到“High故障排查实例”。
6. 总结:relabeling 是 Prometheus 高基数场景的标配技能
实际操作要点
不要迷信“加机器能解决一切”。不加控制的高基数即使扩到 128GB 内存也照样崩。学会用 metric_relabel_configs 中的 drop、labelkeep、labelmap,你可以从源头减少 80% 以上的时间序列。关键是先定位元凶,再逐步收紧策略,验证一步走一步。
对小白来说,最直接的上手路径:找到系列数最高的那个指标,在对应的 scrape job 里加一个 metric_relabel_configs,用 drop 先扔掉它(同时备份配置)。观察内存下降后,再改造成 labelkeep 只保留你真正需要的标签。
Prometheus 的 relabeling 机制并不难,难的是意识到“哪些数据可以丢”。下次再遇到内存告警,先查高基数指标,再动手 relabel——比你盲目加机器有效得多。把这些步骤跑通后,High基本就能稳定落地。
延伸阅读
