原文档地址:https://mp.weixin.qq.com/s/Wcp7ltEbnHpUlbaF9JDgZg

去绘制渲染报警图表,然后上传到对象存储中保存起来,在钉钉中就可以直接展示了,Promoter 就是这个方案的一个实现,支持在消息通知中展示实时报警图表,效果图如下所示:

目前是将报警数据渲染成图片后上传到 S3 对象存储,所以需要配置一个对象存储(阿里云 OSS 也可以),此外消息通知展示样式支持模板定制,该功能参考自项目 https://github.dev/timonwong/prometheus-webhook-dingtalk。

模板

默认模板位于 template/default.tmpl,可以根据自己需求定制:

{{ define "__subject" }}[{{ .Status | toUpper }}{{ if eq .Status "firing" }}:{{ .Alerts.Firing | len }}{{ end }}] {{ .GroupLabels.SortedPairs.Values | join " " }} {{ if gt (len .CommonLabels) (len .GroupLabels) }}({{ with .CommonLabels.Remove .GroupLabels.Names }}{{ .Values | join " " }}{{ end }}){{ end }}{{ end }}
{{ define "__alertmanagerURL" }}{{ .ExternalURL }}/#/alerts?receiver={{ .Receiver }}{{ end }} {{ define "default.__text_alert_list" }}{{ range . }}
### {{ .Annotations.summary }} **详情:** {{ .Annotations.description }} {{ range .Images }}
**条件:** `{{ .Title }}`
![]({{ .Url }})
{{- end }} **标签:**
{{ range .Labels.SortedPairs }}{{ if and (ne (.Name) "severity") (ne (.Name) "summary") }}> - {{ .Name }}: {{ .Value | markdown | html }}
{{ end }}{{ end }}
{{ end }}{{ end }} {{/* Default */}}
{{ define "default.title" }}{{ template "__subject" . }}{{ end }}
{{ define "default.content" }}
{{ if gt (len .Alerts.Firing) 0 -}}
#### **{{ .Alerts.Firing | len }} 条报警**
{{ template "default.__text_alert_list" .Alerts.Firing }}
{{ range .AtMobiles }}@{{ . }}{{ end }}
{{- end }}
{{ if gt (len .Alerts.Resolved) 0 -}}
#### **{{ .Alerts.Resolved | len }} 条报警恢复**
{{ template "default.__text_alert_list" .Alerts.Resolved }}
{{ range .AtMobiles }}@{{ . }}{{ end }}
{{- end }}
{{- end }}

部署

默认配置文件如下所示,放置在 /etc/promoter/config.yaml

debug: true
http_port: 8080
timeout: 5s
prometheus_url: <prometheus_url> # Prometheus 的地址
metric_resolution: 100 s3:
access_key: <ak>
secret_key: <sk>
endpoint: oss-cn-beijing.aliyuncs.com
region: cn-beijing
bucket: <bucket> dingtalk:
url: https://oapi.dingtalk.com/robot/send?access_token=<token>
secret: <SEC> # secret for signature

可以直接使用 Docker 镜像 cnych/promoter:v0.1.1 部署,在 Kubernetes 中部署可以直接参考 deploy/kubernetes/promoter.yaml

启动完成后在 AlertManager 配置中指定 Webhook 地址即可:

route:
group_by: ['alertname', 'cluster']
group_wait: 30s
group_interval: 2m
repeat_interval: 1h
receiver: webhook receivers:
- name: 'webhook'
webhook_configs:
- url: 'http://promoter.kube-mon.svc.cluster.local:8080/webhook' # 配置 promoter 的 webhook 接口
send_resolved: true

核心原理

该项目采用 golang 实现,Webhook 的实现很简单,这里的核心部分是如何渲染监控图表,核心方式是通过 Prometheus 的 API 接口来获取查询的指标数据:

func Metrics(server, query string, queryTime time.Time, duration, step time.Duration) (promModel.Matrix, error) {
client, err := prometheus.NewClient(prometheus.Config{Address: server})
if err != nil {
return nil, fmt.Errorf("failed to create Prometheus client: %v", err)
} api := prometheusApi.NewAPI(client)
value, _, err := api.QueryRange(context.Background(), query, prometheusApi.Range{
Start: queryTime.Add(-duration),
End: queryTime,
Step: duration / step,
})
if err != nil {
return nil, fmt.Errorf("failed to query Prometheus: %v", err)
} metrics, ok := value.(promModel.Matrix)
if !ok {
return nil, fmt.Errorf("unsupported result format: %s", value.Type().String())
} return metrics, nil
}

然后将获取的指标绘制出来,图形绘制使用的 gonum.org/v1/plot 这个包来实现的:

func PlotMetric(metrics promModel.Matrix, level float64, direction string) (io.WriterTo, error) {
p, err := plot.New()
if err != nil {
return nil, fmt.Errorf("failed to create new plot: %v", err)
} textFont, err := vg.MakeFont("Helvetica", 3*vg.Millimeter)
if err != nil {
return nil, fmt.Errorf("failed to load font: %v", err)
} evalTextFont, err := vg.MakeFont("Helvetica", 5*vg.Millimeter)
if err != nil {
return nil, fmt.Errorf("failed to load font: %v", err)
} evalTextStyle := draw.TextStyle{
Color: color.NRGBA{A: 150},
Font: evalTextFont,
XAlign: draw.XRight,
YAlign: draw.YBottom,
} p.X.Tick.Marker = plot.TimeTicks{Format: "15:04:05"}
p.X.Tick.Label.Font = textFont
p.Y.Tick.Label.Font = textFont
p.Legend.Font = textFont
p.Legend.Top = true
p.Legend.YOffs = 15 * vg.Millimeter // Color palette for drawing lines
paletteSize := 8
palette, err := brewer.GetPalette(brewer.TypeAny, "Dark2", paletteSize)
if err != nil {
return nil, fmt.Errorf("failed to get color palette: %v", err)
}
colors := palette.Colors() var lastEvalValue float64 for s, sample := range metrics {
data := make(plotter.XYs, 0)
for _, v := range sample.Values {
fs := v.Value.String()
if fs == "NaN" {
_, err := drawLine(data, colors, s, paletteSize, p, metrics, sample)
if err != nil {
return nil, err
} data = make(plotter.XYs, 0)
continue
} f, err := strconv.ParseFloat(fs, 64)
if err != nil {
return nil, fmt.Errorf("sample value not float: %s", v.Value.String())
}
data = append(data, plotter.XY{X: float64(v.Timestamp.Unix()), Y: f})
lastEvalValue = f
} _, err := drawLine(data, colors, s, paletteSize, p, metrics, sample)
if err != nil {
return nil, err
}
} var polygonPoints plotter.XYs if direction == "<" {
polygonPoints = plotter.XYs{{X: p.X.Min, Y: level}, {X: p.X.Max, Y: level}, {X: p.X.Max, Y: p.Y.Min}, {X: p.X.Min, Y: p.Y.Min}}
} else {
polygonPoints = plotter.XYs{{X: p.X.Min, Y: level}, {X: p.X.Max, Y: level}, {X: p.X.Max, Y: p.Y.Max}, {X: p.X.Min, Y: p.Y.Max}}
} poly, err := plotter.NewPolygon(polygonPoints)
if err != nil {
return nil, err
}
poly.Color = color.NRGBA{R: 255, A: 40}
poly.LineStyle.Color = color.NRGBA{R: 0, A: 0}
p.Add(poly)
p.Add(plotter.NewGrid()) // Draw plot in canvas with margin
margin := 6 * vg.Millimeter
width := 20 * vg.Centimeter
height := 10 * vg.Centimeter
c, err := draw.NewFormattedCanvas(width, height, "png")
if err != nil {
return nil, fmt.Errorf("failed to create canvas: %v", err)
} cropedCanvas := draw.Crop(draw.New(c), margin, -margin, margin, -margin)
p.Draw(cropedCanvas) // Draw last evaluated value
evalText := fmt.Sprintf("latest evaluation: %.2f", lastEvalValue) plotterCanvas := p.DataCanvas(cropedCanvas) trX, trY := p.Transforms(&plotterCanvas)
evalRectangle := evalTextStyle.Rectangle(evalText) points := []vg.Point{
{X: trX(p.X.Max) + evalRectangle.Min.X - 8*vg.Millimeter, Y: trY(lastEvalValue) + evalRectangle.Min.Y - vg.Millimeter},
{X: trX(p.X.Max) + evalRectangle.Min.X - 8*vg.Millimeter, Y: trY(lastEvalValue) + evalRectangle.Max.Y + vg.Millimeter},
{X: trX(p.X.Max) + evalRectangle.Max.X - 6*vg.Millimeter, Y: trY(lastEvalValue) + evalRectangle.Max.Y + vg.Millimeter},
{X: trX(p.X.Max) + evalRectangle.Max.X - 6*vg.Millimeter, Y: trY(lastEvalValue) + evalRectangle.Min.Y - vg.Millimeter},
}
plotterCanvas.FillPolygon(color.NRGBA{R: 255, G: 255, B: 255, A: 90}, points)
plotterCanvas.FillText(evalTextStyle, vg.Point{X: trX(p.X.Max) - 6*vg.Millimeter, Y: trY(lastEvalValue)}, evalText) return c, nil
} func drawLine(data plotter.XYs, colors []color.Color, s int, paletteSize int, p *plot.Plot, metrics promModel.Matrix, sample *promModel.SampleStream) (*plotter.Line, error) {
var l *plotter.Line
var err error
if len(data) > 0 {
l, err = plotter.NewLine(data)
if err != nil {
return &plotter.Line{}, fmt.Errorf("failed to create line: %v", err)
} l.LineStyle.Width = vg.Points(1)
l.LineStyle.Color = colors[s%paletteSize] p.Add(l)
if len(metrics) > 1 {
m := labelText.FindStringSubmatch(sample.Metric.String())
if m != nil {
p.Legend.Add(m[1], l)
}
}
} return l, nil
}

更多实现细节可以前往项目 https://github.com/cnych/promoter 查看。

在 AlertManager 报警通知中展示监控图表的更多相关文章

  1. zabbix监控之邮件报警通知

    zabbix官网的操作指南:https://www.zabbix.com/documentation/4.0/zh/manual 首先我们需要创建一个需要被监控的主机,并设置相应的监控项.当监控项收集 ...

  2. incubator-dolphinscheduler 如何在不写任何新代码的情况下,能快速接入到prometheus和grafana中进行监控

    一.prometheus和grafana 简介 prometheus是由谷歌研发的一款开源的监控软件,目前已经贡献给了apache 基金会托管. 监控通常分为白盒监控和黑盒监控之分. 白盒监控:通过监 ...

  3. 分布式系统监视zabbix讲解二之邮件报警通知--技术流ken

    概述 在上一篇博客<分布式系统监视zabbix讲解一技术流ken>中已经详细讲解了如何安装zabbix,本篇博客将详细讲解如何使用zabbix监控另外一台主机,并实现email报警通知机制 ...

  4. 分布式系统监视zabbix讲解二之邮件报警通知

    概述 在上一篇博客<分布式系统监视zabbix讲解一技术流ken>中已经详细讲解了如何安装zabbix,本篇博客将详细讲解如何使用zabbix监控另外一台主机,并实现email报警通知机制 ...

  5. zabbix实现QQ邮件报警通知--技术流ken

    前言 前几天搜了下网上使用zabbix邮件报警通知的文章,大多数还是使用mailx的方法,过程配置起来比较冗余繁琐,这几天想着把自己平时用到的qq邮件报警的方法分享出来供大家参考,以此减少不必要的步骤 ...

  6. Dubbo中的监控和管理

    一.Dubbo中的监控 1.原理 原理:服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心. 2.搭建监控服务 3.修改配置文件 修改注册中心的地址: 注意:这个 ...

  7. zabbix3.4调用钉钉报警通知(超详细)

     一.备注: zabbix调用钉钉接口报警通知有两种情况: 1.通知到个人钉 2.通知到钉钉群 本文主要介绍zabbix调用钉钉接口通知到钉钉个人的方式 二.zabbix3.4调用钉钉接口报警通知到个 ...

  8. Zabbbix之十二------Zabbix实现微信报警通知及创建聚合图形

    实战一:实现zabbix监控微信报警 1.在企业微信上注册账号 1.注册企业微信,管理员需要写上自己的真实姓名,扫描以下的二维码,与微信关联真实姓名. 2.登陆企业微信,然后创建一个微信故障通知应用 ...

  9. C#结合SMTP实现邮件报警通知

    写在前面 C#是微软推出的一门面向对象的通用型编程语言,它除了可以开发PC软件.网站(借助 http://ASP.NET)和APP(基于 Windows Phone),还能作为游戏脚本,编写游戏逻辑. ...

随机推荐

  1. python虚拟环境(python+conda)

    python的不同虚拟环境就相当于在电脑上装了很多个python.下面写python创建虚拟环境.conda创建虚拟环境和在pycharm中配置一下. python -m venv (要创虚拟环境的路 ...

  2. VGA设计(原理说明。Verilog代码实现,仿真结果)

    各类显示屏的显示原理大部分是利用人眼的视觉暂留效应.比如之前的数码管显示就是设计每个周期内各个小段按顺序显示,来达到显示一个数字的效果. VGA同理,显示屏在显示时是一个像素一个像素地显示,在人眼看来 ...

  3. 小红书携手HMS Core,畅玩高清视界,种草美好生活

    在相同流量消耗的情况下,540p可秒变1080p?这不是魔法,通过视频超分辨率技术(简称视频超分),就能让视频变得更清晰. 7月20日,在小红书最新版本7.48的App中,用户就能体验到这项技术带来的 ...

  4. CF1709A Three Doors 题解

    题意 有三扇门,每扇门都有一把钥匙与该门上的数字相对应,若数字一样就可以打开. 其中有两扇门后有钥匙,一扇门后为空. 现拥有一把钥匙 \(x\),给出三扇门后的钥匙(若为 \(0\) 即门后没有钥匙) ...

  5. Cube Stacking 来源:洛谷

    题目 题目oj(洛谷) Farmer John and Betsy are playing a game with N (1 <= N <= 30,000)identical cubes ...

  6. 常用的函数式接口_Consumer接口和常用的函数式接口_Consumer接口的默认方法andThen

    Consumer接口 java,util.function.Consumer接口则正好与Supplier接口相反,它不是生产一个数据,而是消费一个数据,其数据类型由泛型决定 抽象方法:accept C ...

  7. SQL语句实战学习

    参考:https://zhuanlan.zhihu.com/p/38354000再次感谢作者的整理!! 1.数据已提前准备好了,已知有如下4张表:学生表:student 成绩表:score(学号,课程 ...

  8. Iconfont(矢量图标)+iconmoon(图标svg互转)配合javascript来打造属于自己的个性化社交分享系统

    原文转载自「刘悦的技术博客」https://v3u.cn/a_id_143 每一个应用程序,其实都会有分享的需求,比如一键分享一篇文章或者一些活动到微博或者微信亦或者是twitter等社交平台,因为人 ...

  9. 2022了你还不会『低代码』?数据科学也能玩转Low-Code啦! ⛵

    作者:韩信子@ShowMeAI 数据分析实战系列:http://www.showmeai.tech/tutorials/40 机器学习实战系列:http://www.showmeai.tech/tut ...

  10. 开源深度思考 - In Community We Trust

    作者 | 黄东旭,PingCAP 联合创始人&CTO 出品 | CSDN(ID:CSDNnews) 业界一直流传着黄东旭的传说:小学三年级开始写代码,四五年级学习C语言,初中毕业时,已经能够用 ...