获取通过官方api获取docker状态
写的不好仅供参考。
package main
import (
"context"
"encoding/json"
"fmt"
"github.com/docker/docker/api/types"
"github.com/docker/docker/client"
"github.com/robfig/cron/v3"
"github.com/shirou/gopsutil/v3/cpu"
"github.com/shirou/gopsutil/v3/mem"
"io/ioutil"
"log"
"net/http"
"strings"
)
var sts types.Stats
//企业微信数据结构定义
type Message struct {
MsgType string `json:"msgtype"`
Text struct {
Content string `json:"content"`
Mentioned_list []string `json:"mentioned_list"`
} `json:"text"`
}
type DockerState struct {
Name string //获取容器名称
ID string //容器id
PrivateIP string //指代机器的IP,不指代特定docker ip
PublicIP string //指代公网IP,不指代docker ip
Logpath string //获取容器路径
Size string //获取日志大小
RestartTime string //获取容器启动时间
//FinishedTime string //容器关闭时间,需要记录的是这一次的状态,而不是上一次的状态,所以删除。
LogTime string //启动日志收集容器时间本机utc时间-3分钟
LogSize string // docker日志文件大小
LogCount int //计数,查过10次,也就是10分钟没人处理吧日志清空。
Oversize bool //日志过大,超过预期
OneMinute bool //运行状态是否小于一分钟
IsUP bool // 容器是否为up启动状态,down表示已经被删除。
GetLogcomment string //显示重启前三分钟日志方法
cpupercent string //cpu占用百分比
memlitpercent string //限制状态下,内存占用百分比
memallpercent string //总内存状态,内存占用百分比
memusagesize string //容器使用内存
memlimitsize string //容器限制使用的内存
cputhresholdalter bool //cpu是否超过报警阈值
memlimithresholdalter bool //mem是否超过限制使用内存阈值
memallthresholdalter bool //mem是否超过总使用内存阈值
}
var memtotal uint64
var memtotalsize string
var ncpu int
func main() {
nproc, memtotal1, err := GetCPUAndMemPercent()
ncpu = nproc
memtotal = memtotal1
if err != nil {
fmt.Println(err)
}
memtotalsize = GetMemSize(memtotal)
c := cron.New()
//定时任务一分钟重启一次
c.AddFunc("@every 1m",GetDockerState )
c.Start()
select {}
}
func GetDockerState() {
//获取容器状态,参考docker官方教程
ctx := context.Background()
cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
if err != nil {
fmt.Println(err)
}
containers, err := cli.ContainerList(ctx, types.ContainerListOptions{})
if err != nil {
fmt.Println(err)
}
for _, container := range containers {
//遍历容器
//获取容器信息
ds := DockerState{}
//fmt.Println(container.ID)
ds.ID = container.ID[:12]
ds.Name = strings.Trim(container.Names[0], "/")
cpupercent, memusage, memlimit, err := GetDockersts(ds.ID)
if err != nil {
fmt.Println(err)
}
//设置cpu超过0.75报警,这里是总cpu,不是单个cpu
//设置mem内存与限制内存的比例超过0.8报警。
//设置mem内存与限制内存的比例超过0.6报警
cpupercentsize, memlitpercent, memallpercent, memusagesize, memlimitsize, cputhresholdalter, memlimithresholdalter, memallthresholdalter, err := GetDockerAlter(cpupercent, memusage, memlimit, memtotal, ncpu, 0.75, 0.75, 0.6)
if err != nil {
fmt.Println(err)
}
if cputhresholdalter {
ds.cputhresholdalter = true
}
if memlimithresholdalter {
ds.memlimithresholdalter =true
}
if memallthresholdalter {
ds.memallthresholdalter = true
}
url := "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=dxxxxxx"
context := "容器名称: " + ds.Name + "\ncpu利用率: " + cpupercentsize + "\n限制内存利用率: " + memlitpercent + "\n正常内存利用率:" + memallpercent + "\n已用内存:" + memusagesize + "\n总限制内存:" + memlimitsize + "\n总内存:" + memtotalsize + "\ncpu是否超用:" + fmt.Sprint(cputhresholdalter) + "\n内存是否超用:" + fmt.Sprint(memlimithresholdalter, memallthresholdalter)
SendMessage(url, context)
}
}
func GetMemSize(u uint64) (size string) {
//为了简单计算,小于1G的都设置为1G
if u < 1024 {
size = fmt.Sprintf("%.2fB", float64(u))
return
} else if float64(u) < 1024*1024 {
size = fmt.Sprintf("%.2fKB", float64(u)/float64(1024))
return
} else if float64(u) < 1024*1024*1024 {
size = fmt.Sprintf("%.2fMB", float64(u)/float64(1024*1024))
return
} else if float64(u) < 1024*1024*1024*1024 {
size = fmt.Sprintf("%.2fGB", float64(u)/float64(1024*1024*1024))
return
} else {
size = fmt.Sprintf("%.2fTB", float64(u)/float64(1024*1024*1024))
return
}
}
//本来想从client.info获取内存和,我发现这个内存在window上是限制内存,不是真是内存。
//所以还是通过外部工具获取
func GetCPUAndMemPercent() (ncpu int, memtotal uint64, err error) {
ncpu, err = cpu.Counts(true)
if err != nil {
return 0, 0, err
}
memInfo, err := mem.VirtualMemory()
if err != nil {
return 0, 0, err
}
return ncpu, memInfo.Total, nil
}
//cpu数据取值很复杂,建议使用exec直接调用docker stats命令读取(但是也是肉眼可见,它也是执行了1s多,应该也是抛弃了第一次的数据)
//后来考虑修改client.ContainerStatsoneshot中的函数中的query.Set("one-shot", "1") 将这个值更改为2试下,发现他的函数没有暴露。
//再后来就是通过client.ContainerStatsoneshot中获取的precpustatus是0,所以更改为流式获取,获取第二次的数据
//时间片1s=1000000000ns
//这里使用的是时间片的概念。源码说明unix系统就是ns为单位,windows是以100ns为单位
//大致概念
//unix 1s有1000000000时间片,即为1s总共可以执行那么多动作,谁拿的时间片多,谁占用时间片大
//windows 1s有10000000时间片,即为1s总共可以执行那么多动作,谁拿的时间片多,谁占用时间片大
//所以计算cpu比例如下
//cpu使用率获取思路
// cpuusage = (cpustats-precpustats)/1000000000(unix)
// cpuusage = (cpustats-precpustats)/10000000(windows)
func GetDockerAlter(cpupercent, memusage, memlimit, memtotal uint64, ncpu int, cputhreshold, memlitmitthreshold, memallthreshold float64) (cpupercentsize, memlitpercent, memallpercent, memusagesize, memlimitsize string, cputhresholdalter, memthresholdalter, memallthresholdalter bool, err error) {
//本地考虑的是将windows和linux区分,再一次思考,windows用的是wsl的。底层运行的是linux内核。
//sysType := runtime.GOOS
var cpufloat float64
cpufloat = float64(cpupercent) / 1000000000
cpupercentsize = fmt.Sprintf("%.2f%%", cpufloat*100)
if float64(cpufloat)/float64(ncpu) > cputhreshold {
cputhresholdalter = true
}
memusagesize = GetMemSize(memusage)
memlimitsize = GetMemSize(memlimit)
if float64(memusage)/float64(memlimit) > memlitmitthreshold {
memlitpercent = fmt.Sprintf("%.2f%%", float64(memusage)/float64(memlimit)*100)
memthresholdalter = true
}
if float64(memusage)/float64(memtotal) > memallthreshold {
memallpercent = fmt.Sprintf("%.2f%%", float64(memusage)/float64(memtotal)*100)
memallthresholdalter = true
}
return cpupercentsize, memlitpercent, memallpercent, memusagesize, memlimitsize, cputhresholdalter, memthresholdalter, memallthresholdalter, nil
}
func GetDockersts(id string) (cpupercent, memusage, memlimit uint64, err error) {
//获取容器状态,参考docker官方教程
ctx := context.Background()
cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
if err != nil {
return 0, 0, 0, err
}
stats, err := cli.ContainerStats(ctx, id, true)
if err != nil {
return 0, 0, 0, err
}
defer stats.Body.Close()
//获取第二次的数据进行解析
//可能存在[]byte字节过少的情况
var stsbody []byte
buf := make([]byte, 4096)
//只是为了获取第三次数据,第二次的数据有第一次的cpu信息。
//这里存在一个问题,取2次的数据要2秒。所以使用这个函数尽量使用go并发处理。
i := 0
for {
n, err := stats.Body.Read(buf)
if err != nil {
break
}
if i == 2 {
stsbody = make([]byte, n)
copy(stsbody, buf[:n])
break
}
i++
}
//参考官方文档,请求访问的是/sys 发现获取的是docker.Stats类型
err = json.Unmarshal(stsbody, &sts)
if err != nil {
return 0, 0, 0, err
}
cpupercent = sts.CPUStats.CPUUsage.TotalUsage - sts.PreCPUStats.CPUUsage.TotalUsage
memusage = sts.MemoryStats.Usage
memlimit = sts.MemoryStats.Limit
//这里是为了测试精度,发现使用先转换为float精度更准确一点
//fi := decimal.NewFromInt(int64(sts.MemoryStats.Usage)*100)
//fy := decimal.NewFromInt(int64(sts.MemoryStats.Limit))
//sub := fi.Div(fy)
//
//fmt.Printf("%.2f%%\n",float64(sts.MemoryStats.Usage/sts.MemoryStats.Limit)*100)
//fmt.Printf("%.2f%%\n",float64(sts.MemoryStats.Usage)/float64(sts.MemoryStats.Limit)*100)
//fmt.Println(sub)
//fmt.Println(GetMemSize(sts.MemoryStats.Usage))
//fmt.Println(GetMemSize(sts.MemoryStats.Limit))
return cpupercent, memusage, memlimit, nil
}
//企业微信webhook发送
func SendMessage(url, msg string) {
var m Message
m.MsgType = "text"
m.Text.Content = msg
//m.Text.Mentioned_list = []string{"@all"}
jsons, err := json.Marshal(m)
if err != nil {
log.Println(err)
return
}
resp := string(jsons)
client := &http.Client{}
req, err := http.NewRequest("POST", url, strings.NewReader(resp))
if err != nil {
log.Println(err)
return
}
req.Header.Set("Content-Type", "application/json")
r, err := client.Do(req)
if err != nil {
log.Println(err)
return
}
defer r.Body.Close()
_, err = ioutil.ReadAll(r.Body)
if err != nil {
log.Println(err)
return
}
}