告警 Fingerprint 生成规则详解

一、什么是 Fingerprint?
Fingerprint(指纹) 是 Prometheus Alertmanager 中用于唯一标识告警的哈希值。它基于告警的标签(labels)生成,用于:
- 识别相同的告警
- 告警去重和收敛
- 告警状态跟踪
- 告警路由和分组
二、Fingerprint 生成规则
2.1 基本生成原理
Fingerprint 是通过对告警的所有标签(labels)进行哈希计算生成的。
生成公式:
fingerprint = hash(labels)
其中 labels 包括:
- 告警规则中定义的标签(如
alertname,severity,cluster等) - Prometheus 自动添加的标签(如
instance,job等) - 告警表达式中匹配到的所有标签
2.2 生成算法
Prometheus 使用 FNV-1a 哈希算法(64位)生成 fingerprint:
// Prometheus 内部实现(简化版)
func FingerprintFromLabels(labels Labels) Fingerprint {
hash := uint64(2166136261) // FNV offset basis
// 对标签进行排序(按标签名)
sortedLabels := sortLabels(labels)
// 对每个标签进行哈希
for _, label := range sortedLabels {
hash ^= uint64(label.Name[0])
hash *= 16777619 // FNV prime
for _, char := range label.Name {
hash ^= uint64(char)
hash *= 16777619
}
hash ^= uint64('=')
hash *= 16777619
for _, char := range label.Value {
hash ^= uint64(char)
hash *= 16777619
}
hash ^= uint64('\xff')
hash *= 16777619
}
return Fingerprint(hash)
}
2.3 关键特性
- 确定性:相同的标签组合总是生成相同的 fingerprint
- 唯一性:不同的标签组合(几乎)总是生成不同的 fingerprint
- 不可逆性:无法从 fingerprint 反推出原始标签
- 标签顺序无关:标签的排序不影响 fingerprint(内部会排序)
三、不同告警的 Fingerprint 是否一样?
3.1 答案:取决于标签是否相同
相同标签 = 相同 Fingerprint
不同标签 = 不同 Fingerprint
3.2 示例说明
示例 1:相同 Fingerprint(相同标签)
# 告警 1
labels:
alertname: HighCPU
instance: 172.20.96.84
cluster: A-K8S
severity: warning
# 告警 2(标签完全相同)
labels:
alertname: HighCPU
instance: 172.20.96.84
cluster: A-K8S
severity: warning
# 结果:fingerprint 相同 ✅
# 原因:所有标签值都相同
示例 2:不同 Fingerprint(不同标签)
# 告警 1
labels:
alertname: HighCPU
instance: 172.20.96.84
cluster: A-K8S
severity: warning
fingerprint: f9c94431a06dfc2f
# 告警 2(instance 不同)
labels:
alertname: HighCPU
instance: 172.20.47.55 # 不同!
cluster: A-K8S
severity: warning
fingerprint: a1b2c3d4e5f6g7h8 # 不同!
# 结果:fingerprint 不同 ❌
# 原因:instance 标签值不同
示例 3:不同 Fingerprint(标签数量不同)
# 告警 1
labels:
alertname: HighCPU
instance: 172.20.96.84
cluster: A-K8S
fingerprint: f9c94431a06dfc2f
# 告警 2(多了一个标签)
labels:
alertname: HighCPU
instance: 172.20.96.84
cluster: A-K8S
service: gch-cn-mlu # 新增标签
fingerprint: x1y2z3w4v5u6t7s8 # 不同!
# 结果:fingerprint 不同 ❌
# 原因:标签数量不同
四、实际场景分析
4.1 场景 1:同一问题的多次触发
问题:同一个节点多次触发相同的告警
# 时间 1: 13:50:20
labels:
alertname: NodeExporterDown
instance: 172.20.96.84
cluster: A-K8S
exporter_name: node_exporter
fingerprint: f9c94431a06dfc2f
# 时间 2: 13:51:20(1分钟后再次触发)
labels:
alertname: NodeExporterDown
instance: 172.20.96.84
cluster: A-K8S
exporter_name: node_exporter
fingerprint: f9c94431a06dfc2f # 相同!
# 结果:✅ Fingerprint 相同
# 说明:这是同一个告警的重复触发,Alertmanager 会进行去重
4.2 场景 2:不同节点的相同告警
问题:多个节点同时触发相同的告警类型
# 节点 1
labels:
alertname: NodeExporterDown
instance: 172.20.96.84
cluster: A-K8S
exporter_name: node_exporter
fingerprint: f9c94431a06dfc2f
# 节点 2
labels:
alertname: NodeExporterDown
instance: 172.20.47.55 # 不同节点
cluster: A-K8S
exporter_name: node_exporter
fingerprint: a1b2c3d4e5f6g7h8 # 不同!
# 结果:❌ Fingerprint 不同
# 说明:这是两个不同的告警实例,会被分组但不会合并
4.3 场景 3:告警状态变化
问题:告警从 firing 变为 resolved
# Firing 状态
labels:
alertname: HighCPU
instance: 172.20.96.84
cluster: A-K8S
severity: warning
alertStatus: firing
fingerprint: f9c94431a06dfc2f
# Resolved 状态(标签相同)
labels:
alertname: HighCPU
instance: 172.20.96.84
cluster: A-K8S
severity: warning
alertStatus: resolved
fingerprint: f9c94431a06dfc2f # 相同!
# 结果:✅ Fingerprint 相同
# 说明:告警状态(firing/resolved)不影响 fingerprint
# 注意:alertStatus 不是标签,是告警的状态字段
五、影响 Fingerprint 的因素
5.1 会影响 Fingerprint 的因素
✅ 会影响:
- 标签名称(label name)
- 标签值(label value)
- 标签数量
- 标签值的任何变化(包括大小写、空格等)
5.2 不会影响 Fingerprint 的因素
❌ 不会影响:
- 告警状态(firing/resolved)
- 告警时间戳
- 告警描述(annotations)
- 告警生成时间
- 标签的原始顺序(内部会排序)
六、如何查看和验证 Fingerprint
6.1 通过 Prometheus API 查看
# 查看所有活跃告警
curl http://prometheus:9090/api/v1/alerts
# 查看特定告警的 fingerprint
curl http://prometheus:9090/api/v1/alerts | jq '.data.alerts[] | {fingerprint, labels}'
6.2 通过 Alertmanager API 查看
# 查看所有告警(包含 fingerprint)
curl http://alertmanager:9093/api/v2/alerts
# 查看特定告警组
curl http://alertmanager:9093/api/v2/alerts/groups
6.3 使用 PromQL 查询
# 查看告警的标签(用于理解 fingerprint 生成)
ALERTS{alertname="NodeExporterDown"}
# 查看告警状态
ALERTS{alertstate="firing"}
6.4 计算 Fingerprint(Python 示例)
import hashlib
def calculate_fingerprint(labels):
"""
计算告警的 fingerprint(简化版,实际使用 FNV-1a)
"""
# 对标签进行排序
sorted_labels = sorted(labels.items())
# 构建标签字符串
label_str = ','.join([f"{k}={v}" for k, v in sorted_labels])
# 使用 MD5 计算(实际 Prometheus 使用 FNV-1a)
hash_obj = hashlib.md5(label_str.encode())
fingerprint = hash_obj.hexdigest()[:16] # 取前16位
return fingerprint
# 示例
labels1 = {
'alertname': 'HighCPU',
'instance': '172.20.96.84',
'cluster': 'A-K8S',
'severity': 'warning'
}
labels2 = {
'alertname': 'HighCPU',
'instance': '172.20.96.84',
'cluster': 'A-K8S',
'severity': 'warning'
}
print(f"Labels1 fingerprint: {calculate_fingerprint(labels1)}")
print(f"Labels2 fingerprint: {calculate_fingerprint(labels2)}")
# 输出:相同(因为标签相同)
七、Fingerprint 在告警收敛中的作用
7.1 告警分组(Grouping)
Alertmanager 使用 fingerprint 进行告警分组:
# Alertmanager 配置
route:
group_by: ['alertname', 'cluster'] # 按这些标签分组
group_wait: 10s
group_interval: 10s
分组逻辑:
- 相同
group_by标签的告警会被分到同一组 - 但每个告警实例的 fingerprint 仍然不同
- 同一组内的告警会被合并发送
7.2 告警去重(Deduplication)
# 场景:同一个告警在短时间内多次触发
告警 1 (13:50:20):
fingerprint: f9c94431a06dfc2f
status: firing
告警 2 (13:50:25): # 5秒后再次触发
fingerprint: f9c94431a06dfc2f # 相同!
status: firing
# Alertmanager 行为:
# - 识别出 fingerprint 相同
# - 只发送一次告警通知
# - 更新告警时间戳
7.3 告警抑制(Inhibition)
# 抑制规则
inhibit_rules:
- source_match:
alertname: NodeDown
target_match:
alertname: PodDown
equal: ['instance']
# 当 NodeDown 告警触发时:
# - 查找所有 PodDown 告警
# - 比较标签(特别是 instance)
# - 抑制相同 instance 的 PodDown 告警
# - 使用 fingerprint 进行匹配
八、常见问题和注意事项
8.1 问题 1:为什么相同告警的 fingerprint 不同?
可能原因:
- 标签值有细微差异(空格、大小写等)
- 标签数量不同
- 标签名称拼写错误
- 动态标签值变化(如时间戳)
解决方案:
# 对比两个告警的标签
curl http://alertmanager:9093/api/v2/alerts | \
jq '.data[] | select(.fingerprint=="f9c94431a06dfc2f") | .labels'
# 检查标签差异
diff <(echo $labels1) <(echo $labels2)
8.2 问题 2:如何确保相同问题的告警有相同的 fingerprint?
最佳实践:
- 标准化标签:使用统一的标签命名规范
- 避免动态标签:不要在标签中使用时间戳、随机值等
- 标签清理:使用
labeldrop或labelkeep清理不需要的标签
# Prometheus 告警规则示例
groups:
- name: example
rules:
- alert: HighCPU
expr: cpu_usage > 80
labels:
alertname: HighCPU # 固定标签
severity: warning # 固定标签
cluster: A-K8S # 固定标签
annotations:
summary: "CPU usage is high"
# 注意:不要在 labels 中使用动态值
8.3 问题 3:Fingerprint 冲突(碰撞)
理论:FNV-1a 是 64 位哈希,理论上可能发生碰撞,但概率极低。
实际情况:
- 64 位哈希空间:18,446,744,073,709,551,616 种可能
- 碰撞概率:在实际告警场景中几乎不可能发生
如果发生碰撞:
- 两个不同的告警会有相同的 fingerprint
- 可能导致告警被错误地合并或抑制
- 需要检查标签是否真的不同
九、实际案例:你的告警分析
根据你提供的告警信息:
{
"fingerprint": "f9c94431a06dfc2f",
"alertStatus": "firing",
"cluster": "A-K8S",
"hostname": "node-96-084",
"exporter_name": "node_exporter",
"description": "集群:A-K8S,节点:172.20.96.84,服务:***,异常信息:计算节点监控组件异常"
}
9.1 生成 Fingerprint 的标签可能包括:
labels:
alertname: NodeExporterDown # 或其他告警名称
cluster: A-K8S
instance: 172.20.96.84
hostname: node-96-084
exporter_name: node_exporter
job: node_exporter
# 可能还有其他标签...
9.2 相同 Fingerprint 的情况:
✅ 会相同:
- 同一个节点(172.20.96.84)再次触发相同告警
- 告警状态从 firing 变为 resolved(标签不变)
- 告警在短时间内重复触发
9.3 不同 Fingerprint 的情况:
❌ 会不同:
- 不同节点触发相同告警(instance 不同)
- 不同集群触发相同告警(cluster 不同)
- 不同服务触发相同告警(service 标签不同)
- 告警规则名称不同(alertname 不同)
十、总结
10.1 核心要点
- Fingerprint 基于标签生成:所有标签的哈希值
- 相同标签 = 相同 Fingerprint:用于告警去重
- 不同标签 = 不同 Fingerprint:用于区分不同告警实例
- 标签顺序无关:内部会自动排序
- 状态不影响 Fingerprint:firing/resolved 不影响计算
10.2 实际应用
- ✅ 告警去重:相同 fingerprint 的告警会被去重
- ✅ 告警分组:配合
group_by进行告警分组 - ✅ 告警抑制:使用 fingerprint 匹配抑制规则
- ✅ 告警跟踪:通过 fingerprint 跟踪告警生命周期
10.3 最佳实践
- 标准化标签:使用统一的标签命名
- 避免动态标签:不要在标签中使用时间戳等动态值
- 合理使用 group_by:根据业务需求设置分组策略
- 监控 fingerprint:定期检查是否有异常
附录:相关工具和命令
查看告警 Fingerprint
# 使用 amtool(Alertmanager 工具)
amtool alert query --alertmanager.url=http://alertmanager:9093
# 使用 curl + jq
curl -s http://alertmanager:9093/api/v2/alerts | \
jq -r '.[] | "\(.fingerprint) \(.labels.alertname) \(.labels.instance)"'
# 使用 Prometheus API
curl -s http://prometheus:9090/api/v1/alerts | \
jq '.data.alerts[] | {fingerprint, labels}'
计算 Fingerprint(Go 语言)
package main
import (
"fmt"
"github.com/prometheus/common/model"
)
func main() {
labels := model.LabelSet{
"alertname": "HighCPU",
"instance": "172.20.96.84",
"cluster": "A-K8S",
"severity": "warning",
}
fingerprint := labels.Fingerprint()
fmt.Printf("Fingerprint: %d\n", fingerprint)
fmt.Printf("Fingerprint (hex): %x\n", fingerprint)
}
参考文档:
963

被折叠的 条评论
为什么被折叠?



