Prometheus自定义Exporter开发过程

2020-08-05

Go语言基础知识

入门视频教程:https://study.163.com/course/courseMain.htm?courseId=306002

Prometheus go client

Github: https://github.com/prometheus/client_golang

作用

官方的封装SDK,用到Registry、Metric概念

Kubernetes go client

Github: https://github.com/kubernetes/client-go

作用

与api-server交互,获取各种集群资源

推荐官方示例

https://github.com/kubernetes/client-go/tree/master/examples/in-cluster-client-configuration

本地Build测试注意点

看下面这段Kubernetes Go语言客户端的代码

const (
    tokenFile  = "/var/run/secrets/kubernetes.io/serviceaccount/token"
    rootCAFile = "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"
)
host, port := os.Getenv("KUBERNETES_SERVICE_HOST"), os.Getenv("KUBERNETES_SERVICE_PORT")
if len(host) == 0 || len(port) == 0 {
    return nil, ErrNotInCluster
}

token, err := ioutil.ReadFile(tokenFile)
if err != nil {
    return nil, err
}

tlsClientConfig := TLSClientConfig{}

if _, err := certutil.NewPool(rootCAFile); err != nil {
    klog.Errorf("Expected to load root CA config from %s, but got err: %v", rootCAFile, err)
} else {
    tlsClientConfig.CAFile = rootCAFile
}

如果是通过idea开发Go语言程序,可在Environment中添加KUBERNETES_SERVICE_HOSTKUBERNETES_SERVICE_PORT两项环境变量

然后在本地新建/var/run/secrets/kubernetes.io/serviceaccount/token和/var/run/secrets/kubernetes.io/serviceaccount/ca.crt文件,文件内容可拷贝集群中某个Pod用到的文件,
token文件确定了对应的RBAC权限

这样就可以在本地进行测试,不需要打出镜像,再创建部署,发到集群中才能测试

踩到的坑

官方Prometheus Go client中通过Collect方法搜集指标,用到channel,自定义collector需要实现Collect方法,如下:

func (c *Metrics) Collect(ch chan<- prometheus.Metric) {
    c.mutex.Lock() // 加锁
    defer c.mutex.Unlock()

    pods, err := c.clientset.CoreV1().Pods("").List(context.TODO(), metav1.ListOptions{})
    if err != nil {
        panic(err.Error())
    }
    items := pods.Items
    var wg sync.WaitGroup
    for _, item := range items {
        wg.Add(1)
        tmp := item
        go healthCheck(&tmp, c, ch, &wg)
    }

    wg.Wait()
}

其中healthCheck方法中对pod进行健康检查,返回健康检查耗时

坑1

为了防止prometheus拉取时耗时过长造成超时,代码里用GoRoutine完成所有pod健康检查接口的http请求,但是一开始没注意到同步问题,所以控制台会报往关闭的channel里发送数据的异常,经过排查得知,可以用sync包里的WaitGroup实现GoRoutine的同步

var wg sync.WaitGroup

坑2

闭包问题,for循环中用GoRoutine时,如果for循环遍历的对象作为方法参数,需要注意闭包问题,现象就是很可能所有GoRoutine执行时用到的参数都是最后一次循环对应的内容

解决方法,可以用局部变量指向需要用到的参数

tmp := item

总结

开发一个自定义prometheus exporter不是很难,社区有很多优秀的第三方exporter实现,本文目的是介绍go语言方法的开发,希望能起到一定的帮助

Go语言是云原生的语言,学好Go才能更好的投入云原生,遇到的两个坑都跟go语言本身的特性有关,需要加强go语言基础的学习

Prometheus Operator是另一项值得玩的东西,以后将作出介绍