1 前言
由于现在云原生场景愈发的多所以必不可少的就需要 Prometheus 等开源监控工具来进行对监控,虽然很多第三方的 exporter 都能对大部分组件进行更好的监控,但是由于最近公司误删除了一个 NS 却没有及时的告警,导致近 10 小时数据丢失(因为 etcd 的数据备份有近 10 小时的空档期),所以为了及时避免损失我们需要做出尽快的响应
所以这两天我就对一个删除 NS 的操作告警做一个 exporter 的开发
2 开发流程
在做 exporter 开发之前需要先学习 Prometheus 的一下指标等内容为基础这里可以看我以前写过的一篇文章,Prometheus export 开发系列所以我在这里就不过多赘述
然后由于需要对 K8S 做操作所以这里也需要对 client-go 有一定了解当然这里也可以看我以前写过的文章 K8S 二次开发系列
话不多说进入正题,开发思路:
- 由于需要对 K8S 做全程的 Watch 操作这样才能够实时监控 K8S 中的资源变化,所以这里我采用了 Informer 作为使用对象;
- 还需要有一定的日志功能因为在我的 exporter web 页面中无法回显 namespace 信息,所以这里我通过将 delete ns 的动作输出到 log 中;
- 最后还编写了 config 配置模块,这样我们就可以自定义我们的 exporter web 端口;日志路径、日志存放时长、日志存放大小等功能;以及kubeconfig 路径等
开发流程:
- 先对 k8s cluster 做 informer 动作,所以第一步编写对应的处理函数
- 注册到 Prometheus
- 编写日志系统
- 编写配置文件系统
项目地址:https://github.com/As9530272755/Prometheus_exporter/tree/v1.0.beta/ns_exporter
3 代码实现
这里大家可以看我当前的目录结构
root@ubuntu:~/go/src/cicc/prometheus/ns_exporter# tree
.
├── controller # 处理器以及 Prometheus 注册
│ └── ns_controller.go
├── etc # 配置文件存放位置
│ ├── config
│ └── exporter.yaml
├── ex_config # 配置文件模块
│ └── config.go
├── go.mod
├── go.sum
├── linkKube # 链接 K8S
│ └── linkKube.go
├── logs
│ └── exporter.log
├── main.go
├── msgErr # 错误处理模块
│ └── error.go
└── ns_exporter
3.1 编写 error 模块
先编写处理error 模块是因为后续的使用中直接通过该函数即可调用,从而避免重复使用 if err != nil
这种语法
// 这里的代码比较简单我就不做解释
package msgErr
import "log"
func ErrInfo(err error) {
if err != nil {
log.Panic(err)
}
}
3.2 编写链接 K8S 模块
在下面的模块中我将使用 client-go 一个官方的 sdk
package linkKube
import (
"kube_expoter/msgErr"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/clientcmd"
)
// 这里我直接返回的一个 *clientset 用于下面代码中的操作
func Link_kube(kubeconfig string) *kubernetes.Clientset {
// 读取 config 文件
config, err := clientcmd.BuildConfigFromFlags("", kubeconfig)
msgErr.ErrInfo(err)
// new 一个client
clientSet, err := kubernetes.NewForConfig(config)
msgErr.ErrInfo(err)
return clientSet
}
3.3 编写 controller 模块
这里的代码就相对比较复杂,大概逻辑就是当我们通过 Prometheus 的注册处理函数来判断如果有 namespace 被删除了那么就会 value = 1 ,如果没有 value = 0
package controller
import (
"kube_expoter/ex_config"
"kube_expoter/linkKube"
"time"
"github.com/prometheus/client_golang/prometheus"
"github.com/sirupsen/logrus"
"gopkg.in/natefinch/lumberjack.v2"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/informers"
"k8s.io/client-go/tools/cache"
)
func PrometheusCotroller(optins *ex_config.Optins) {
// 定义 NS 用于下面接收被删除 ns 的赋值
namespace := ""
// 注册一个 Prometheus 处理函数
prometheus.MustRegister(prometheus.NewGaugeFunc(prometheus.GaugeOpts{
// 下面这 4 行是我们的指标描述信息
Name: "namespaace",
Namespace: "kube_delete",
Help: "This is a deleted namespace",
ConstLabels: prometheus.Labels{"Namespace": "Delete_Operation"},
}, func() float64 {
// 由于下面代码需要使用到 channel 所以这里我开启一个线程,同时执行
go func() {
// 实例化 SharedInformer
// NewSharedInformerFactory 的参数
// 1. 与k8s交互的客户端
// 2. resync 的时间,如果传入0,则禁用resync功能,该功能使用 List 操作
stopCh := make(chan struct{})
defer close(stopCh)
// 通过 client 来构建一个新的 informert 实例
sharedInformers := informers.NewSharedInformerFactory(linkKube.Link_kube(optins.KubeConfig.ConfigPath), time.Minute)
// 生成 pod 资源的informer
informer := sharedInformers.Core().V1().Namespaces().Informer()
// 添加事件回调
informer.AddEventHandler(cache.ResourceEventHandlerFuncs{
// 创建资源时触发
AddFunc: nil,
// 更新资源时触发
UpdateFunc: nil,
// 删除资源时触发
DeleteFunc: func(obj interface{}) {
DelNamespace := obj.(v1.Object)
// 获取到已经被删除的 NS 并赋值给 namespace
namespace = DelNamespace.GetName()
},
})
// 运行 informer
informer.Run(stopCh)
}()
// 这里是日志系统模块,这些都可以在读取配置文件模块中进行定义
logger := lumberjack.Logger{
Filename: optins.Log.FileName, // 日志name
MaxAge: optins.Log.Max_age, // 日志天数
Compress: optins.Log.Compress, // 日志是否开启压缩
MaxSize: optins.Log.Max_size, // 日志大小
}
defer logger.Close()
// 定义日志级别
logLevel, _ := logrus.ParseLevel(optins.Log.Level)
// 创建输出对象
logrus.SetOutput(&logger)
// 创建日志文件
logrus.SetReportCaller(true)
// 设置日志级别
logrus.SetLevel(logLevel)
// 日志的输出模板
logrus.WithFields(
logrus.Fields{
"DeleteNS": namespace,
}).Info("Kube_Exporter")
// 如果有 delete namespace 操作返回 1,如果没有就为 0
if namespace != "" {
return 1.0
} else {
return 0.0
}
}))
}
3.4 配置文件模块
我们现在可以看到这个程序在启动的时候需要加载很多配置信息,那我们可以将其写到配置文件中
配置文件都有对应不同的库,因为每一种配置文件他都有固定的格式,所以需要通过不同的解析器
- 常见配置文件:
- ini => 解析器
- json => 解析器
- yaml => 解析器
- toml => 解析器
- 自定义格式 => 自定义解析器
这里我使用的是 viper
,这个包的话支持多种配置文件的解析
官方地址:https://pkg.go.dev/github.com/spf13/viper
package ex_config
import (
"github.com/spf13/viper"
)
type KubeConfig struct {
ConfigPath string `mapstructure:"path"`
}
type Web struct {
ListenPort string `mapstructure:"port"`
}
type Log struct {
FileName string `mapstructure:"filename"`
Max_age int `mapstructure:"max_age"`
Max_size int `mapstructure:"max_size"`
Compress bool `mapstructure:"compress"`
Level string `mapstructure:"level"`
}
type Optins struct {
KubeConfig KubeConfig `mapstructure:"kubeconfig"`
Log Log `mapstructure:"log"`
Web Web `mapstructure:"web"`
}
// 传入配置文件路径并返回一个 *options
func ParseConfig(path string) (*Optins, error) {
conf := viper.New()
// 这是传入配置文件路径
conf.SetConfigFile(path)
// 索引配置文件中的所有 key:value
if err := conf.ReadInConfig(); err != nil {
return nil, err
}
// 解析配置文件并交给 optins 这样就读取到了所有配置信息
optins := &Optins{}
if err := conf.Unmarshal(&optins); err != nil {
return nil, err
}
return optins, nil
}
3.5 最后编写 main 函数
package main
import (
"fmt"
"kube_expoter/controller"
"kube_expoter/ex_config"
"kube_expoter/msgErr"
"net/http"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
func main() {
//mkconfigDir.MakeDir()
// 传入配置我呢见
options, err := ex_config.ParseConfig("./etc/exporter.yaml")
msgErr.ErrInfo(err)
// 拼接 web 地址,通过解析配置文件之后的 prot
addr := fmt.Sprintf(":" + options.Web.ListenPort)
// 传入 option 到 PrometheusCotroller,因为这里面有我们的 log 配置操作
controller.PrometheusCotroller(options)
// 路由
http.Handle("/metrics", promhttp.Handler())
// 启动
http.ListenAndServe(addr, nil)
}
4 验证
4.1 编写配置文件
$ mkdir etc/
$ vim etc/exporter.yaml
# Kubeconfig path
kubeconfig:
path: etc/config
# web addr The default address is 8889
web:
port: 8889
# Log configuration
# Default log path logs/exporter.log
# There are seven log levels: panic fatal error warn info debug trace
log:
filename: logs/exporter.log
max_age: 14
max_size: 10
max_backups: 14
compress: false
level: info
4.2 启动程序验证
1 启动程序
$ go run main.go
2 验证当前指标状态
可以看到当前为 0
3 K8S 上删除 ns
ot@master:~# kubectl create ns test4
namespace/test4 created
root@master:~# kubectl delete ns test4
namespace "test4" deleted
4 浏览器验证可以看到当前的指标为 1,这样后期我们就可以通过该指标来配置 alert manager 从而实现 delete NS 动作告警
4.3 验证日志
$ cat logs/exporter.log
time="2022-07-20T23:25:12+08:00" level=info msg=Kube_Exporter func=kube_expoter/controller.PrometheusCotroller.func1 file="/root/go/src/cicc/prometheus/ns_exporter/controller/ns_controller.go:72" DeleteNS=test4
# 可以清晰的看到 DeleteNS=test4 已经做出回显
以上就是整个开发流程