当前位置: 移动技术网 > IT编程>脚本编程>Go语言 > golang实现对docker容器心跳监控功能

golang实现对docker容器心跳监控功能

2020年03月09日  | 移动技术网IT编程  | 我要评论

自己写的go程序放到线上本来编译成二进制扔上去就行啦,但是怀着一颗docker的心,最终还是将它放到docker容器中运行起来了,运行起来也ok,一个最小容器64m,统一管理起来也方便,但是毕竟是个线上长驻内存的服务程序,万一跑挂了怎么办,如何才能监控它,直接上go代码,网上代码,略微做了下注释,但实测过,真实有效:

package main
import (
 "encoding/json"
 "errors"
 "flag"
 "fmt"
 "io/ioutil"
 "log"
 "net"
 "os"
 "strings"
 "time"
)
// 镜像结构
type image struct {
 created uint64
 id string
 parentid string
 repotags []string
 size uint64
 virtualsize uint64
}
// 容器结构
type container struct {
 id string `json:"id"`
 names []string `json:"names"`
 image string `json:"image"`
 imageid string `json:"imageid"`
 command string `json:"command"`
 created uint64 `json:"created"`
 state string `json:"state"`
 status string `json:"status"`
 ports []port `json:"ports"`
 labels map[string]string `json:"labels"`
 hostconfig map[string]string `json:"hostconfig"`
 networksettings map[string]interface{} `json:"networksettings"`
 mounts []mount `json:"mounts"`
}
// docker 端口映射
type port struct {
 ip string `json:"ip"`
 privateport int `json:"privateport"`
 publicport int `json:"publicport"`
 type string `json:"type"`
}
// docker 挂载
type mount struct {
 type string `json:"type"`
 source string `json:"source"`
 destination string `json:"destination"`
 mode string `json:"mode"`
 rw bool `json:"rw"`
 propatation string `json:"propagation"`
}
// 连接列表
var sockaddr = "/var/run//docker.sock" //这可不是随便写的,是docker官网文档的套接字默认值,当然守护进程通讯方式还有tcp,fd等方式,各自都有适用场景。。。
var imagessock = "get /images/json http/1.0\r\n\r\n" //docker对外的镜像api操作
var containersock = "get /containers/json?all=true http/1.0\r\n\r\n"  //docker对外的容器查看api
var startcontainersock = "post /containers/%s/start http/1.0\r\n\r\n" //docker对外的容器启动api
// 白名单
var whitelist []string
func main() {
 // 读取命令行参数
 // 白名单列表
 list := flag.string("list", "", "docker white list to restart, eg: token,explorer")
 // 轮询的时间间隔,单位秒
 times := flag.int64("time", 10, "time interval to set read docker containers [second], default is 10 second")
 flag.parse()
 // 解析list => whitelist
 whitelist = strings.split(*list, ",") //将我们命令行中list参数的容器列表解析到代码中
 log.setoutput(os.stdout)
 log.println("start docker watching...")
 log.printf("your whitelist: %v\n", *list)
 log.printf("your shedule times: %ds\n", *times)
  //接下来的这个for循环就是每隔一定时间监控docker容器是否正常运行,不正常就重新启动它
 for {
  // 轮询docker
  err := listendocker() 
  if err != nil {
   log.println(err.error())
  }
  time.sleep(time.duration(*times)*time.second)
 }
}
func listendocker() error {
 // 获取容器列表,拿到所有的容器信息
 containers, err := readcontainer() 
 if err != nil {
  return errors.new("read container error: " + err.error())
 }
 // 先遍历白名单快,次数少
 for _, name := range whitelist {
 name:
  for _, container := range containers {
   for _, cname := range container.names {
    // 如果匹配到白名单
    if cname[1:] == name {
     // 关心一下容器状态
     log.printf("id=%s, name=%s, state=%s", container.id[:12], container.names, container.status)
     if strings.contains(container.status, "exited") {
      // 如果出现异常退出的容器,启动它
      log.printf("find container: [%s] has exited, ready to start it. ", name)
      e := startcontainer(container.id)
      if e != nil {
       log.println("start container error: ", e.error())
      }
      break name
     }
    }
   }
  }
 }
 return nil
}
// 获取 unix sock 连接
func connectdocker() (*net.unixconn, error) {
 addr := net.unixaddr{sockaddr, "unix"}  // sockaddr 这个变量的值被设定为docker的/var/run/docker 套接字路径值,也就是说此处就是拨通与docker的daemon通讯建立的关键处,其他处的代码就是些正常的逻辑处理了
 return net.dialunix("unix", nil, &addr)
}
// 启动容器
func startcontainer(id string) error {
 conn, err := connectdocker()
 if err != nil {
  return errors.new("connect error: " + err.error())
 }
 start := fmt.sprintf(startcontainersock, id)
 fmt.println(start)
 cmd := []byte(start)
 code, err := conn.write(cmd)
 if err != nil {
  return err
 }
 log.println("start container response code: ", code)
 // 启动容器等待20秒,防止数据重发
 time.sleep(20*time.second)
 return nil
}
// 获取容器列表
func readcontainer() ([]container, error) {
 conn, err := connectdocker() //建立一个unix连接,这其实是一个关键点,需要你了解unix 套接字 建立连接
 if err != nil {
  return nil, errors.new("connect error: " + err.error())
 }
 _, err = conn.write([]byte(containersock))
 if err != nil {
  return nil, err
 }
 result, err := ioutil.readall(conn)
 if err != nil {
  return nil, err
 }
 body := getbody(result)
 var containers []container
 err = json.unmarshal(body, &containers)
 if err != nil {
  return nil, err
 }
 log.println("len of containers: ", len(containers))
 if len(containers) == 0 {
  return nil, errors.new("no containers")
 }
 return containers, nil
}
// 获取镜像列表
func readimage(conn *net.unixconn) ([]image, error) {
 _, err := conn.write([]byte(imagessock))
 if err != nil {
  return nil, err
 }
 result, err := ioutil.readall(conn)
 if err != nil {
  return nil, err
 }
 body := getbody(result[:])
 var images []image
 err = json.unmarshal(body, &images)
 if err != nil {
  return nil, err
 }
 return images, nil
}
// 从返回的 http 响应中提取 body
func getbody(result []byte) (body []byte) {
 for i:=0; i<=len(result)-4; i++ {
  if result[i] == 13 && result[i+1] == 10 && result[i+2] == 13 && result[i+3] == 10 {
   body = result[i+4:]
   break
  }
 }
 return
}
/*
error log :
 1、write unix @->/var/run/docker.sock: write: broken pipe
  建立的tcp连接不能复用,每次操作都建立连接
 */

使用方法

1.编译

go build -o main main.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容器心跳监控功能,希望对大家有所帮助

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网