基于kratos的prometheus告警平台

开篇之前先上demo, 我知道你们也不想看文字

账号:prometheus

密码:123456

服务地址:prometheus.aide-cloud.cn/
仓库地址:github.com/aide-cloud/...

prometheus

参考资料:github.com/prometheus/...

熟悉prometheus的同学可能清楚一些, 配置prometheus报警时候,需要写很多的yaml规则文件,例如下面的nginx监控规则

yaml 复制代码
groups:
  - name: NginxLuaPrometheusMonitoring
    rules:
      # Too many HTTP requests with status 4xx (> 5%)
      - alert: NginxHighHttp4xxErrorRate
        expr: sum(rate(nginx_http_requests_total{status=~"^4.."}[1m])) / sum(rate(nginx_http_requests_total[1m])) * 100 > 5
        for: 1m
        labels:
          severity: critical
        annotations:
          summary: Nginx high HTTP 4xx error rate (instance {{ $labels.instance }})
          description: "Too many HTTP requests with status 4xx (> 5%)\n  VALUE = {{ $value }}\n  LABELS = {{ $labels }}"

      # Too many HTTP requests with status 5xx (> 5%)
      - alert: NginxHighHttp5xxErrorRate
        expr: sum(rate(nginx_http_requests_total{status=~"^5.."}[1m])) / sum(rate(nginx_http_requests_total[1m])) * 100 > 5
        for: 1m
        labels:
          severity: critical
        annotations:
          summary: Nginx high HTTP 5xx error rate (instance {{ $labels.instance }})
          description: "Too many HTTP requests with status 5xx (> 5%)\n  VALUE = {{ $value }}\n  LABELS = {{ $labels }}"

      # Nginx p99 latency is higher than 3 seconds
      - alert: NginxLatencyHigh
        expr: histogram_quantile(0.99, sum(rate(nginx_http_request_duration_seconds_bucket[2m])) by (host, node)) > 3
        for: 2m
        labels:
          severity: warning
        annotations:
          summary: Nginx latency high (instance {{ $labels.instance }})
          description: "Nginx p99 latency is higher than 3 seconds\n  VALUE = {{ $value }}\n  LABELS = {{ $labels }}"

当我们准备好这些数据后, 需要重新reload到prometheus-server, 像下面这样

sh 复制代码
curl -X POST http://localhost:9090/-/reload

如此,规则就算加载成功了,我们就可以在prometheus平台看到类似的页面(随意截图, 效果差不多)

图中绿色的就是正常状态, 红色就是已经触发了规则, 发生了报警事件,其实还有个中间态(黄色),当达到规则中配置的for(持续时间)条件时候, 就会触发告警,并推送到alertmanager, 如此, 规则的配置和使用就算告一段落了, 接下来就是出来alert manager中的告警数据,并把这些事件通过各种消息通道发送给接收对象。

但是这样有什么问题呢?

问题一

  • 刷新规则需要到服务器上直接操作,风险较大
  • 配置规则是直接书写yaml配置,普通人要么不会,要么没有服务器权限,只能交给专人处理

问题二

  • 报警事件没有归档,不能做数据统计
  • 报警接收对象不好管理,需要直接在alert manager服务器上直接操作yaml配置
  • 如果自己实现hook,需要适配

以上就是简单梳理出来的问题。

我为prometheus赋能

prometheus-manager的出现, 到底有没有解决以上问题呢?答案是解决了。

功能介绍

  1. 规则管理
  2. 通知对象管理
  3. 实时告警
  4. 告警历史

规则管理

提供规则管理功能(prometheus.aide-cloud.cn/#/home/moni...

通过可视化表单完成prometheus规则的编辑,在规则编辑中,可以选择数据源, 这意味着我们可以配置多个数据源的告警。Prom QL支持智能提示,语法校验,能很好的帮助我们配置出正确有效的规则。右侧闪电按钮可以查看这条规则对应的数据,这能帮助我们判断这条规则语句,产生的告警事件也是可预见的。

通知对象管理

提供通知对象的集中管理,可以从已有的通知对象中直接与规则进行关联, 从而将报警通知给这些对象。

支持多个hook平台,例如钉钉、企业微信、飞书等平台,也支持第三方自定义hook,这些通知方式能有效的是我们的告警事件稳定送达,当然,邮件、短信等方式也在规划中,我们拭目以待。

实时告警

当告警事件发生时候,我们除了希望收到通知以外, 也希望有一个可以查看当前告警事件的面板,方便我们值班或者运维的同学去处理这些问题,那我们的实时告警面板就可以满足这个场景。

实时告警页面有多个tab面板,我们可以按这些tab分类展示,区分不同业务类型和值班类型告警,一定程度上减少不相关告警事件的干扰,归类功能在规则配置里面选择报警页面即可。

告警附带一些处理功能,例如静默、强制消警、告警标记、告警升级等。

告警历史(开发中)

告警事件从事件发生到事件结束会经历一定的持续时间, 当这些告警我们处理之后,必然是需要做一些复盘和系统优化的,那么告警历史会提供基础的事件查询、汇总、大盘等功能,为我们后续优化和规则调整作出一些数据参考

为什么要做这样一个系统呢?

开发这个系统的初衷是因为作为一个纯粹的Go开发者,为了提升自己的开发能力,希望通过自己的能力为开源社区做一份微不足道的贡献。

那为什么选择做一个prometheus监控系统呢?这和我的工作经历有关,我的第一份工作就是负责公司自研的监控系统研发,在偶然的机会下接触到开源的prometheus监控系统,这让我对他产生很大的学习兴趣, 从最开始的服务搭建,各种博客找资源学习怎么部署,怎么配置规则,怎么自己写一个hook中间件。慢慢的,随着越来越熟悉,发现prometheus在使用过程中并不是那么丝滑。那我就想有没有这样一个系统或者平台能够让我们使用过程更加简单一点。找了一圈下来,发现大多这方面的系统都是收费的,要么就是没有达到我的预期,自己作为一个开发,这种时候当然是直接编码了。

编码也经历了好几个阶段,从整体体架构的设计,到前端UI框架调研、PromQL组件源码阅读,可谓是坎坎坷坷。

先说说后端项目的搭建,那时候刚开始学习kratos框架,想着直接拿来练手,刚写完登录功能就发现越来越臃肿,越来越写不下去,想着要不换个框架,说干就干,又改成自己熟悉的gin框架,但是gin框架本身直接用比较自由,这个项目涉及的接口比较多,为了开发方便,中途还为此对gin做了一次封装,搞了个gin-plus框架(github.com/aide-cloud/... 这个框架主要是为了方便我更快的完成CRUD,起初也是用得很舒服,但是因为当时设计的时候考虑了很多组件的使用,得去适配这些组件,比如gorm, 因此又为gorm写了个crud的包(github.com/aide-cloud/... 使用之初倒是没啥问题,但是在写功能时候就发现,有的功能存在BUG,这时候就需要又改项目BUG,又改这些工具包的BUG,人直接麻了,本来是想着搞几个包出来减轻负担的,这下负担更重了,算了算了, 还是专心撸码吧。然后, 我又从gin-plus改成kratos了。

前些日子,刚完成这个项目的报警功能,就迫不及待的跟群友们分享,群友们也很热情,同时也提了很多宝贵的意见。 比如这个系统已开始设计使用了mysql、redis、kafka等组件,大家都觉得这样会使得再使用过程中变得复杂,维护起来也变得困难,刚好功能开发也告一段落,就把大家提的这些意见一一记录下来,并改进。

首先改动的是redis使用部分,使用redis是为了缓存告警中的持续事件,权限缓存等,在调研了一些本地缓存之后,最终采用了nutsdb(github.com/nutsdb/nuts...), 这是一个本地缓存数据库,使用姿势和redis基本一样,因此,针对我使用到的redis功能,再结合这个包定义了通用接口,以适配两种存储。

go 复制代码
type GlobalCache interface {
    HDel(ctx context.Context, prefix string, keys ...string) error
    HSet(ctx context.Context, prefix string, values ...[]byte) error
    HGet(ctx context.Context, prefix string, keys string) ([]byte, error)
    HGetAll(ctx context.Context, prefix string) (map[string][]byte, error)
    Get(ctx context.Context, key string) ([]byte, error)
    Set(ctx context.Context, key string, value []byte, ttl time.Duration) error
    Del(ctx context.Context, keys ...string) error
    SetNX(ctx context.Context, key string, value []byte, ttl time.Duration) bool
    Exists(ctx context.Context, keys ...string) int64
    Close() error
}

我实现了redis版本和nutsdb版本的两种缓存组件,可以通过配置自由选择,给了用户更多选择的空间。

解决完缓存,就是前面提到的kafka了,选择kafak是为了使系统解耦,同时也是通过kafka传递告警事件的数据,mq能有效解决告警突增时候的突刺问题(削峰),但是kafka的维护比较复杂,需要的资源也比较多,并不是每个人都需要的,因此,我根据我的mq姿势,抽象出来了一个消息接口。

go 复制代码
type (
    Callback func(topic consts.TopicType, key, value []byte) error

    HookMsg struct {
       Topic string `json:"topic"`
       Value []byte `json:"value"`
       Key   []byte `json:"key"`
    }

    Interflow interface {
       // Send 把数据投递给谁
       Send(ctx context.Context, to string, msg *HookMsg) error
       // Receive 接收投递过来的数据
       Receive() error
       // SetHandles 设置回调函数
       SetHandles(handles map[consts.TopicType]Callback) error
       // Close 关闭
       Close() error
    }
)

数据格式基本按照kafka message来设计,后续在适配其他MQ或者其他方式时候,可能得略微调整,基于这个接口,我实现了MQ版本和hook版本, hook版本就是服务端和代理端都提供receive接口(如下)。然后通过http或者RPC方式通信,从而达到和MQ一样的效果。在这里其实还应该引入队列来实现告警削峰,由于没有实际数据支撑,就暂时不做考虑了。

proto 复制代码
service HookInterflow {
  rpc Receive(ReceiveRequest) returns (ReceiveResponse) {
   option (google.api.http) = {
    post: "/api/v1/interflows/receive"
    body: "*"
   };
  }
}

message ReceiveRequest {
  string topic = 1 [(validate.rules).string.min_len = 1];
  bytes value = 2;
  bytes key = 3;
}
message ReceiveResponse {
  uint32 code = 1;
  string message = 2;
}

至此,对redis和kafka的依赖算是完全抽离出来,用户在使用过程中可以自由选择要不要使用这些组件。

再来说说前端。最有意思的是我想实现一个和prometheus web服务端一样的PromQL输入框(可我不会写啊)。

为了实现这个效果,详细阅读了prometheus web模块的全部代码,看它用了哪些库, 怎么用的,我怎么搬过来。在进行了各种demo的搭建后,终于,基于Arco Design(arco.design/react/docs/... 版本的PromQL输入框完成了,给我激动好久,这意味着后续的功能都可以围绕着展开了。

其实我更喜欢Arco一点, 这种风格深得我心, 那为什么后面换成了Ant design呢, 这要从发现他们Form的Bug(如下, 其实是antd的,哈哈哈, 反正都是一样的问题)说起,后来我发现不止它们家有,antd也有,失算了。当我发现有这个BUG, 并给他们提了issue之后, 没有得到解决,我就直接用antd开发了一版, 也就是你们看到上面的部分。结果发现,antd也有,提了issue之后,他们修了一个,但是其实是所有Form下的组件都有问题,我淦,修了,但好像又没有修。所以你们会发现, 登录页是Arco的pro版本登录页抄过来的,登录后是antd各式组件。

PromQL功能完成之后, 剩下的就是各种CRUD管理页面的开发,此处省略一万字......

未来展望

目前这个系统只是具备了正常的规则管理、报警、hook告警通知能力,实时告警展示也比较简陋,MVP版本都算不上,但是好在我通过此次的设计开发,越来越清晰的知道这个系统它应该长什么样,还需要什么功能,开发路线也是越来越明确。

我希望这个系统能成为大家喜爱的监控系统,并成为开源社区的一个重要组成部分。

最后,再贴一下仓库链接,欢迎大家PR

github.com/aide-cloud/...

相关推荐
我是前端小学生8 小时前
Go语言中的方法和函数
go
探索云原生12 小时前
在 K8S 中创建 Pod 是如何使用到 GPU 的: nvidia device plugin 源码分析
ai·云原生·kubernetes·go·gpu
自在的LEE18 小时前
当 Go 遇上 Windows:15.625ms 的时间更新困局
后端·kubernetes·go
Gvto2 天前
使用FakeSMTP创建本地SMTP服务器接收邮件具体实现。
go·smtp·mailtrap
白泽来了2 天前
【Go进阶】手写 Go websocket 库(一)|WebSocket 通信协议
开源·go
witton2 天前
将VSCode配置成Goland的视觉效果
ide·vscode·编辑器·go·字体·c/c++·goland
非凡的世界2 天前
5个用于构建Web应用程序的Go Web框架
golang·go·框架·web
湫qiu2 天前
6.5840 Lab-Key/Value Server 思路
后端·go
我是前端小学生3 天前
Go语言中的init函数
go
我是前端小学生3 天前
Go语言中内部模块的可见性规则
go