获取通过官方api获取docker状态


获取通过官方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
    }

}