欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2022-08-30 12:18:36
自己写的go程序放到线上本来编译成二进制扔上去就行啦,但是怀着一颗docker的心,最终还是将它放到docker容器中运行起来了,运行起来也ok,一个最小容器64m,统一管理起来也方便...

自己写的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容器心跳监控功能,希望对大家有所帮助