使用Golang编写自定义Prometheus Metrics

网络 通信技术
为什么想到要用golang来编写metrics呢?这主要是我们的一个客户那里,k8s网络使用了ovs,并且做了bond,即bond0和bond1,每个bond下面2张网卡。

本文转载自微信公众号「运维开发故事」,作者xiyangxixia。转载本文请联系运维开发故事公众号。

一、前言

为什么想到要用golang来编写metrics呢?这主要是我们的一个客户那里,k8s网络使用了ovs,并且做了bond,即bond0和bond1,每个bond下面2张网卡。在上了生产后,让我每天都要检查一下网卡是否正常,因为之前就有网卡DOWN了。而我呢,比较懒,不想手动去检查。想着通过prometheus最终展示到grafana,我就在grafana上看看有没有处于异常的网卡就好了。其次呢,我最近刚好在学习go,也想练练手;同时也问了一下研发同学,说很简单,叫我试试,遇到困难时也愿意帮助我。所以,我就打算试试了。

二、环境

组件 版本 备注
k8s v1.14  
ovs v2.9.5  
go 1.14.1  

三、目标

目标就是要通过prometheus去拉取我的ovs bond的网卡状态指标,那么这里我需要编写一个go程序去获取我主机的ovs bond信息,并最终以metrics方式暴露供prometheus来拉取,在grafana上展示。示例如下:

  1. # 现获取当前bond信息 
  2. [root@test~]$ ovs-appctl  bond/show |grep '^slave' |grep -v grep  |awk '{print $2""$3}' 
  3. a1-b1:enabled 
  4. a2-b2:enabled 
  5. a3-b3:enabled 
  6. a4-b4:disabled 
  7. # 最终组件暴露的数据如下 5代表获取bond信息的命令执行执行失败了,0-4表示有几张处于disabled状态的网卡 
  8. curl http://$IP:$PORT/metrics  
  9. ovs_bond_status{component="ovs"} 5 
  10. ovs_bond_status{component="ovs","a1b1"="enabled","a2b2"="disabled","a3b3"="enabled",a4b4="disabled“} 2 

四、构想

由于要通过prometheus来抓取指标,所以bond 信息肯定要以metrics格式进行暴露。metrics格式可以参考prometheus官网。

bond有两个,每个下面有两张网卡,每张网卡的状态只有enabled和disabled,因此用数字0-4来告诉用户有几张网卡disabled了,用数字5来表示命令执行有问题或没有bond,需要人工介入。可以通过命令去获取bond信息,因此还是采取命令方式去获取。

要对执行命令获取的输出结果进行处理并放到metrics中去。注:metrics的label不能有【-】。

shell命令返回的bond正确信息用map去接收,key为网卡名,value为网卡状态

可以参考client_golang/prometheus

五、实践

先执行shell命令去获取bond信息

  1. # 现获取当前bond信息 
  2. [root@test~]$ ovs-appctl  bond/show |grep '^slave' |grep -v grep  |awk '{print $2""$3}' 
  3. a1-b1:enabled 
  4. a2-b2:enabled 
  5. a3-b3:enabled 
  6. a4-b4:disabled 

要针对shell的输出结果进行处理

  1. # 执行shell命令,并对输出进行处理,记录相关日志 
  2. // return map  
  3. // 一种是执行命令错误,一种是执行命令成功,但是返回null 
  4. func getBondStatus() (m map[string]string) { 
  5.  result, err := exec.Command("bash""-c""ovs-appctl bond/show | grep '^slave' | grep -v grep | awk '{print $2\"\"$3}'").Output() 
  6.  if err != nil { 
  7.   log.Error("result: ", string(result)) 
  8.   log.Error("command failed: ", err.Error()) 
  9.   m = make(map[string]string) 
  10.   m["msg"] = "failure" 
  11.   return m 
  12.  } else if len(result) == 0 { 
  13.   log.Error("command exec failed, result is null"
  14.   m = make(map[string]string) 
  15.   m["msg"] = "return null" 
  16.   return m 
  17.  } 
  18. // 对结果进行进行处理,先去除两边空格 
  19.  ret := strings.TrimSpace(string(result)) 
  20.     // 通过换行符切割 
  21.  tt := strings.Split(ret, "\n"
  22.  //tt := []string{"a1-b1:enabled","a2-b2:disabled"
  23.     //如果key带有【-】,则需要去掉 
  24.  var nMap = make(map[string]string) 
  25.  for i := 0; i < len(tt); i++ { 
  26.   // if key contains "-" 
  27.   if strings.Contains(tt[i], "-") == true { 
  28.    nKey := strings.Split(strings.Split(tt[i], ":")[0], "-"
  29.    nMap[strings.Join(nKey, "")] = (strings.Split(tt[i], ":"))[1] 
  30.   } else { 
  31.    nMap[(strings.Split(tt[i], ":"))[0]] = (strings.Split(tt[i], ":"))[1] 
  32.   } 
  33.  } 
  34.  return nMap 

定义metrics指标

  1. // define a struct 
  2. type ovsCollector struct { 
  3.     // 可以定义多个 
  4.  ovsMetric *prometheus.Desc 
  5.  
  6. func (collector *ovsCollector) Describe(ch chan<- *prometheus.Desc) { 
  7.  ch <- collector.ovsMetric 
  8.  
  9. // 网卡名 
  10. var vLable = []string{} 
  11. // 网卡状态 
  12. var vValue = []string{} 
  13. // 固定label,表明是ovs 
  14. var constLabel = prometheus.Labels{"component""ovs"
  15.  
  16. // define metric 
  17. func newOvsCollector() *ovsCollector { 
  18.  var rm = make(map[string]string) 
  19.  rm = getBondStatus() 
  20.  if _, ok := rm["msg"]; ok { 
  21.   log.Error("command execute failed:", rm["msg"]) 
  22.  } else { 
  23.         //只获取网卡名 
  24.   for k, _ := range rm { 
  25.    // get the net 
  26.    vLable = append(vLable, k) 
  27.   } 
  28.  } 
  29.     // metric 
  30.  return &ovsCollector{ 
  31.   ovsMetric: prometheus.NewDesc("ovs_bond_status"
  32.    "Show ovs bond status", vLable, 
  33.    constLabel), 
  34.  } 

指标对应值

  1. // 命令执行正确则将对应的网卡、网卡状态以及处于异常的网卡数量注入到到metrics中去 
  2. func (collector *ovsCollector) Collect(ch chan<- prometheus.Metric) { 
  3.  var metricValue float64 
  4.  var rm = make(map[string]string) 
  5.  rm = getBondStatus() 
  6.  if _, ok := rm["msg"]; ok { 
  7.   log.Error("command exec failed"
  8.   metricValue = 5 
  9.   ch <- prometheus.MustNewConstMetric(collector.ovsMetric, prometheus.CounterValue, metricValue) 
  10.  } else { 
  11.   vValue = vValue[0:0] 
  12.         //只取value 
  13.   for _, v := range rm { 
  14.    // get the net 
  15.    vValue = append(vValue, v) 
  16.             // 针对disabled计数 
  17.    if v == "disabled" { 
  18.     metricValue++ 
  19.    } 
  20.   } 
  21.   ch <- prometheus.MustNewConstMetric(collector.ovsMetric, prometheus.CounterValue, metricValue, vValue...) 
  22.  } 

程序入口

  1. func main() { 
  2.  ovs := newOvsCollector() 
  3.  prometheus.MustRegister(ovs) 
  4.  
  5.  http.Handle("/metrics", promhttp.Handler()) 
  6.  
  7.  log.Info("begin to server on port 8080"
  8.  // listen on port 8080 
  9.  log.Fatal(http.ListenAndServe(":8080", nil)) 

完整代码

  1. package main 
  2.  
  3. import ( 
  4.  "github.com/prometheus/client_golang/prometheus" 
  5.  "github.com/prometheus/client_golang/prometheus/promhttp" 
  6.  log "github.com/sirupsen/logrus" 
  7.  "net/http" 
  8.  "os/exec" 
  9.  "strings" 
  10.  
  11. // define a struct   from prometheus's struct  named Desc 
  12. type ovsCollector struct { 
  13.  ovsMetric *prometheus.Desc 
  14.  
  15. func (collector *ovsCollector) Describe(ch chan<- *prometheus.Desc) { 
  16.  ch <- collector.ovsMetric 
  17.  
  18. var vLable = []string{} 
  19. var vValue = []string{} 
  20. var constLabel = prometheus.Labels{"component""ovs"
  21.  
  22. //  get the value of the metric from a function who would execute a command and return a float64 value 
  23. func (collector *ovsCollector) Collect(ch chan<- prometheus.Metric) { 
  24.  var metricValue float64 
  25.  var rm = make(map[string]string) 
  26.  rm = getBondStatus() 
  27.  if _, ok := rm["msg"]; ok { 
  28.   log.Error("command exec failed"
  29.   metricValue = 5 
  30.   ch <- prometheus.MustNewConstMetric(collector.ovsMetric, prometheus.CounterValue, metricValue) 
  31.  } else { 
  32.   vValue = vValue[0:0] 
  33.   for _, v := range rm { 
  34.    // get the net 
  35.    vValue = append(vValue, v) 
  36.    if v == "disabled" { 
  37.     metricValue++ 
  38.    } 
  39.   } 
  40.  
  41.  
  42.   ch <- prometheus.MustNewConstMetric(collector.ovsMetric, prometheus.CounterValue, metricValue, vValue...) 
  43.  } 
  44.  
  45. // define metric's name、help 
  46. func newOvsCollector() *ovsCollector { 
  47.  var rm = make(map[string]string) 
  48.  rm = getBondStatus() 
  49.  if _, ok := rm["msg"]; ok { 
  50.   log.Error("command execute failed:", rm["msg"]) 
  51.  } else { 
  52.   for k, _ := range rm { 
  53.    // get the net 
  54.    vLable = append(vLable, k) 
  55.   } 
  56.  } 
  57.  return &ovsCollector{ 
  58.   ovsMetric: prometheus.NewDesc("ovs_bond_status"
  59.    "Show ovs bond status", vLable, 
  60.    constLabel), 
  61.  } 
  62.  
  63. func getBondStatus() (m map[string]string) { 
  64.  result, err := exec.Command("bash""-c""ovs-appctl bond/show | grep '^slave' | grep -v grep | awk '{print $2\"\"$3}'").Output() 
  65.  if err != nil { 
  66.   log.Error("result: ", string(result)) 
  67.   log.Error("command failed: ", err.Error()) 
  68.   m = make(map[string]string) 
  69.   m["msg"] = "failure" 
  70.   return m 
  71.  } else if len(result) == 0 { 
  72.   log.Error("command exec failed, result is null"
  73.   m = make(map[string]string) 
  74.   m["msg"] = "return null" 
  75.   return m 
  76.  } 
  77.  ret := strings.TrimSpace(string(result)) 
  78.  tt := strings.Split(ret, "\n"
  79.  var nMap = make(map[string]string) 
  80.  for i := 0; i < len(tt); i++ { 
  81.   // if key contains "-" 
  82.   if strings.Contains(tt[i], "-") == true { 
  83.    nKey := strings.Split(strings.Split(tt[i], ":")[0], "-"
  84.    nMap[strings.Join(nKey, "")] = (strings.Split(tt[i], ":"))[1] 
  85.   } else { 
  86.    nMap[(strings.Split(tt[i], ":"))[0]] = (strings.Split(tt[i], ":"))[1] 
  87.   } 
  88.  } 
  89.  return nMap 
  90.  
  91. func main() { 
  92.  ovs := newOvsCollector() 
  93.  prometheus.MustRegister(ovs) 
  94.  
  95.  http.Handle("/metrics", promhttp.Handler()) 
  96.  
  97.  log.Info("begin to server on port 8080"
  98.  // listen on port 8080 
  99.  log.Fatal(http.ListenAndServe(":8080", nil)) 

六、部署

因为最终要部署到k8s环境中, 先构建镜像,参考如下Dockerfile

  1. FROM golang:1.14.1 AS builder 
  2. WORKDIR /go/src 
  3. COPY ./ . 
  4. RUN go build -o ovs_check main.go 
  5.  
  6. # runtime 
  7. FROM centos:7.7 
  8. COPY --from=builder /go/src/ovs_check /xiyangxixia/ovs_check 
  9. ENTRYPOINT ["/xiyangxixia/ovs_check"

我这里部署使用的yaml如下所示:

  1. --- 
  2. apiVersion: apps/v1 
  3. kind: DaemonSet 
  4. metadata: 
  5.   name: ovs-agent 
  6.   namespace: kube-system 
  7. spec: 
  8.   minReadySeconds: 5 
  9.   selector: 
  10.     matchLabels: 
  11.       name: ovs-agent 
  12.   template: 
  13.     metadata: 
  14.       annotations: 
  15.       # 这里三个都要加上,告诉promethue抓取路径 
  16.         prometheus.io/scrape: "true" 
  17.         prometheus.io/port: "8080" 
  18.         prometheus.io/path: "/metrics" 
  19.       labels: 
  20.         name: ovs-agent 
  21.     spec: 
  22.       containers: 
  23.       - name: ovs-agent 
  24.         image: ovs_bond:v1 
  25.         imagePullPolicy: IfNotPresent 
  26.         resources: 
  27.             limits: 
  28.               cpu: 100m 
  29.               memory: 200Mi 
  30.             requests: 
  31.               cpu: 100m 
  32.               memory: 200Mi 
  33.         securityContext: 
  34.           privileged: true 
  35.           procMount: Default 
  36.         volumeMounts: 
  37.         - mountPath: /lib/modules 
  38.           name: lib-modules 
  39.           readOnly: true 
  40.         - mountPath: /var/run/openvswitch 
  41.           name: ovs-run 
  42.         - mountPath: /usr/bin/ovs-appctl 
  43.           name: ovs-bin 
  44.           subPath: ovs-appctl 
  45.       serviceAccountName: xiyangxixia 
  46.       hostPID: true 
  47.       hostIPC: true 
  48.       volumes: 
  49.       - hostPath: 
  50.           path: /lib/modules 
  51.           type: "" 
  52.         name: lib-modules 
  53.       - hostPath: 
  54.           path: /var/run/openvswitch 
  55.           type: "" 
  56.         name: ovs-run 
  57.       - hostPath: 
  58.           path: /usr/bin/ 
  59.           type: "" 
  60.         name: ovs-bin 
  61.   updateStrategy: 
  62.     type: RollingUpdate 

七、测试

  1. [root@test ~]$ kubectl get po -n kube-system -o wide  |grep ovs 
  2. ovs-agent-h8zc6    1/1     Running     0    2d14h   10.211.55.41   master-1   <none>           <none> 
  3. [root@test ~]$ curl 10.211.55.41:8080/metrics |grep ovs_bond 
  4. # HELP ovs_bond_status Show ovs bond status 
  5. # TYPE ovs_bond_status counter 
  6. ovs_bond_status{component="ovs",a1b1="enabled",a2b2="enabled",a3b3="enabled",a4b4="enabled"} 0 

八、总结

 

以上就是这篇文章的所有了,原谅我学艺不精只能粗糙的介绍一下。感谢一直以来关注公众号的朋友们!

 

责任编辑:武晓燕 来源: 运维开发故事
相关推荐

2020-12-14 10:26:48

Prometheus 监控Services

2023-03-26 08:41:37

2023-12-29 08:01:52

自定义指标模板

2021-03-26 20:37:14

Prometheus监控指标

2009-06-25 14:53:35

自定义UI组件JSF框架

2009-08-03 13:34:06

自定义C#控件

2023-10-31 09:10:39

2023-07-28 09:26:43

GolangZap

2019-12-25 11:47:27

LinuxFVWM

2010-10-25 16:05:07

oracle自定义函数

2009-06-23 11:35:44

JSF的Naviati

2022-01-14 09:17:13

PythonAPISIX插件

2015-02-12 15:33:43

微信SDK

2022-09-13 15:44:52

VSLook插件

2021-10-28 08:39:22

Node Export自定义 监控

2015-02-12 15:38:26

微信SDK

2009-06-22 15:07:45

原则和技巧JSF自定义复合组件

2017-03-16 14:37:05

LinuxShell函数

2016-12-26 15:25:59

Android自定义View

2016-11-16 21:55:55

源码分析自定义view androi
点赞
收藏

51CTO技术栈公众号