namespace delete 操作触发告警 Exporter 开发

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 路径等

开发流程:

  1. 先对 k8s cluster 做 informer 动作,所以第一步编写对应的处理函数
  2. 注册到 Prometheus
  3. 编写日志系统
  4. 编写配置文件系统

项目地址: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 已经做出回显

以上就是整个开发流程

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇