实现告警可视化:Prometheus告警附带图表最佳实践

科技   2024-10-30 14:39   上海  

背景概述

最近看到很多群里的小伙伴在讨论,当prometheus告警时,如何获取当前值以及如何附带最近指标的监控图标,当然我们的告警架构依旧使用prometheusalertmanager、钉钉hook。那么我们想要获取到当前值也好,还是图标也好,都需要首先获取到当前的promQL

效果展示

image-20241029191352371

告警通知,使用的是钉钉hook,模板可能不是很美观,主要是看效果。当然也是支持获取最新的告警value的。

前期准备

  1. goland
  2. golang sdk 1.22

大致实现思路

  1. 通过告警数据获取alertname
  2. 通过alertname获取promQL
  3. 通过client queryRange获取最近几分钟值
  4. 格式化获取到的value
  5. 图表渲染
  6. 本地测试

核心代码

我们仅测试使用,代码中并不是完整的代码哦,可以自行扩展。

func getAlertQuery(alertName string) *string {
 client := NewClient()
 ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
 defer cancel()
 v1api := v1.NewAPI(client)
 rules, err := v1api.Rules(ctx)
 if err != nil {
  return nil
 }
 for _, rule := range rules.Groups {
  for _, query := range rule.Rules {
   switch v := query.(type) {
   case v1.RecordingRule:
    if v.Name == alertName {
     return &v.Query
    }
   case v1.AlertingRule:
    if v.Name == alertName {
     return &v.Query
    }
   default:
    fmt.Printf("unknown rule type %s", v)
    return nil
   }
  }
 }
 return nil
}

这里主要是获取promQL

func getQueryRangeValue(query string) []*QueryValue {
 client := NewClient()
 ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
 defer cancel()
 v1api := v1.NewAPI(client)
 value, _, err := v1api.QueryRange(ctx, query, v1.Range{
  Start: time.Now().Add(10 * -time.Second),
  End:   time.Now(),
  Step:  1 * time.Second,
 })
 if err != nil {
  fmt.Printf("查询失败: %v\n", err)
 }
 //var tv = map[string]string{"instance": "ip-10-19-140-140.ap-southeast-1.compute.internal"}
 type tv model.Metric
 var queryValues []*QueryValue
 var tvvalue = tv{"instance""ip-10-19-140-140.ap-southeast-1.compute.internal"}
 switch v := value.(type) {
 case model.Matrix:
  for _, frame := range v {
   if frame.Metric.Equal(model.Metric(tvvalue)) {
    for _, v := range frame.Values {
     var queryValue = QueryValue{
      Value:     fmt.Sprintf("%s", v.Value),
      Timestamp: int64(v.Timestamp),
     }
     queryValues = append(queryValues, &queryValue)
    }
    return queryValues
   }
  }
 case model.Vector:
  fmt.Printf("%+v\n", v)
 case *model.Scalar:
  fmt.Printf("%+v\n", v)
 case *model.String:
  fmt.Printf("%+v\n", v)
 default:
  fmt.Println("unknown value type")
 }
 return nil
}

这里主要是通过promQL获取最近一段时间的value,这里的tv,我们是直接写死的,仅测试使用而已。

func image(values []*QueryValue, fingerprint string) {
 var (
  alertSals []float64
  Timestamp []time.Time
 )
 for _, v := range values {
  a := time.Unix(v.Timestamp/10000)
  Timestamp = append(Timestamp, a)
  fv, _ := strconv.ParseFloat(v.Value, 64)
  alertSals = append(alertSals, fv)
 }
 graph := chart.Chart{
  Title: "alert message",
  XAxis: chart.XAxis{
   ValueFormatter: chart.TimeValueFormatterWithFormat("15:04:05"),
  },
  YAxis: chart.YAxis{
   ValueFormatter: func(v interface{}) string {
    if vf, isFloat := v.(float64); isFloat {
     return fmt.Sprintf("%.3f", vf)
    }
    return ""
   },
  },
  Series: []chart.Series{
   chart.TimeSeries{
    Name:    "安若",
    XValues: Timestamp,
    YValues: alertSals,
   },
  },
 }
 // 创建输出文件
 f, err := os.Create("output.png")
 if err != nil {
  fmt.Println("Error creating file:", err)
  return
 }
 defer f.Close()

 // 渲染图表到文件
 err = graph.Render(chart.PNG, f)
 if err != nil {
  fmt.Println("Error rendering chart:", err)
 }
}

图片渲染

我们可以直接通过浏览器访问图片

image-20241029192138659

总结

以上就是简单的对prometheus告警附带图表的实现,当然这里还有一些不完善的地方需要自己去修改一下哦,例如图片的name,我们可以使用告警指纹进行命名。

添加👇下面微信,拉你进群与大佬一起探讨云原生!

安若


云原生运维圈
专注于Docker、Kubernetes、Prometheus、Istio、Terraform、OpenTelemetry等云原生技术分享!
 最新文章