整体流程概览:

告警(alerts)概念解析:
首先快速梳理一下 AlertManager 中告警的基本概念与状态。每条告警对应一个具体事件,包含名称、时间、状态,以及用户自定义的详细说明。其数据结构大致如下:
[{"startsAt": "2019-06-03T03:32:20.859Z","endsAt": "2019-06-03T03:32:20.859Z","annotations":{"additionalProp1": "string","additionalProp2": "string","additionalProp3": "string"},"labels":{"additionalProp1": "string","additionalProp2": "string","additionalProp3": "string"},"generatorURL": "string"}]
那么告警的唯一标识是如何生成的呢?系统会将 labels 下的所有项组合起来计算出一个指纹(fingerprint),因此可以认为 labels 集合本身就是唯一标识。合并告警、更新状态都依赖这个指纹来定位。相关参考代码如下:
// Fingerprint returns a unique hash for the alert. It is equivalent to
// the fingerprint of the alert's label set.
func (a *Alert) Fingerprint() Fingerprint {
return a.Labels.Fingerprint()
}
// labelSetToFingerprint works exactly as LabelsToSignature but takes a LabelSet as
// parameter (rather than a label map) and returns a Fingerprint.
func labelSetToFingerprint(ls LabelSet) Fingerprint {
if len(ls) == 0 {
return Fingerprint(emptyLabelSignature)
}
labelNames := make(LabelNames, 0, len(ls))
for labelName := range ls {
labelNames = append(labelNames, labelName)
}
sort.Sort(labelNames)
sum := hashNew()
for _, labelName := range labelNames {
sum = hashAdd(sum, string(labelName))
sum = hashAddByte(sum, SeparatorByte)
sum = hashAdd(sum, string(ls[labelName]))
sum = hashAddByte(sum, SeparatorByte)
}
return Fingerprint(sum)
}
告警的状态只有两种:firing(触发中)和 resolved(已解决)。该状态由 Manager 根据收到的告警结构自行判断。什么时候算 firing?当 endsAt 为空,或者当前时间小于等于 endsAt 时,视为告警仍在触发。什么时候算 resolved?当 endsAt 不为空且大于当前时间时,视为已解决。代码逻辑非常清晰:
// Resolved returns true iff the activity interval ended in the past.
func (a *Alert) Resolved() bool {
return a.ResolvedAt(time.Now())
}
// ResolvedAt returns true off the activity interval ended before
// the given timestamp.
func (a *Alert) ResolvedAt(ts time.Time) bool {
if a.EndsAt.IsZero() {
return false
}
return !a.EndsAt.After(ts)
}
路由(router)机制详解
路由可以看作是一棵树,借助它能够灵活配置告警的流转路径。根节点是 router,所有属性均可被子节点继承,因此根节点的属性相当于全局默认值。子节点 routers 可以有零个或多个,每个子节点都能独立配置属性来覆盖父节点。
每个 router 包含以下几类控制:
- 匹配规则:
match和match_re,前者做等值匹配,后者支持正则表达式,这是选择某个 router 的依据。 - 流程控制:
continue表示告警是否继续向下路由,若为 false,则终止于当前节点,不再往下传递。 - 分组规则:group 相关参数,用于告警聚合。
- 接收端:
receiver指定处理方式,例如发送邮件、Webhook 等。
分组(group)策略说明
分组操作的核心价值在于将同一类告警归集到一条通知中。最典型的场景是:当某个核心服务或组件发生故障,可能瞬间触发成百上千条同类型告警。此时分组聚合能大幅降低通知数量,保持告警信息的清晰与有效。分组涉及三个关键参数:
- group_by:指定按哪个标签(label)进行分组,多个标签用逗号分隔。
- group_wait:从第一个新分组创建开始,到实际发送告警之间的等待时长。系统会在此时间段内将同一分组的所有告警合并为一条再发送。
- group_interval:同一分组成功发送后,后续再有同组告警时,需等待此间隔时间后才能发送下一轮通知。
存储(storage)机制概览
AlertManager 不支持历史存储,仅保存告警快照。存储分为两部分:
(1)告警状态快照:只保存尚未恢复的告警,存储周期可配置,默认时长为 120 小时。底层数据结构为 map,key 格式为 GroupKey:r.GroupName,/r.Integration,/r.Idx,value 为 MeshEntry。具体结构如下:
type MeshEntry struct {
Entry *Entry `protobuf:"bytes,1,opt,name=entry,proto3" json:"entry,omitempty"`
ExpiresAt time.Time `protobuf:"bytes,2,opt,name=expires_at,json=expiresAt,proto3,stdtime" json:"expires_at"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
type Entry struct {
GroupKey []byte `protobuf:"bytes,1,opt,name=group_key,json=groupKey,proto3" json:"group_key,omitempty"`
Receiver *Receiver `protobuf:"bytes,2,opt,name=receiver,proto3" json:"receiver,omitempty"`
GroupHash []byte `protobuf:"bytes,3,opt,name=group_hash,json=groupHash,proto3" json:"group_hash,omitempty"`
Resolved bool `protobuf:"varint,4,opt,name=resolved,proto3" json:"resolved,omitempty"`
Timestamp time.Time `protobuf:"bytes,5,opt,name=timestamp,proto3,stdtime" json:"timestamp"`
FiringAlerts []uint64 `protobuf:"varint,6,rep,packed,name=firing_alerts,json=firingAlerts,proto3" json:"firing_alerts,omitempty"`
ResolvedAlerts []uint64 `protobuf:"varint,7,rep,packed,name=resolved_alerts,json=resolvedAlerts,proto3" json:"resolved_alerts,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
(2)告警静音状态:存储周期由静音规则自行配置,此处不再展开细节。
