go接收alertmanager告警并发送钉钉

简介: go接收alertmanager告警并发送钉钉

前言

  • 功能:作为 alertmanager 的 webhook receiver,提取需要的数据转发到钉钉群机器人的webhook
  • web框架:gin
  • alertmanager版本:0.24
  • 系统版本:ubuntu 20.04
  • 功能比较简单,所以就随便写了,全部属于main

原始json数据示例

{
    "receiver": "web\\.hook",
    "status": "firing",
    "alerts": [{
        "status": "firing",
        "labels": {
            "alertname": "node",
            "instance": "192.168.0.10",
            "job": "rh7",
            "severity": "critical"
        },
        "annotations": {
            "description": "rh7 192.168.0.10 节点断联已超过1分钟!",
            "summary": "192.168.0.10 down "
        },
        "startsAt": "2022-04-28T08:44:23.05Z",
        "endsAt": "0001-01-01T00:00:00Z",
        "generatorURL": "http://localhost.localdomain:19090/graph?g0.expr=up+%3D%3D+0\u0026g0.tab=1",
        "fingerprint": "726681bf4674e8a5"
    }],
    "groupLabels": {
        "alertname": "node"
    },
    "commonLabels": {
        "alertname": "node",
        "instance": "192.168.0.10",
        "job": "rh7",
        "severity": "critical"
    },
    "commonAnnotations": {
        "description": "rh7 192.168.0.10 节点断联已超过1分钟!",
        "summary": "192.168.0.10 down "
    },
    "externalURL": "http://192.168.0.10:19092",
    "version": "4",
    "groupKey": "{}:{alertname=\"node\"}",
    "truncatedAlerts": 0
}

示例代码

  • model.go(定义结构体)
package main
// 定义接收JSON数据的结构体
type ReqAlert struct {
  Status       string              `json:"status"`
  StartsAt     string              `json:"startsAt"`
  EndsAt       string              `json:"endsAt"`
  GeneratorURL string              `json:"generatorURL"`
  Fingerprint  string              `json:"fingerprint"`
  Labels       ReqAlertLabel       `json:"labels"`
  Annotations  ReqAlertAnnotations `json:"annotations"`
}
type ReqGroupLabels struct {
  Alertname string `json:"alertname"`
}
type ReqCommonLabels struct {
  Alertname string `json:"alertname"`
  Instance  string `json:"instance"`
  Job       string `json:"job"`
  Severity  string `json:"severity"`
}
type ReqCommonAnnotations struct {
  Description string `json:"description"`
  Summary     string `json:"summary"`
}
type ReqAlertLabel struct {
  Alertname string `json:"alertname"`
  Instance  string `json:"instance"`
  Job       string `json:"job"`
  Severity  string `json:"severity"`
}
type ReqAlertAnnotations struct {
  Description string `json:"description"`
  Summary     string `json:"summary"`
}
type RequestBody struct {
  // alertmanager传来的请求体
  Receiver          string               `json:"receiver"`
  Status            string               `json:"status"`
  ExternalURL       string               `json:"externalURL"`
  Version           string               `json:"version"`
  GroupKey          string               `json:"groupkey"`
  TruncatedAlerts   float64              `json:"truncatedAlerts"`
  Alert             []ReqAlert           `json:"alerts"`
  GroupLabels       ReqGroupLabels       `json:"groupLabels"`
  CommonLabels      ReqCommonLabels      `json:"commonLabels"`
  CommonAnnotations ReqCommonAnnotations `json:"commonAnnotations"`
}
  • dingmsg.go(对接钉钉webhook,注意替换webhook的url)
package main
import (
  "bytes"
  "encoding/json"
  "fmt"
  "io/ioutil"
  "log"
  "net/http"
  "strings"
)
type Md struct {
  Title string `json:"title"`
  Text  string `json:"text"`
}
type Ding struct {
  Msgtype  string `json:"msgtype"`
  Markdown Md     `json:"markdown"`
}
func DingMarshal(text string) string {
  // struct序列化为json
  var myjson Ding = Ding{
    Msgtype: "markdown",
    Markdown: Md{
      Title: "锄田测试钉钉机器人",
      Text:  text,
    },
  }
  va, err := json.Marshal(myjson)
  if err != nil {
    panic(err)
  }
  return string(va)
}
func PostUrl(jsondata string) {
  // 发起post请求
  // 替换钉钉群机器人的webhook
  url := "https://oapi.dingtalk.com/robot/send?access_token=123456"
  var jsonstr = []byte(jsondata)
  req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonstr))
  if err != nil {
    log.Fatal(err)
  }
  req.Header.Set("Content-Type", "application/json;charset=utf-8")
  client := &http.Client{}
  resp, err := client.Do(req)
  if err != nil {
    log.Fatal(err)
  }
  defer resp.Body.Close()
  body, err := ioutil.ReadAll(resp.Body)
  if err != nil {
    log.Fatal(err)
  }
  fmt.Printf("钉钉webhook响应: %v\n", string(body))
}
func SpliceString(status, description, summary string) string {
  // 拼接字符串
  var rst strings.Builder
  rst.WriteString("# 锄田测试钉钉机器人 \n\n")
  rst.WriteString("- status: " + status + " \n\n")
  rst.WriteString("- description: " + description + " \n\n")
  rst.WriteString("- summary: " + summary + " \n\n")
  return rst.String()
}
  • main.go
package main
import (
  "context"
  "errors"
  "fmt"
  "io"
  "log"
  "net/http"
  "os"
  "os/signal"
  "syscall"
  "time"
  "github.com/gin-gonic/gin"
)
func ParseJson(c *gin.Context) {
  // 解析AlertManager 传递的 json 数据
  var json RequestBody
  // 将数据解析到结构体中
  if err := c.ShouldBindJSON(&json); err != nil {
    // 返回错误信息
    c.JSON(http.StatusBadRequest, gin.H{
      "error": err.Error(),
    })
    return
  }
  // 遍历json中alerts数据
  for k, v := range json.Alert {
    fmt.Printf("k: %d, status: %s, description: %s, summary: %s \n", k, v.Status, v.Annotations.Description, v.Annotations.Summary)
    text := SpliceString(v.Status, v.Annotations.Description, v.Annotations.Summary)
    va := DingMarshal(text)
    PostUrl(va)
  }
  // 返回给客户端的消息
  c.JSON(http.StatusOK, gin.H{
    "msg": "success",
  })
}
func main() {
  // 设置gin运行模式,可选:DebugMode、ReleaseMode、TestMode
  gin.SetMode(gin.ReleaseMode)
  // 关闭控制台日志颜色
  gin.DisableConsoleColor()
  // 记录到文件
  f, _ := os.OpenFile("gin.log", os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0644)
  gin.DefaultWriter = io.MultiWriter(f)
  r := gin.Default()
  // 只接受来自127.0.0.1的请求
  r.SetTrustedProxies([]string{"127.0.0.1"})
  v1 := r.Group("/v1")
  {
    v1.POST("/alert", ParseJson)
  }
  // 设置程序优雅退出
  srv := &http.Server{
    Addr:    "127.0.0.1:19100",
    Handler: r,
  }
  go func() {
    if err := srv.ListenAndServe(); err != nil && errors.Is(err, http.ErrServerClosed) {
      log.Printf("listen: %s\n", err)
    }
  }()
  quit := make(chan os.Signal)
  signal.Notify(quit, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
  <-quit
  log.Println("Shutting down server...")
  ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
  defer cancel()
  if err := srv.Shutdown(ctx); err != nil {
    log.Fatal("Server forced to shutdown:", err)
  }
  log.Println("Server exiting")
}

使用示例

  1. 导入依赖
go mod tidy
  1. 编译
go build -o goalert.bin ./main.go ./model.go ./dingmsg.go
  1. 运行
./goalert.bin
  1. 测试。使用python + requests 测试。python代码如下
import requests
# 注意json字符串的代码转为utf-8,否则会报Unicode的错误
# 使用json原字符串,而不是字典
json_firing = r"""
{"receiver":"web\\.hook","status":"firing","alerts":[{"status":"firing","labels":{"alertname":"node","instance":"192.168.0.10","job":"rh7","severity":"critical"},"annotations":{"description":"rh7 192.168.0.10 节点断联已超过1分钟!","summary":"192.168.0.10 down "},"startsAt":"2022-04-28T08:44:23.05Z","endsAt":"0001-01-01T00:00:00Z","generatorURL":"http://localhost.localdomain:19090/graph?g0.expr=up+%3D%3D+0\u0026g0.tab=1","fingerprint":"726681bf4674e8a5"}],"groupLabels":{"alertname":"node"},"commonLabels":{"alertname":"node","instance":"192.168.0.10","job":"rh7","severity":"critical"},"commonAnnotations":{"description":"rh7 192.168.0.10 节点断联已超过1分钟!","summary":"192.168.0.10 down "},"externalURL":"http://192.168.0.10:19092","version":"4","groupKey":"{}:{alertname=\"node\"}","truncatedAlerts":0}
""".encode("utf-8").decode("latin1")
url = "http://127.0.0.1:19100/v1/alert"
resp = requests.post(url=url, data = json_firing, headers={"Content-Type": "application/json"})
print(resp.text)
  1. alertmanager配置略过

补充

  • 钉钉可以通过面对面建群的方式建一个单人群,单人群也能添加机器人。
  • 如果不知道alertmanager发送的json数据是什么样,可以写个服务端,直接接收数据不解析,在控制台原样输出字符串。示例代码如下:
package main
import (
  "fmt"
  "io/ioutil"
  "net/http"
)
func f1(w http.ResponseWriter, r *http.Request) {
  // 向客户端响应ok
  defer fmt.Fprintf(w, "ok\n")
  // 获取客户端的请求方式
  fmt.Println("method:", r.Method)
  // 获取客户端请求的body
  body, err := ioutil.ReadAll(r.Body)
  if err != nil {
    fmt.Printf("read body err, %v\n", err)
    return
  }
  fmt.Println("json: ", string(body))
}
func main() {
  http.HandleFunc("/", f1)
  http.ListenAndServe("127.0.0.1:8888", nil)
}


相关文章
|
7月前
|
缓存
ecs-centos分区空间大于70时发送钉钉告警并清理
当分区空间大于70时,开始清理并发送钉钉告警。
98 1
|
7月前
|
机器人 关系型数据库 MySQL
shell脚本实现文件自动清理并推送钉钉机器人告警
shell脚本实现文件自动清理并推送钉钉机器人告警
121 1
|
7月前
|
运维 监控 安全
调用钉钉机器人API接口将堡垒机安全运维告警单发给运维人员
调用钉钉机器人API接口将堡垒机安全运维告警单发给运维人员
204 0
|
7月前
|
机器人 API
钉钉机器人的运行和消息接收是通过Webhook和Stream两种方式实现的
钉钉机器人的运行和消息接收是通过Webhook和Stream两种方式实现的
543 5
|
4月前
|
存储 Prometheus Cloud Native
[prometheus]配置alertmanager和钉钉告警
[prometheus]配置alertmanager和钉钉告警
222 0
|
6月前
|
JSON 分布式计算 DataWorks
DataWorks产品使用合集之能否支持从结果表取出示警信息并且打通钉钉进行告警
DataWorks作为一站式的数据开发与治理平台,提供了从数据采集、清洗、开发、调度、服务化、质量监控到安全管理的全套解决方案,帮助企业构建高效、规范、安全的大数据处理体系。以下是对DataWorks产品使用合集的概述,涵盖数据处理的各个环节。
|
6月前
|
Go
如何理解Go语言中的值接收者和指针接收者?
Go语言中,函数和方法可使用值或指针接收者。值接收者是参数副本,内部修改不影响原值,如示例中`ChangeValue`无法改变`MyStruct`的`Value`。指针接收者则允许修改原值,因为传递的是内存地址。选择接收者类型应基于是否需要修改参数,值接收者用于防止修改,指针接收者用于允许修改。理解这一区别对编写高效Go代码至关重要。
|
7月前
|
Go 开发者
Golang深入浅出之-Go语言方法与接收者:面向对象编程初探
【4月更文挑战第22天】Go语言无类和继承,但通过方法与接收者实现OOP。方法是带有接收者的特殊函数,接收者决定方法可作用于哪些类型。值接收者不会改变原始值,指针接收者则会。每个类型有相关方法集,满足接口所有方法即实现该接口。理解并正确使用这些概念能避免常见问题,写出高效代码。Go的OOP机制虽不同于传统,但具有灵活性和实用性。
54 1
|
7月前
|
监控 机器人 BI
基于钉钉的阿里云管理实践(二)之消息接收
在企业云资源的日常管理中,实时监控和报警通知对于维持系统稳定性至关重要。阿里云提供了一系列的监控服务,可以对云资源进行实时监控,并在出现问题时发送报警。将这些报警消息及时推送到钉钉,可以让团队成员迅速响应,采取必要的措施。本教程将指导您如何将阿里云账号的报警监控消息推送到钉钉上。
268 1
基于钉钉的阿里云管理实践(二)之消息接收
|
7月前
|
安全 机器人 Shell
shell脚本实现Linux磁盘空间超过阈值自动钉钉机器人告警
shell脚本实现Linux磁盘空间超过阈值自动钉钉机器人告警
136 0