欢迎访问我的GitHub
这里分类和汇总了欣宸的全部原创(含配套源码):github.com/zq2599/blog...
本篇概览
-
本文是《prometheus实战》系列的第五篇,主要内容是完成任务:应用服务器CPU使用率偏高时,飞书APP收到告警通知,完整的数据流如下图
-
前文的进度是完成了上图的绿色部分,今天要做的就是完成红色部分,让完整的功能可以使用
-
前文咱们部署好了alertmanager,也在alertmanager上配置了告警时的webhook地址,然后还触发告警试了一下,不过由于webhook地址对应的服务并不存在,于是alertmanager调用失败,通过日志咱们观察到以下错误
shell
May 13 10:04:40 deskmini alertmanager[767]: ts=2023-05-13T02:04:40.869Z caller=notify.go:732 level=warn component=dispatcher receiver=web.hook integration=webhook[0] msg="Notify attempt failed, will retry later" attempts=1 err="Post \"http://192.168.50.134:8888/webhook\": dial tcp http://192.168.50.134:8888/webhook: connect: connection refused"
May 13 10:09:40 deskmini alertmanager[767]: ts=2023-05-13T02:09:40.869Z caller=dispatch.go:352 level=error component=dispatcher msg="Notify for alerts failed" num_alerts=1 err="web.hook/webhook[0]: notify retry canceled after 16 attempts: Post \"http://192.168.50.134:8888/webhook\": dial tcp http://192.168.50.134:8888/webhook: connect: connection refused"
May 13 10:09:40 deskmini alertmanager[767]: ts=2023-05-13T02:09:40.869Z caller=notify.go:732 level=warn component=dispatcher receiver=web.hook integration=webhook[0] msg="Notify attempt failed, will retry later" attempts=1 err="Post \"http://192.168.50.134:8888/webhook\": dial tcp http://192.168.50.134:8888/webhook: connect: connection refused"
- 今天的任务就是把这个web服务开发出来,并运行起来,这样整个功能就完善了
准备工作:飞书机器人
- 本次实战打算用飞书作为通知消息的途径,因此要先把飞书机器人准备好,步骤如下
- 先创建一个群聊
- 名称随意
- 点击设置
- 点击群机器人,打开群机器人设置页面
- 点击添加
- 选择自定义机器人
- 对机器人的名称和描述做简单的说明
- 如下图,页面会给出这个机器人唯一的webhook地址,也就是说,只要向这个地址发送请求,机器人就会发言,内容就是请求body
- 把上面的webhook地址准备好,稍后编码的时候会用到
- 机器人已经准备好了,接下来开始编码
- 这里要声明一下,选用飞书机器人,仅仅是欣宸个人觉得它简单方便,您完全可以按自己喜好选择其他通知途径
源码下载
- 接下来要开发web服务,如果您不想写代码,可以在下面的仓库下载完整源码,更换飞书机器人的webhook地址后即可正常使用:
名称 | 链接 | 备注 |
---|---|---|
项目主页 | github.com/zq2599/blog... | 该项目在GitHub上的主页 |
git仓库地址(https) | github.com/zq2599/blog... | 该项目源码的仓库地址,https协议 |
git仓库地址(ssh) | git@github.com:zq2599/blog_demos.git | 该项目源码的仓库地址,ssh协议 |
- 这个git项目中有多个文件夹,本篇的文件在tutorials/prometheus/webhook/文件夹下,如下图红框所示:
开发web服务,梳理开发步骤
- 本篇打算使用go语言来开发web服务,web框架选用的是hertz,其实这不重要,您可以选择自己擅长的语言和框架来完成
- 之所以用hertz,是因为它提供了命令可以快速创建工程,仅此而已
- 动手写代码之前,先梳理好编码的具体步骤
- 接下来的操作就按照上述步骤进行,先来创建web工程吧
创建web工程
- 再次强调,这里的web服务只是个helloworld级别的小工程,不论用什么语言什么框架都无所谓,您完全可以随心所欲
- 接下来介绍我这边的开发步骤,请确保本地golang已经部署成功,并且将GOPATH/bin添加到 PATH 环境变量之中(例如 export PATH=GOPATH/bin:$PATH)
- 首先是安装hertz,请确保本地golang已经部署成功,执行以下命令
shell
go install github.com/cloudwego/hertz/cmd/hz@latest
- 新建名为webhook的文件夹,在里面执行以下命令就会创建名为webhook的web工程
shell
hz new -module webhook
- 下载依赖包
shell
go mod tidy
- 此时再看webhook目录,整个代码框架已经准备好了,接下来只要把业务代码填上去即可
shell
tree webhook
webhook
├── biz
│ ├── handler
│ │ └── ping.go
│ └── router
│ └── register.go
├── build.sh
├── go.mod
├── main.go
├── router_gen.go
├── router.go
└── script
└── bootstrap.sh
编码,alertmanager请求体定义
- 先定义model,这是接受alertmanager请求的数据结构,新建文件webhook/biz/model/alert.go
go
package model
import "time"
type Alert struct {
Labels map[string]string `json:"labels"`
Annotations map[string]string `json:annotations`
StartsAt time.Time `json:"startsAt"`
EndsAt time.Time `json:"endsAt"`
}
type Notification struct {
Version string `json:"version"`
GroupKey string `json:"groupKey"`
Status string `json:"status"`
Receiver string `json:receiver`
GroupLabels map[string]string `json:groupLabels`
CommonLabels map[string]string `json:commonLabels`
CommonAnnotations map[string]string `json:commonAnnotations`
ExternalURL string `json:externalURL`
Alerts []Alert `json:alerts`
}
编码,飞书消息的请求和响应
- 由于要请求飞书服务器,因此请求和响应的数据结构也要定义好,新建文件webhook/biz/model/lark.go
go
package model
// 飞书机器人支持的POST数据结构
// 请求体相关
type LarkRequest struct {
MsgType string `json:"msg_type"`
Content Content `json:"content"`
}
type Content struct {
Text string `json:"text"`
}
// 响应体相关
type LarkResponse struct {
Code int `json:"code"`
Msg string `json:"msg"`
Data Data `json:"data"`
}
type Data struct {
}
编码,alertmanager到飞书请求体的转换工具
- 收到alertmanager请求后,要把有效内容转换成飞书请求体,这里做一个转换工具,新建文件webhook/biz/util/lark_transformer.go
go
package util
import (
"bytes"
"fmt"
"webhook/biz/model"
)
// TransformToLarkRequest 根据alertmanager的对象,创建出飞书消息的对象
func TransformToLarkRequest(notification model.Notification) (larkRequest *model.LarkRequest, err error) {
var buffer bytes.Buffer
// 先拿到分组情况
buffer.WriteString(fmt.Sprintf("通知组%s,状态[%s]\n告警项\n\n", notification.GroupKey, notification.Status))
// 每条告警逐个获取,拼接到一起
for _, alert := range notification.Alerts {
buffer.WriteString(fmt.Sprintf("摘要:%s\n详情:%s\n", alert.Annotations["summary"], alert.Annotations["description"]))
buffer.WriteString(fmt.Sprintf("开始时间: %s\n\n", alert.StartsAt.Format("15:04:05")))
}
// 构造出飞书机器人所需的数据结构
larkRequest = &model.LarkRequest{
MsgType: "text",
Content: model.Content{
Text: buffer.String(),
},
}
return larkRequest, nil
}
编码:主逻辑
-
数据结构和工具方法都准备好了,接下来就是主逻辑:收到alertmanager的请求后,根据请求体转为飞书消息请求体,再向飞书发送请求
-
接着是响应请求的handler,新建文件webhook/biz/handler/alertmanager.go
go
// Code generated by hertz generator.
package handler
import (
"bytes"
"context"
"encoding/json"
"io"
"net/http"
"webhook/biz/model"
"webhook/biz/util"
"github.com/cloudwego/hertz/pkg/app"
"github.com/cloudwego/hertz/pkg/common/hlog"
"github.com/cloudwego/hertz/pkg/common/utils"
"github.com/cloudwego/hertz/pkg/protocol/consts"
)
const (
// 请使用您自己的机器人的webhook地址
LARK_URL = "https://open.feishu.cn/open-apis/bot/v2/hook/12345678-1234-1234-1234-123456789012"
)
// Ping .
func AlertmanagerWebhook(ctx context.Context, c *app.RequestContext) {
var notification model.Notification
// 绑定对象
err := c.BindAndValidate(¬ification)
if err != nil {
c.JSON(consts.StatusBadRequest, utils.H{
"error": err.Error(),
})
return
}
hlog.Info("收到alertmanager告警:\n%s", notification)
// 根据alertmanager的请求构造飞书消息的请求数据结构
larkRequest, _ := util.TransformToLarkRequest(notification)
// 向飞书服务器发送POST请求,将飞书服务器返回的内容转为对象
bytesData, _ := json.Marshal(larkRequest)
req, _ := http.NewRequest("POST", LARK_URL, bytes.NewReader(bytesData))
req.Header.Add("content-type", "application/json")
res, err := http.DefaultClient.Do(req)
// 飞书服务器可能通信失败
if err != nil {
hlog.Error("请求飞书服务器失败:%s", err)
c.JSON(consts.StatusInternalServerError, utils.H{
"error": err.Error(),
})
return
}
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
var larkResponse model.LarkResponse
err = json.Unmarshal([]byte(body), &larkResponse)
// 飞书服务器返回的包可能有问题
if err != nil {
hlog.Error("获取飞书服务器响应失败:%s", err)
c.JSON(consts.StatusInternalServerError, utils.H{
"error": err.Error(),
})
return
}
hlog.Info("向飞书服务器发送消息成功")
c.JSON(consts.StatusOK, utils.H{
"message": "successful receive alert notification message!",
})
}
- 编码结束,可以运行起来试试了
vscode配置
- 如果您的IDE是vscode,将launch.json写成下面这样,就能在vscode启动这个项目了
json
{
"version": "0.2.0",
"configurations": [
{
"name": "Launch Package",
"type": "go",
"request": "launch",
"mode": "auto",
"program": "${workspaceFolder}"
}
]
}
shell脚本配置
- 如果想在linux下编译、构建、运行,可以用hertz准备好的shell脚本,不过要先做一点修改
- 打开script/bootstrap.sh,内容如下
shell
#!/bin/bash
CURDIR=$(cd $(dirname $0); pwd)
BinaryName=
echo "$CURDIR/bin/${BinaryName}"
exec $CURDIR/bin/${BinaryName}
- 上述内容中BinaryName变量没有值,这会导致运行程序时找不到二进制文件,这里给它加上,修改后如下
shell
#!/bin/bash
CURDIR=$(cd $(dirname $0); pwd)
BinaryName=webhook
echo "$CURDIR/bin/${BinaryName}"
exec $CURDIR/bin/${BinaryName}
- 现在可以用现成的shell编译和运行项目了
- 构建命令是sh build.sh
- 启动命令是sh output/bootstrap.sh
验证
-
将web服务运行起来,确保其地址和alertmanager配置的一致
-
想办法触发告警,我这里是用ffmpeg使得应用服务器CPU使用类升高,如下图,prometheus的告警进入Firing状态
-
alertmanager收到告警
-
很快,飞书消息也到达了,内容符合预期
-
想办法让应用服务器退出告警状态,我这里是杀掉ffmpeg进程,让应用服务器的CPU回到正常状态
-
很快,飞书的第二条消息到来,状态是resolved,表示应用服务器已经退出告警状态
-
两条告警消息的间隔是5分钟,这和alertmanager的配置有关,如下图,group_interval表示5分钟内不在firing状态,就表示故障已恢复
-
至此,从部署到配置,再到最终飞书通知,整个告警的流程咱们都动手实现了,希望这一系列实战操作能给您一些参考,助您搭建出匹配业务的告警系统