prometheus实战之五:飞书通知告警

欢迎访问我的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,是因为它提供了命令可以快速创建工程,仅此而已
  • 动手写代码之前,先梳理好编码的具体步骤
graph TD A(1. 创建工程) --> B(2. 搭好web框架) --> C(3. 定义数据结构:alertmanager的请求体) --> D(4. 定义数据结构:飞书消息的请求体和响应) --> E(5. 转换器,将alertmanager请求体转换为飞书请求体) --> F(6. 主逻辑,收到alertmanager请求时向飞书发请求) --> G(7. 路由配置,将web path和主逻辑绑定)
  • 接下来的操作就按照上述步骤进行,先来创建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(&notification)
	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状态,就表示故障已恢复

  • 至此,从部署到配置,再到最终飞书通知,整个告警的流程咱们都动手实现了,希望这一系列实战操作能给您一些参考,助您搭建出匹配业务的告警系统

欢迎关注掘金:程序员欣宸

学习路上,你不孤单,欣宸原创一路相伴...

相关推荐
程序员码歌1 分钟前
短思考第264天,每天复盘5分钟,胜过你盲目努力1整年(2)
前端·后端·ai编程
Victor3565 分钟前
Hibernate(3)Hibernate的优点是什么?
后端
Victor3566 分钟前
Hibernate(4)什么是Hibernate的持久化类?
后端
JaguarJack8 分钟前
PHP True Async 最近进展以及背后的争议
后端·php
想不明白的过度思考者2 小时前
Spring Boot 配置文件深度解析
java·spring boot·后端
WanderInk8 小时前
刷新后点赞全变 0?别急着怪 Redis,这八成是 Long 被 JavaScript 偷偷“改号”了(一次线上复盘)
后端
吴佳浩9 小时前
Python入门指南(七) - YOLO检测API进阶实战
人工智能·后端·python
廋到被风吹走9 小时前
【Spring】常用注解分类整理
java·后端·spring