1 实现方式

golang调用shell实现kubeconfig文件有效期监测和监控,代码采用cobra命令行工具库编写。其主要功能是:

  • 调用shell命令检测kubeconfig文件过期时间
  • 有效期低于30天的kubeconfig通过钉钉发送到期通知 
  • 通过crontab任务每天定时监测

2 代码

2.1 main.go

/*
Copyright © 2023 NAME HERE <EMAIL ADDRESS>

*/
package main

import "sretools/cmd"

func main() {
	cmd.Execute()
}

2.2 cmd/kubeconfcheck.go

/*
Copyright © 2023 NAME HERE <EMAIL ADDRESS>

*/
package cmd

import (
        "strconv"
        "strings"
        "fmt"
        "time"
        "github.com/spf13/cobra"
        "sretools/lib"
)
const ( 
        STANDARD_DIR="/root/kubeconfig_monitor"
        CERT_PEM_FILE_NAME="client-cert.pem"
        DAY_SECOND=86400
        DING_TALK_URL="钉钉群机器人地址"
)
// kubeconfcheckCmd represents the kubeconfcheck command
var kubeconfcheckCmd = &cobra.Command{
        Use:   "kubeconfcheck",
        Short: "kubeconfig expire time verificate",
        Example: "sretools kubeconfcheck --conf ./root/.kube/config",
        Long: ``,
        Run: func(cmd *cobra.Command, args []string) {
                CheckKubeConfig(conf,app)
        },
}

func init() {
        rootCmd.AddCommand(kubeconfcheckCmd)
        kubeconfcheckCmd.Flags().StringVarP(&conf, "conf", "c", "", "kubeconfig path,example:/root/.kube/config")
        kubeconfcheckCmd.MarkFlagRequired("conf")
        kubeconfcheckCmd.Flags().StringVarP(&app, "app", "a", "", "oceanbase public cloud app,example:ocp,oms...")
        kubeconfcheckCmd.MarkFlagRequired("app")
        kubeconfcheckCmd.Flags().StringVarP(&region, "region", "r", "", "oceanbase public cloud region,please refer to:https://help.aliyun.com/document_detail/40654.html?spm=a2c4g.750001.0.i2")
        kubeconfcheckCmd.MarkFlagRequired("region")
        kubeconfcheckCmd.Flags().StringVarP(&zone, "zone", "z", "", "oceanbase public cloud region zone,example:zeus(宙斯区)、obvpc(obvpc区)")
        kubeconfcheckCmd.MarkFlagRequired("zone")
        kubeconfcheckCmd.Flags().StringVarP(&days, "days", "d", "", "expire days,example:1、7、30、180、365")
        kubeconfcheckCmd.MarkFlagRequired("days")
}
func CheckKubeConfig(file,app string) {
    var err error 
    //校验配置文件是否存在
    if !lib.IsExist(file) {
        fmt.Printf("file:%v node exist",file)
        return
    }
    //校验kubeconfig文件到期时间
    //读取文件内容
    fileContentCmd := fmt.Sprintf("cat %v | grep client-certificate-data | awk -F ' ' '{print $2}' |base64 -d",file) 
    content,err := lib.RunCmd(fileContentCmd)
    if err != nil {
        fmt.Printf("read file content failed:%v\n",err)
        return 
    }
        // 判断路径是否存在
        err = lib.DirNotExistAndCreate(STANDARD_DIR)
        if err != nil {
                fmt.Printf("create dir :%v failed:%v\n",STANDARD_DIR,err)
                return
        }
    pemfile := fmt.Sprintf("%v/%v-%v",STANDARD_DIR,app,CERT_PEM_FILE_NAME)
    err = lib.WriteToFile(pemfile,content)
    if err != nil {
         return 
    }
    checkCertCmd := fmt.Sprintf("openssl x509 -in %v -noout -dates| grep After| awk -F '=' '{print $2}'|awk -F 'GMT' '{print $1}'",pemfile)
    //fmt.Printf("%v\n",checkCertCmd)
    certTime,err := lib.RunCmd(checkCertCmd)
    if err != nil {
        fmt.Printf("read file content failed:%v\n",err)
        return
    }
    //fmt.Printf("cert time:%v\n",strings.Replace(certTime,"\n","",-1))
    timeToUnix := fmt.Sprintf("date -d '%v' %v",strings.Replace(certTime,"\n","",-1),"+%s")
    //fmt.Printf("%v\n",timeToUnix)
    unixTime,err :=  lib.RunCmd(timeToUnix)
        if err != nil {
        fmt.Printf("read file content failed:%v\n",err)
        return
    }
    
    //fmt.Printf("cert time:%v\n",certTime)
    now := time.Now().Unix() 
    utime,_ := strconv.ParseInt(strings.Replace(unixTime,"\n","",-1), 10, 64)
    expiretime := utime - now
    msgContent := fmt.Sprintf("标题:kubeconfig有效期巡检\n检测时间:%v\nregion:%v\n功能区:%v\n巡检内容:kubeconfig文件【%v】有效期不足%v天,请及时替换!\n到期时间:%v",time.Non,zone,file,days,certTime)
    day,_ := strconv.ParseInt(days, 10, 64)
    if expiretime <= day * DAY_SECOND {
        err := lib.SendMsg(msgContent,DING_TALK_URL)    
        if err != nil {
                fmt.Printf("send msg to dingding failed:%v\n",err)
                return 
        }       
    }
        return 
}

2.3 cmd/var.go

package cmd 

var (
     process string
     conf string
     app string
     region string
     zone string
     days string 
     env string 
)

2.4 lib/cmd.go

package lib
import (
	"fmt"
	"os/exec"
	"bytes"
)

func RunCmd(cmdstring string) (string, error) {
        var out bytes.Buffer
        var stderr bytes.Buffer
        cmd := exec.Command("/bin/sh", "-c", cmdstring)
        cmd.Stdout = &out
        cmd.Stderr = &stderr
        err := cmd.Run()
        if err != nil {
                fmt.Printf("err:%v\n",err)
                return fmt.Sprintf("%s",stderr.String()),err
        }
        return fmt.Sprintf("%v",out.String()),nil
}

2.5 lib/file.go

package lib
import (
	"io/fs"
	"io/ioutil"
	"fmt"
	"os"	
)
 
func ReadFile(path string) (string, error) {
    content, err := os.ReadFile(path)
    if err != nil {
       return "",err 
    }
    return string(content),nil
}

func IsExist(filePath string) bool {
	_, err := os.Stat(filePath)
	return err == nil || os.IsExist(err)
}

func CreateFile(filepath string) error {
        file, err := os.Create(filepath)
        if err != nil {
                fmt.Println(err)
                return err
        }
        // 关流(不关流会长时间占用内存)
        defer file.Close()
        return nil
}
func WriteToFile(file,content string) error {
        err := ioutil.WriteFile(file,[]byte(content),0666)
        if err != nil {
                fmt.Printf("write to file failed:%v\n",err)
                return err
        }
        return nil
}
func CheckDirExist(path string) error {
	var err error 
        _, err = os.ReadDir(path)
        if err != nil {
		fmt.Printf("dir:%v not exist:%v\n",path,err)
		return err 
	}
	return nil 
}
func CreateDir(path string) error {
	var err error 
         err = os.MkdirAll(path, fs.ModePerm)
         if err != nil {
                 fmt.Printf("create dir :%v failed:%v\n",path,err)
                 return err 
         }
	return nil 
}
func DirNotExistAndCreate(path  string) error {
        // 判断路径是否存在
        err := CheckDirExist(path)
        if err != nil {
                // 不存在就创建
                err = CreateDir(path)
                if err != nil {
                        fmt.Printf("create dir :%v failed:%v\n",path,err)
                        return err 
                }
        }
	return nil 
}

2.6 lib/fmt.go

package  lib 
import (
   "fmt"
   "time"
)

func Printf(msg,level string) {
   fmt.Printf("[%v] [%v] %v\n",time.Now().Format("2006-01-02 15:04:05"),level,msg)
}

2.7 lib/ip.go

package lib 
import (
	"net"
	"fmt"
)
//获取本机ip地址
func GetIps() (ips []string) {
 	interfaceAddr, err := net.InterfaceAddrs()
 	if err != nil {
 		fmt.Printf("fail to get net interfaces ipAddress: %v\n", err)
 		return ips
 	}

 	for _, address := range interfaceAddr {
 		ipNet, isVailIpNet := address.(*net.IPNet)
 		if isVailIpNet && !ipNet.IP.IsLoopback() {
 			if ipNet.IP.To4() != nil {
 				ips = append(ips, ipNet.IP.String())
 			}
		}
	}
	return ips
}
func GetEth0Ip() string {
	ips := GetIps()
	if len(ips) > 0 {
		return ips[0]
	}
	return ""
}

2.8 lib/msg.go

package lib 
import (
	"fmt"
	//"time"
	"encoding/json"
	"strings"
	"net/http"
	"io/ioutil"
	"errors"
)
func SendMsg(content,url string) error {
	var msg *Message = &Message{
		Msgtype: "text",
		Text: &Msgtext{
			Content: content,
		},
	}
	err := HttpPost(msg,url)
	if err != nil {
		message := fmt.Sprintf("发送钉钉消息失败,失败原因:%v",err)
		Printf(message,"ERROR")
		return err
	}
	return nil
}

type Message struct {
	Msgtype string `json:"msgtype"`
	Text *Msgtext `json:"text"`
}
type Msgtext struct {
     Content	string `json:"content"`
}

type Response struct {
	Errcode     int   `json:"errcode"`
	Errmsg      string    `json:"errmsg"`
}

func HttpPost(param *Message,url string) error {
	var (
		err    error
		message string 
	)

	// json.Marshal
	reqParam, err := json.Marshal(&param)
	if err != nil {
		message = fmt.Sprintf("解析请求参数失败:%v",err)
                Printf(message,"ERROR")
		return err
	}

	reqBody := strings.NewReader(string(reqParam))
	httpReq, err := http.NewRequest("POST", url, reqBody)
	if err != nil {
                message = fmt.Sprintf("发起http post请求失败,url: %s, reqBody: %s,失败原因:%v",url, reqBody,err)
                Printf(message,"ERROR")
		return err
	}
	httpReq.Header.Add("Content-Type", "application/json")

	// DO: HTTP请求
	httpRsp, err := http.DefaultClient.Do(httpReq)
	if err != nil {
		message = fmt.Sprintf("http post请求处理失败, url: %s, reqBody: %s, 失败原因:%v", url, reqBody, err)
		Printf(message,"ERROR")
		return err
	}
	defer httpRsp.Body.Close()

	// Read: HTTP结果
	rspBody, err := ioutil.ReadAll(httpRsp.Body)
	if err != nil {
		message = fmt.Sprintf("获取http post响应失败, url: %s, reqBody: %s, 失败原因: %v", url, reqBody, err)
		Printf(message,"ERROR")
		return err
	}

	var result Response
	if err = json.Unmarshal(rspBody, &result); err != nil {
		message = fmt.Sprintf("解析http响应失败, 失败原因:%v", err)
		Printf(message,"ERROR")
		return err
	}
	if result.Errcode != 0 {
		message = fmt.Sprintf("http post fail, url: %s, reqBody: %s, ErrorMsg:%s", url, reqBody, result.Errmsg)
		Printf(message,"ERROR")
		return errors.New(result.Errmsg)
	}
 	return nil
}

 3 使用

30 9 * * * /usr/bin/sretools kubeconfcheck -a appname -c /root/.kube/config  -r region-id -z zonename -d 30 >> /dev/null 2>&1

  

 

 

01-11 08:28