go语言-k8s宿主信息采集运维脚本

时间:2024-03-17 18:01:00

背景

由于工作需要,需要采集k8s集群中的宿主相关信息,包括cpu,memory,lvm,标签等信息。通常作为SRE会主选shell或python脚本来实现。但最近我们团队主流开发语言已经切换到golang.所以本次尝试用go语言来写写运维脚本。

实现流程图

代码实现

package main

import (
	"errors"
	"fmt"
	"github.com/gocarina/gocsv"
	"log"
	"os"
	"os/exec"
	"strings"
	"time"
)

// K8SNode 描述宿主节点
type K8SNode struct {
	IP           string `csv:"ip"`
	CPUSize      string `csv:"cpu_size"`
	MemorySize   string `csv:"memory_size"`
	LVMSize      string `csv:"lvm_size"`
	VMINum       int    `csv:"vmi_num"` // 虚拟机个数
	ProjectLabel string `csv:"project_label"`
}

// NodeDescribeOutput 执行kubectl describe node 指定ip后面的输出内容
func NodeDescribeOutput(ip string) (string, error) {
	cmd := "kubectl  describe node " + ip
	log.Printf("execut command: %s\n", cmd)
	output, err := ExecCmd(cmd)
	if err != nil {
		log.Println("err:", err)
		return "", err
	}
	return output, nil
}

// CpuSize 获取节点cpu
func CPUSize(describeInfo string) (string, error) {

	describeInfoList := strings.Split(describeInfo, "\n")
	for _, line := range describeInfoList {
		if strings.Contains(line, "cpu:") {
			cpusizelist := strings.Split(line, ":")
			//log.Printf("cpusize: %s", cpusizelist[1])
			size := strings.Replace(cpusizelist[1], " ", "", -1)
			return size, nil
		}
	}
	return "", errors.New("not found cpu size.")
}

// MemorySize 获取节点内存大小
func MemorySize(describeInfo string) (string, error) {

	describeInfoList := strings.Split(describeInfo, "\n")
	for _, line := range describeInfoList {
		if strings.Contains(line, "memory:") {
			memorysizelist := strings.Split(line, ":")
			size := strings.Replace(memorysizelist[1], " ", "", -1)
			return size, nil
		}
	}
	return "", errors.New("not found memory size.")
}

// LVMSize 获取节点本地磁盘大小
func LVMSize(describeInfo string) (string, error) {

	describeInfoList := strings.Split(describeInfo, "\n")
	for _, line := range describeInfoList {
		if strings.Contains(line, "lvm:") {
			sizelist := strings.Split(line, ":")
			size := strings.Replace(sizelist[1], " ", "", -1)
			return size, nil
		}
	}
	return "", errors.New("not found memory size.")
}

// VMINum 统计节点已经允许vmi的数量(vmi表示在k8s+kubevirt中KVM对应的对象)
func VMINum(describeInfo string) (int, error) {

	describeInfoList := strings.Split(describeInfo, "\n")
	var count int
	for _, line := range describeInfoList {
		if strings.Contains(line, "irt-launcher-") {
			count++
		}
	}
	return count, nil
}

// ProjectLabel 获取节点项目标签[我们业务定义project表示宿主用于哪一个项目]
func ProjectLabel(describeInfo string) (string, error) {

	describeInfoList := strings.Split(describeInfo, "\n")
	for _, line := range describeInfoList {
		if strings.Contains(line, "project=") {
			//log.Printf("cpusize: %s", cpusizelist[1])
			label := strings.Replace(line, " ", "", -1)
			label = strings.Replace(label, "project=", "", -1)
			label = strings.Replace(label, "\n", "", -1)
			return label, nil
		}
	}
	return "", errors.New("not found project label.")
}

// ExecCmd 对exec.Command()的封装
func ExecCmd(cmdstr string) (string, error) {

	cmd := exec.Command("bash", "-c", cmdstr)
	res, err := cmd.Output()
	if err != nil {
		return "", err
	}
	return string(res), nil

}

// GetIPList 获取集群中节点对应的IP列表
func GetIPList() []string {

	cmd := "kubectl get  node |grep -E \"[0-9]\" |awk '{print $1}'"
	log.Printf("execut command: %s\n", cmd)
	output, err := ExecCmd(cmd)
	if err != nil {
		log.Println("err:", err)
		var nilIPList []string
		return nilIPList
	}
	return strings.Split(string(output), "\n")
}

// 将结构体切片转换为csv文件
func WriteToCsv(filename string, nodes []*K8SNode) {
	// 创建csv文件
	os.Chdir("/tmp")
	nodesFile, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE, os.ModePerm)
	if err != nil {
		panic(err)
	}
	defer nodesFile.Close()

	// 将结构体切片转换为csv文件
	if err := gocsv.Marshal(&nodes, nodesFile); err != nil { // Load clients from file
		panic(err)
	}
}

// uploadToFilesever 将文件通过scp命令传到文件服务器
func uploadToFilesever(filename string) error {
	cmd := "scp " + filename + " 172.17.123.89:/home/segops/app/nginx/data/tmp/"
	log.Printf("execut command: %s\n", cmd)
	_, err := ExecCmd(cmd)
	if err != nil {
		log.Println("err:", err)
		return err
	}
	return nil
}

func main() {

	// Nodes 保存节点的切片
	var Nodes = []*K8SNode{}
	// count 用于统计节点数量
	var count int

	ipList := GetIPList()
	// 依次处理每一个宿主节点IP
	for _, ip := range ipList {
		//fmt.Println(ip)
		if ip == "" {
			continue
		}
		count++
		var oneNode = &K8SNode{}
		oneNode.IP = ip

		describeNodeOutput, err := NodeDescribeOutput(ip)
		if err != nil {
			log.Printf("%s NodeDescribeOutput error.\n", ip)
		}

		cpusize, err := CPUSize(describeNodeOutput)
		if err == nil {
			oneNode.CPUSize = cpusize
		} else {
			oneNode.CPUSize = ""
		}

		memsize, err := MemorySize(describeNodeOutput)
		if err == nil {
			oneNode.MemorySize = memsize
		} else {
			oneNode.MemorySize = ""
		}

		lvmsize, err := LVMSize(describeNodeOutput)
		if err == nil {
			oneNode.LVMSize = lvmsize
		} else {
			oneNode.LVMSize = ""
		}

		vminum, err := VMINum(describeNodeOutput)
		if err == nil {
			oneNode.VMINum = vminum
		} else {
			oneNode.VMINum = -1
		}

		projectlabel, err := ProjectLabel(describeNodeOutput)
		if err == nil {
			oneNode.ProjectLabel = projectlabel
		} else {
			oneNode.ProjectLabel = ""
		}
		fmt.Printf("%v\n", oneNode)
		//log.Println(ProjectLabel(describeNodeOutput))

		// 将一个K8SNode对象加入切片
		Nodes = append(Nodes, oneNode)
	}
	//fmt.Println("nodemap=%v", nodeMap)
	fmt.Printf("===========================================================================================\n")
	fmt.Printf("=========================================统计结果===========================================\n")
	fmt.Printf("===========================================================================================\n")
	fmt.Printf("ip\t\tcpu\t\tmemory\t\tlvmsize\t\tvminum\t\tprojectlabel\n")
	for _, node := range Nodes {
		fmt.Printf("%s\t%s\t\t%s\t%s\t\t%d\t\t%s\n", node.IP, node.CPUSize, node.MemorySize, node.LVMSize, node.VMINum, node.ProjectLabel)
	}
	fmt.Println()
	fmt.Printf("node num is %d\n", count)

	fmt.Println()
	fmt.Println("write to csv file:")
	currentTime := time.Now().Format("20060102150405")
	hostname, _ := os.Hostname()
	csvfilename := hostname + "_" + "nodesinfo" + "_" + currentTime + ".csv"

	// 写入csv文件
	WriteToCsv(csvfilename, Nodes)

	// 上传到文件服务器
	err := uploadToFilesever(csvfilename)
	if err != nil {
		fmt.Println("upload csv file error.")
		return
	}

	// 定义下载文件的路径
	downloadPath := "http://172.17.123.89:8080/tmp/" + csvfilename

	fmt.Println()
	fmt.Println("csv file can download from: ", downloadPath)
	fmt.Println()
}

实现结果

​
17:35:14  ===========================================================================================
17:35:14  =========================================统计结果===========================================
17:35:14  ===========================================================================================
17:35:14  ip		cpu		memory		lvmsize		vminum		projectlabel
17:35:14  172.24.52.11	96		394687572Ki	4862Gi		7		personal-dev
17:35:14  172.24.52.12	96		394687572Ki	4862Gi		8		personal-dev
17:35:14  172.24.52.13	96		394682812Ki	4862Gi		2		personal-dev
17:35:14  172.24.52.14	96		394687572Ki	4862Gi		5		personal-dev
17:35:14  172.24.52.141	96		394687572Ki	4862Gi		1		tools
17:35:14  172.24.52.142	96		394687572Ki	4862Gi		0		personal-dev-unuse
17:35:14  172.24.52.143	96		394687572Ki	4862Gi		0		personal-dev-unuse
17:35:14  172.24.53.79	80		394679084Ki	3225Gi		0		unuse
17:35:14  172.24.53.93	72		131267356Ki	11431Gi		0		unuse
17:35:14  172.24.53.94	72		131267356Ki	11431Gi		0		unuse
17:35:14  
17:35:14  node num is 122
17:35:14  
17:35:14  write to csv file:
17:35:14  2024/03/07 17:35:14 execut command: scp hyd01-seg-admin01_nodesinfo_20240307173514.csv 172.17.123.89:/home/segops/app/nginx/data/tmp/
17:35:15  
17:35:15  csv file can download from:  http://172.17.123.89:8080/tmp/hyd01-seg-admin01_nodesinfo_20240307173514.csv

​

 

总结

用go脚本写了约300行,并不简洁。所以在工作实际中,如果写一些逻辑简单的脚本建议首选用shell或python。