共计 4363 个字符,预计需要花费 11 分钟才能阅读完成。
背景
公司最近使用了火山引擎的云拨测服务,用于观测 url 在各个地域的可用性并发送告警。
一开始是直接登录控制台查看任务监控数据,在云上配置告警。
实现方式不够优雅,没有将数据集成到我们自己的可观测平台,监控和告警都与现有SRE体系割裂。
实践
从公有云收集数据
云拨测的监控指标很多,这里以 可用率
为例。
查阅 火山引擎云拨测API参考,返回的数据为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))
}
数据处理
有两种方式:
- 直接在 Grafana 中解析 json
- 需要安装额外插件,例如:Grafana 插件:infinity 用于解析 json
-
需要维护中间层,提供一个 http 接口用于从公有云获取 json 并暴露
- 在 Grafana 绘图局限较大,插件对于许多 panel 样式支持不好
-
没有数据存储层,只能查询实时数据
-
将 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
首先需要选择插件,以 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,但是维护成本更高。需要安装插件、维护中间层提供HTTP接口、用不熟悉的方式实现查询、无法提供持久化数据存储等。
还是建议在代码逻辑内将 json 转换为 Prometheus 指标格式,契合原有技术栈。