golang实现对docker容器心跳监控功能
自己写的go程序放到线上本来编译成二进制扔上去就行啦,但是怀着一颗docker的心,最终还是将它放到docker容器中运行起来了,运行起来也ok,一个最小容器64M,统一管理起来也方便,但是毕竟是个线上长驻内存的服务程序,万一跑挂了怎么办,如何才能监控它,直接上go代码,网上代码,略微做了下注释,但实测过,真实有效:
packagemain
import(
"encoding/json"
"errors"
"flag"
"fmt"
"io/ioutil"
"log"
"net"
"os"
"strings"
"time"
)
//镜像结构
typeImagestruct{
Createduint64
Idstring
ParentIdstring
RepoTags[]string
Sizeuint64
VirtualSizeuint64
}
//容器结构
typeContainerstruct{
Idstring`json:"Id"`
Names[]string`json:"Names"`
Imagestring`json:"Image"`
ImageIDstring`json:"ImageID"`
Commandstring`json:"Command"`
Createduint64`json:"Created"`
Statestring`json:"State"`
Statusstring`json:"Status"`
Ports[]Port`json:"Ports"`
Labelsmap[string]string`json:"Labels"`
HostConfigmap[string]string`json:"HostConfig"`
NetworkSettingsmap[string]interface{}`json:"NetworkSettings"`
Mounts[]Mount`json:"Mounts"`
}
//docker端口映射
typePortstruct{
IPstring`json:"IP"`
PrivatePortint`json:"PrivatePort"`
PublicPortint`json:"PublicPort"`
Typestring`json:"Type"`
}
//docker挂载
typeMountstruct{
Typestring`json:"Type"`
Sourcestring`json:"Source"`
Destinationstring`json:"Destination"`
Modestring`json:"Mode"`
RWbool`json:"RW"`
Propatationstring`json:"Propagation"`
}
//连接列表
varSockAddr="/var/run//docker.sock"//这可不是随便写的,是docker官网文档的套接字默认值,当然守护进程通讯方式还有tcp,fd等方式,各自都有适用场景。。。
varimagesSock="GET/images/jsonHTTP/1.0\r\n\r\n"//docker对外的镜像api操作
varcontainerSock="GET/containers/json?all=trueHTTP/1.0\r\n\r\n"//docker对外的容器查看api
varstartContainerSock="POST/containers/%s/startHTTP/1.0\r\n\r\n"//docker对外的容器启动api
//白名单
varwhiteList[]string
funcmain(){
//读取命令行参数
//白名单列表
list:=flag.String("list","","dockerwhitelisttorestart,eg:token,explorer")
//轮询的时间间隔,单位秒
times:=flag.Int64("time",10,"timeintervaltosetreaddockercontainers[second],defaultis10second")
flag.Parse()
//解析list=>whiteList
whiteList=strings.Split(*list,",")//将我们命令行中list参数的容器列表解析到代码中
log.SetOutput(os.Stdout)
log.Println("startdockerwatching...")
log.Printf("YourwhiteList:%v\n",*list)
log.Printf("Yoursheduletimes:%ds\n",*times)
//接下来的这个for循环就是每隔一定时间监控docker容器是否正常运行,不正常就重新启动它
for{
//轮询docker
err:=listenDocker()
iferr!=nil{
log.Println(err.Error())
}
time.Sleep(time.Duration(*times)*time.Second)
}
}
funclistenDocker()error{
//获取容器列表,拿到所有的容器信息
containers,err:=readContainer()
iferr!=nil{
returnerrors.New("readcontainererror:"+err.Error())
}
//先遍历白名单快,次数少
for_,name:=rangewhiteList{
Name:
for_,container:=rangecontainers{
for_,cname:=rangecontainer.Names{
//如果匹配到白名单
ifcname[1:]==name{
//关心一下容器状态
log.Printf("id=%s,name=%s,state=%s",container.Id[:12],container.Names,container.Status)
ifstrings.Contains(container.Status,"Exited"){
//如果出现异常退出的容器,启动它
log.Printf("findcontainer:[%s]hasexited,readytostartit.",name)
e:=startContainer(container.Id)
ife!=nil{
log.Println("startcontainererror:",e.Error())
}
breakName
}
}
}
}
}
returnnil
}
//获取unixsock连接
funcconnectDocker()(*net.UnixConn,error){
addr:=net.UnixAddr{SockAddr,"unix"}//SockAddr这个变量的值被设定为docker的/var/run/docker套接字路径值,也就是说此处就是拨通与docker的daemon通讯建立的关键处,其他处的代码就是些正常的逻辑处理了
returnnet.DialUnix("unix",nil,&addr)
}
//启动容器
funcstartContainer(idstring)error{
conn,err:=connectDocker()
iferr!=nil{
returnerrors.New("connecterror:"+err.Error())
}
start:=fmt.Sprintf(startContainerSock,id)
fmt.Println(start)
cmd:=[]byte(start)
code,err:=conn.Write(cmd)
iferr!=nil{
returnerr
}
log.Println("startcontainerresponsecode:",code)
//启动容器等待20秒,防止数据重发
time.Sleep(20*time.Second)
returnnil
}
//获取容器列表
funcreadContainer()([]Container,error){
conn,err:=connectDocker()//建立一个unix连接,这其实是一个关键点,需要你了解unix套接字建立连接
iferr!=nil{
returnnil,errors.New("connecterror:"+err.Error())
}
_,err=conn.Write([]byte(containerSock))
iferr!=nil{
returnnil,err
}
result,err:=ioutil.ReadAll(conn)
iferr!=nil{
returnnil,err
}
body:=getBody(result)
varcontainers[]Container
err=json.Unmarshal(body,&containers)
iferr!=nil{
returnnil,err
}
log.Println("lenofcontainers:",len(containers))
iflen(containers)==0{
returnnil,errors.New("nocontainers")
}
returncontainers,nil
}
//获取镜像列表
funcreadImage(conn*net.UnixConn)([]Image,error){
_,err:=conn.Write([]byte(imagesSock))
iferr!=nil{
returnnil,err
}
result,err:=ioutil.ReadAll(conn)
iferr!=nil{
returnnil,err
}
body:=getBody(result[:])
varimages[]Image
err=json.Unmarshal(body,&images)
iferr!=nil{
returnnil,err
}
returnimages,nil
}
//从返回的http响应中提取body
funcgetBody(result[]byte)(body[]byte){
fori:=0;i<=len(result)-4;i++{
ifresult[i]==13&&result[i+1]==10&&result[i+2]==13&&result[i+3]==10{
body=result[i+4:]
break
}
}
return
}
/*
errorlog:
1、writeunix@->/var/run/docker.sock:write:brokenpipe
建立的tcp连接不能复用,每次操作都建立连接
*/
使用方法
1.编译
gobuild-omainmain.go
2.linux下直接当可执行文件执行便可
./main-list="容器名称1,容器名称2..."
思路分析:
原来docker这个软件对外是提供了一些列api用来管理容器的增删该查的 官方api文档,既然提供了api了那么任何语言都能实现对其的管理控制及动态部署了。
但其实这里面真要弄明白还是有很多话要讲了
docker这个服务已经已进程的形式运行在linux的系统中了,为什么我们输入docker有关的命令能够与之交互,这好像是一个习以为常的行为,貌似理应如此,但是要知道我们是在与一个正在运行的进程发生通讯,若仍不以为然,请接以下问:
1.进程间都是如何通讯的? 进程通讯间方式
在明白了进程之间的通讯方式之后,我明白了docker的这个daemon通讯原理,瞬间就打通了之前对k8管理docker的疑惑(老实讲只知道kubernetes很强大,却没想明白它是如何能动态增容我的容器配置,负载等等等),套接字(socket)/var/run/docker这个我们使用起来不会接触到,理解起来却必须打通的关键点请务必了解它。
总结
以上所述是小编给大家介绍的golang实现对docker容器心跳监控功能,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对毛票票网站的支持!
如果你觉得本文对你有帮助,欢迎转载,烦请注明出处,谢谢!