概述
这篇打算从夜莺的alert告警引擎这个大的模块开始,逐步揭秘,告警的相关流程和组成,以及最关键的Nightingale如何生成告警如何实现,之前博客简单了解过其中关于,多实例组成集群方式如何均分告警,听介绍是用到一致性哈希——一致性哈希(哈希环)解决数据分布问题,本文参考的结构是V6版本(main分支,Version: v6.0.0-da9f5fbb12ce2c291448e3d9a7c5282c911463f4)。
目录介绍
tree -LF 3 .
./
├── aconf/ //告警配置
│ └── conf.go
├── alert.go //告警初始化入口
├── astats/ //统计信息
│ └── stats.go
├── common/ //通用方法或结构体
│ ├── conv.go //转换成异常点结构体
│ └── key.go //规则key序列化和标签判断
├── dispatch/ //发送告警
│ ├── consume.go //消费者
│ ├── dispatch.go //生产者
│ ├── log.go //日志记录
│ ├── notify_channel.go //通知通道的map
│ └── notify_target.go //通知目标结构体,维护发送目标
├── eval/ //评估
│ ├── alert_rule.go //告警调度
│ └── eval.go //查询时序库评估告警,查询promql
├── mute/ //屏蔽
│ └── mute.go //告警屏蔽
├── naming/ //告警命名
│ ├── hashring.go //数据源对应哈希环
│ └── heartbeat.go //告警心跳维护
├── process/ //告警处理
│ ├── alert_cur_event.go //告警发生的事件的map
│ └── process.go //告警处理
├── queue/ //告警队列
│ └── queue.go //Gauge类型的告警队列
├── record/ //记录规则
│ ├── prom_rule.go //将记录规则写入时序库
│ ├── sample.go //样本,转换记录用
│ └── scheduler.go //记录规则调度
├── router/ //告警路由
│ ├── router.go //告警路由
│ └── router_event.go //告警事件路由
└── sender/ //告警通知发送渠道
├── callback.go //回调
├── dingtalk.go //钉钉
├── email.go //邮件
├── feishu.go //飞书
├── mm.go //mm
├── plugin.go //脚本
├── plugin_cmd_unix.go //linux脚本
├── plugin_cmd_windows.go //window脚本
├── sender.go //发送通知
├── telegram.go //telegram
├── webhook.go //webhook
└── wecom.go //企业微信
13 directories, 35 files
初步分析
从目录结构我们通过配置,统计,发送,评估,屏蔽,命名,处理,队列,记录规则,路由,通知渠道等构成了完整的告警引擎模块。
它的入口函数在同名的alert.go,只有两个函数Initialize(configDir string, cryptoKey string) (func(), error)
和Start(alertc aconf.Alert, pushgwc pconf.Pushgw, ...lots of arg... isCenter bool)
,打包的n9e程序通过Start函数接入告警引擎,n9e-alert通过Initialize函数完成一些配置工作,然后也是调用Start函数来实现调用。
其中n9e-alert程序有三个参数,其中configs和crypto-key是Initialize函数需要的参数。
- configs 配置文件路径,优先读取环境变量中N9E_CONFIGS的值没有设置为默认值etc,
- crypto-key 加密密钥,默认’’
- version 是否输出版本,默认false,如果是true,打印在输出到控制台上后立即结束程序
./n9e-alert --help
Usage of ./n9e-alert:
-configs string
Specify configuration directory.(env:N9E_CONFIGS) (default "etc")
-crypto-key string
Specify the secret key for configuration file field encryption.
-version
Show version.
接下来我们看看Initialize的代码如何实现
func Initialize(configDir string, cryptoKey string) (func(), error) {
config, err := conf.InitConfig(configDir, cryptoKey) //通过从配置文件路径和密钥,读取配置文件
if err != nil {
return nil, fmt.Errorf("failed to init config: %v", err)
}
logxClean, err := logx.Init(config.Log)
//logxClean 为了性能使用bufio,bufferSize为256kB。log库会自己定期Flush到文件。在主程序退出之前需要调用dlog.Close(),否则可能会丢失部分log。
if err != nil {
return nil, err
}
db, err := storage.New(config.DB)//连接数据库,底层用的gorm
if err != nil {
return nil, err
}
ctx := ctx.NewContext(context.Background(), db)
redis, err := storage.NewRedis(config.Redis)//连接redis
if err != nil {
return nil, err
}
syncStats := memsto.NewSyncStats()//同步状态监控指标,类型是Gauge
alertStats := astats.NewSyncStats()//告警的各种监控指标
//设置各种Cache
targetCache := memsto.NewTargetCache(ctx, syncStats, redis)
busiGroupCache := memsto.NewBusiGroupCache(ctx, syncStats)
alertMuteCache := memsto.NewAlertMuteCache(ctx, syncStats)
alertRuleCache := memsto.NewAlertRuleCache(ctx, syncStats)
notifyConfigCache := memsto.NewNotifyConfigCache(ctx)
dsCache := memsto.NewDatasourceCache(ctx, syncStats)
promClients := prom.NewPromClient(ctx, config.Alert.Heartbeat)//创建prom查询客户端
externalProcessors := process.NewExternalProcessors()//创建告警对象处理器
Start(config.Alert, config.Pushgw, syncStats, alertStats, externalProcessors, targetCache, busiGroupCache, alertMuteCache, alertRuleCache, notifyConfigCache, dsCache, ctx, promClients, false)//启动告警
r := httpx.GinEngine(config.Global.RunMode, config.HTTP)//使用gin框架启动http服务
rt := router.New(config.HTTP, config.Alert, alertMuteCache, targetCache, busiGroupCache, alertStats, ctx, externalProcessors)
rt.Config(r)
httpClean := httpx.Init(config.HTTP, r)//http清理
return func() {
logxClean()
httpClean()
}, nil
}
配置文件可支持 toml, json, yaml三种类型;
由于告警配置Alert.Heartbeat中IP属性建议设置一个唯一值,配置文件没有填写会自动填充,其逻辑如下:
- 首先通过AliDNS获取本机ip
- 没有获取到有效值会尝试获取hostname,获取失败会直接退出程序
- 获取hostname中包含localhost会打印一个提示建议用一个唯一值
- 在填充完 config.Alert.Heartbeat.IP 后会再根据配置文件HTTP.Port的配置(17000)组合一起设置 config.Alert.Heartbeat.Endpoint 属性
配置文件中属性存在前缀{{cipher}},则代表是加密过的属性,需根据公钥解密,使用BASE64和AES解密。
logx底层是logger使用的是 pkg 的logger,通过readme看到更多详细解释,日志保存在文件是可以按照不同日志等级,设置保存时间(循环写一个文件),或者自动切分日志。
redis可支持单机模式,哨兵模式,cluster集群,不过我了解到redis集群除了上面两种集群方式应该还有个主从模式,不过我想这里应该不会去写redis,只是读如果是主从,应该是同单机模式即可。
syncStats是两个用于监控同步状态的Exporter,duration和sync_number,duration是定时任务使用时长,sync_number是定时任务同步数量。
alertStats是6个用于监控告警状态的Exporter
- samples_received_total 从各个接收接口接收到的监控数据总量
- alerts_total 产生的告警总量
- alert_queue_size 内存中的告警事件队列的长度
- sample_queue_size 数据转发队列,各个队列的长度
- http_request_duration_seconds 一些重要的请求,比如接收数据的请求,应该统计一下延迟情况
- forward_duration_seconds 发往后端TSDB,延迟如何
这篇只是一起看了下告警入口的初始化函数,代码中添加了一些注释,后续我们下篇继续。