Grafana 解析 json 数据

277次阅读
没有评论

共计 4363 个字符,预计需要花费 11 分钟才能阅读完成。

背景

公司最近使用了火山引擎的云拨测服务,用于观测 url 在各个地域的可用性并发送告警。

一开始是直接登录控制台查看任务监控数据,在云上配置告警。

实现方式不够优雅,没有将数据集成到我们自己的可观测平台,监控和告警都与现有SRE体系割裂。

实践

从公有云收集数据

云拨测的监控指标很多,这里以 可用率​ 为例。

查阅 火山引擎云拨测API参考,返回的数据为json格式。

Grafana 解析 json 数据

由于SDK尚未支持云拨测,我们直接参考 HTTP 调用范例,构造请求获取数据:

func main() {
    // 获取即席分析数据
    payload := map[string]interface{}{
       "start_time":  1695020304,
       "end_time":    1695279504,
       "granularity": "hour",
       "filters":     nil,
       "groups": []map[string]interface{}{
          {"key": "timestamp"},
       },
       "measures": []map[string]interface{}{
          {"key": "success.proportion"},
       },
    }
    body, _ := json.Marshal(payload)
    responseBody, err := requestCloudDetect("POST", map[string]string{}, AccessKey, SecretAccessKey, "GetOlapData", body)
    if err != nil {
       fmt.Printf("do request failed: %v", err)
       return
    }
    // 打印输出的结果
    fmt.Println(string(responseBody))
}

数据处理

有两种方式:

  1. 直接在 Grafana 中解析 json
    • 需要安装额外插件,例如:Grafana 插件:infinity 用于解析 json

    • 需要维护中间层,提供一个 http 接口用于从公有云获取 json 并暴露

    • 在 Grafana 绘图局限较大,插件对于许多 panel 样式支持不好
    • 没有数据存储层,只能查询实时数据

  2. 将 json 转换为为 Prometheus 的指标格式

    • 代码逻辑需要处理返回的 json

    • 数据可以持久存储在prometheus,提供给 Grafana 或 alertmanger等监控报警直接消费,不需要再额外处理

    • 绘图灵活

综上,最终选用了第二种方式,当然第一种方式我也会大概谈谈如何实现。

json –> prometheus metrics

转换为 Prometheus 指标不困难,转换够如何上报也有多种方式:

  • 实现一个标准 exporter,/metrics​ 用于暴露转换后的数据。需要单独在 Prometheus 中配置 job
  • 直接使用现有 exporter,例如 node_exporter 支持从文件中直接采集数据。参考站内文章:node_exporter 添加自定义指标

为了方便,还是采取 node_exporter 方式进行实现。

golang 代码 :

  • 签名过程不赘述,直接参考官网范例:使用 HTTP 调用云拨测 API
  • 抓取 1分钟 内的数据
  • 分组条件有4个;job.name:任务名​、client.province.name:节点省份​、client.city.name:节点城市​、client_isp_name:节点运营商
  • 格式化数据,输出到 node_exporer 的 collector.textfile.directory​ 路径下
/* main.go */
type ResponseMetadata struct {
    RequestID string `json:"RequestId"`
    Action    string `json:"Action"`
    Version   string `json:"Version"`
    Service   string `json:"Service"`
    Region    string `json:"Region"`
}

type Group struct {
    Key         string `json:"key"`
    Value       string `json:"value"`
    OriginValue string `json:"origin_value"`
}

type Measure struct {
    Key   string `json:"key"`
    Value string `json:"value"`
}

type Result struct {
    Groups   []Group   `json:"groups"`
    Measures []Measure `json:"measures"`
}

type Response struct {
    ResponseMetadata ResponseMetadata `json:"ResponseMetadata"`
    Result           []Result         `json:"Result"`
}

func main() {
    // 获取当前时间
    now := time.Now().Unix()

    // 计算start_time和end_time
    start_time := now - 60 // 当前时间减去60秒
    end_time := now        // 当前时间

    // 获取即席分析数据
    payload := map[string]interface{}{
        "start_time":  start_time,
        "end_time":    end_time,
        "granularity": "minute",
        "filters": []map[string]interface{}{
            {
                "key":    "job.status.codes",
                "type":   "not in",
                "values": []string{"99", "299"},
            },
        },
        "groups": []map[string]interface{}{
            {"key": "job.name"},
            {"key": "client.province.name"},
            {"key": "client.city.name"},
            {"key": "client_isp_name"},
        },
        "measures": []map[string]interface{}{
            {"key": "success.proportion"},
        },
    }
    body, _ := json.Marshal(payload)
    responseBody, err := requestCloudDetect("POST", map[string]string{}, AccessKey, SecretAccessKey, "GetOlapData", body)
    if err != nil {
        fmt.Printf("do request failed: %v", err)
        return
    }

    // 将响应体反序列化为Response结构体
    var response Response
    err = json.Unmarshal(responseBody, &response)
    if err != nil {
        fmt.Println("Failed to unmarshal JSON data:", err)
        return
    }

    // 打开文件以写入Prometheus格式的数据
    file, err := os.Create("/var/lib/node_exporter/huoshanyun_boce.prom")
    if err != nil {
        fmt.Println("Failed to create file:", err)
        return
    }
    defer file.Close()

    // 写入HELP和TYPE行
    _, err = file.WriteString("# HELP volcengine_cloud_detect_success_proportion\n")
    if err != nil {
        fmt.Println("Failed to write to file:", err)
        return
    }
    _, err = file.WriteString("# TYPE volcengine_cloud_detect_success_proportion gauge\n")
    if err != nil {
        fmt.Println("Failed to write to file:", err)
        return
    }

    // 遍历Result数组,生成Prometheus格式的数据并写入文件
    for _, result := range response.Result {
        labels := make([]string, 0)
        for _, group := range result.Groups {
            labelKey := strings.ReplaceAll(group.Key, ".", "_")
            labels = append(labels, fmt.Sprintf("%s=\"%s\"", labelKey, strings.Trim(group.Value, "\"")))
        }
        value := result.Measures[0].Value
        line := fmt.Sprintf("volcengine_cloud_detect_success_proportion{%s} %s\n", strings.Join(labels, ","), value)
        _, err = file.WriteString(line)
        if err != nil {
            fmt.Println("Failed to write to file:", err)
            return
        }
    }

    fmt.Println("Prometheus data written to /var/lib/node_exporter/huoshanyun_boce.prom")
}

配置计划任务,每分钟执行一次:

*/1 * * * * /usr/local/go1.23.0/bin/go run /data/scripts/huoshanyun_boce.go

对于 Grafana 配置 Prometheus 数据源绘图,也不再赘述。最终效果图:

Grafana 解析 json 数据

在 Grafana 中解析 json

首先需要选择插件,以 Infinity​ 为例:yesoreyeram-infinity-datasource

安装插件:

# 安装
grafana-cli plugins install yesoreyeram-infinity-datasource

# 重启以应用
systemctl restart grafana-server.service

安装完毕后在 grafana 可以创建 yesoreyeram-infinity-datasource​ 类型的数据源。

首先添加 url,该url需要暴露json数据:

Grafana 解析 json 数据

解析具体字段或者更高级的查询方式,参考插件官方文档说明。

总结

Grafana 可以利用第三方插件完成解析 json,但是维护成本更高。需要安装插件、维护中间层提供HTTP接口、用不熟悉的方式实现查询、无法提供持久化数据存储等。

还是建议在代码逻辑内将 json 转换为 Prometheus 指标格式,契合原有技术栈。

引用链接

正文完
 
pengyinwei
版权声明:本站原创文章,由 pengyinwei 2024-08-20发表,共计4363字。
转载说明:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处:https://www.opshub.cn
评论(没有评论)