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)
}


相关文章
|
5月前
|
算法 Java Go
【GoGin】(1)上手Go Gin 基于Go语言开发的Web框架,本文介绍了各种路由的配置信息;包含各场景下请求参数的基本传入接收
gin 框架中采用的路优酷是基于httprouter做的是一个高性能的 HTTP 请求路由器,适用于 Go 语言。它的设计目标是提供高效的路由匹配和低内存占用,特别适合需要高性能和简单路由的应用场景。
505 4
|
10月前
|
Go C++
Go语言方法与接收者 -《Go语言实战指南》
本文介绍了 Go 语言中方法的相关概念和用法。方法是绑定到特定类型上的函数,包含值接收者和指针接收者两种形式。值接收者不会改变原始数据,而指针接收者可修改原始数据,且在处理大型结构体时性能更优。文章详细对比了方法与普通函数的区别,并说明了选择指针接收者的原因,如修改原始值、提升性能及保持一致性。此外,Go 支持为任意自定义类型定义方法,不仅限于结构体。最后通过表格总结了方法的核心概念和使用场景。
276 34
|
12月前
|
Prometheus Kubernetes 监控
Kubernetes监控:Prometheus与AlertManager结合,配置邮件告警。
完成这些步骤之后,您就拥有了一个可以用邮件通知你的Kubernetes监控解决方案了。当然,所有的这些配置都需要相互照应,还要对你的Kubernetes集群状况有深入的了解。希望这份指南能帮助你创建出适合自己场景的监控系统,让你在首次发现问题时就能做出响应。
710 22
|
Web App开发 存储 DataWorks
DataWorks产品使用合集之对实时同步任务设置告警时支持哪些告警接收方式
DataWorks作为一站式的数据开发与治理平台,提供了从数据采集、清洗、开发、调度、服务化、质量监控到安全管理的全套解决方案,帮助企业构建高效、规范、安全的大数据处理体系。以下是对DataWorks产品使用合集的概述,涵盖数据处理的各个环节。
|
存储 Prometheus Cloud Native
[prometheus]配置alertmanager和钉钉告警
[prometheus]配置alertmanager和钉钉告警
1581 0
|
JSON 分布式计算 DataWorks
DataWorks产品使用合集之能否支持从结果表取出示警信息并且打通钉钉进行告警
DataWorks作为一站式的数据开发与治理平台,提供了从数据采集、清洗、开发、调度、服务化、质量监控到安全管理的全套解决方案,帮助企业构建高效、规范、安全的大数据处理体系。以下是对DataWorks产品使用合集的概述,涵盖数据处理的各个环节。
262 0
如何理解Go语言中的值接收者和指针接收者?
Go语言中,函数和方法可使用值或指针接收者。值接收者是参数副本,内部修改不影响原值,如示例中`ChangeValue`无法改变`MyStruct`的`Value`。指针接收者则允许修改原值,因为传递的是内存地址。选择接收者类型应基于是否需要修改参数,值接收者用于防止修改,指针接收者用于允许修改。理解这一区别对编写高效Go代码至关重要。
298 0
|
10月前
|
运维 监控 网络协议
物联网设备状态监控全解析:从告警参数到静默管理的深度指南-优雅草卓伊凡
物联网设备状态监控全解析:从告警参数到静默管理的深度指南-优雅草卓伊凡
321 11
物联网设备状态监控全解析:从告警参数到静默管理的深度指南-优雅草卓伊凡
|
Prometheus 运维 监控
智能运维实战:Prometheus与Grafana的监控与告警体系
【10月更文挑战第26天】Prometheus与Grafana是智能运维中的强大组合,前者是开源的系统监控和警报工具,后者是数据可视化平台。Prometheus具备时间序列数据库、多维数据模型、PromQL查询语言等特性,而Grafana支持多数据源、丰富的可视化选项和告警功能。两者结合可实现实时监控、灵活告警和高度定制化的仪表板,广泛应用于服务器、应用和数据库的监控。
1477 3
|
12月前
|
数据采集 运维 监控
数据采集监控与告警:错误重试、日志分析与自动化运维
本文探讨了数据采集技术从“简单采集”到自动化运维的演进。传统方式因反爬策略和网络波动常导致数据丢失,而引入错误重试、日志分析与自动化告警机制可显著提升系统稳定性与时效性。正方强调健全监控体系的重要性,反方则担忧复杂化带来的成本与安全风险。未来,结合AI与大数据技术,数据采集将向智能化、全自动方向发展,实现动态调整与智能识别反爬策略,降低人工干预需求。附带的Python示例展示了如何通过代理IP、重试策略及日志记录实现高效的数据采集程序。
546 7
数据采集监控与告警:错误重试、日志分析与自动化运维