写点什么

自己动手写 Docker 系列 -- 5.2 实现查看运行中的容器

作者:
  • 2022 年 3 月 22 日
  • 本文字数:4135 字

    阅读完需:约 14 分钟

简介

在上篇中我们实现了将容器后台运行,本篇中我们将实现 docker 的 ps 命令,查看当前正在运行中的容器列表

源码说明

同时放到了 Gitee 和 Github 上,都可进行获取



本章节对应的版本标签是:5.2,防止后面代码过多,不好查看,可切换到标签版本进行查看

代码实现

实现该功能的主要思路如下:


1.在容器启动的时候,将容器的信息写到指定的目录的文件中


2.在查看正在运行中的容器时,读取存放容器信息文件的目录,获取所有的容器信息文件,便可以得到正在运行中的容器列表


3.容器退出的时候,将指定目录下的文件进行删除


核心思路就如上所示,总体来说是比较简单的,下面我们就开始看具体的代码实现

容器启动时,存储容器信息,退出时删除

在启动函数中,我们在启动时,添加存储容器信息的逻辑


首先我们定义好容器信息结构类,如下:


ContainerInfo 是容器信息类,存放容器的信息


定义了容器状态的常量,为后面做准备,DefaultInfoLocation 是约定的容器信息存放的指定目录,ConfigName 是约定的容器信息存储文件名


type ContainerInfo struct {  Pid        string `json:"pid"` // 容器的init进程在宿主机上的PID  ID         string `json:"id"`  Name       string `json:"name"`  Command    string `json:"command"`  CreateTime string `json:"createTime"`  Status     string `json:"status"`}
var ( RUNNING = "running" STOP = "stop" EXIT = "exited" DefaultInfoLocation = "/var/run/mydocker/%s/" ConfigName = "config.json")
复制代码


在 run 命令中,我们添加可选的-name 参数,来设置容器的名称


var RunCommand = cli.Command{  Name:  "run",  Usage: `Create a container with namespace and cgroups limit mydocker run -ti [command]`,  Flags: []cli.Flag{    ......    // 提供run后面的-name指定容器名字参数    cli.StringFlag{      Name:  "name",      Usage: "container name",    },  },  Action: func(context *cli.Context) error {    ......    // 将取到的容器名称传递下去,如果没有则为空    containerName := context.String("name")    run.Run(tty, detach, cmdArray, resConfig, volume, containerName)    return nil  },}
复制代码


具体的 Run 函数逻辑如下:


func Run(tty, detach bool, cmdArray []string, config *subsystem.ResourceConfig, volume, containerName string) {  ......
// 记录容器信息 containerName, err = recordContainerInfo(parent.Process.Pid, cmdArray, containerName) if err != nil { log.Errorf("record contariner info err: %v", err) return } ...... log.Infof("parent process run") if !detach { _ = parent.Wait() deleteWorkSpace(rootUrl, mntUrl, volume) // 容器退出时,删除容器信息 deleteContainerInfo(containerName) } os.Exit(-1)}
复制代码


存储容器信息的函数逻辑如下:


1.首先是生成运行时容器的基本信息


2.将其序列化成 JSON 存储在约定的文件中


如果没有传入容器名,则随机取名



func recordContainerInfo(pid int, cmdArray []string, containerName string) (string, error) { id := randStringBytes(10) createTime := time.Now().Format("2000-01-01 00:00:00") command := strings.Join(cmdArray, " ") if containerName == "" { containerName = id } containerInfo := &container.ContainerInfo{ ID: id, Pid: strconv.Itoa(pid), Command: command, CreateTime: createTime, Status: container.RUNNING, Name: containerName, }
jsonBytes, err := json.Marshal(containerInfo) if err != nil { return "", fmt.Errorf("container info to json string err: %v", err) } jsonStr := string(jsonBytes)
dirUrl := fmt.Sprintf(container.DefaultInfoLocation, containerName) if err := os.MkdirAll(dirUrl, 0622); err != nil { return "", fmt.Errorf("mkdir %s err: %v", dirUrl, err) } fileName := dirUrl + "/" + container.ConfigName file, err := os.Create(fileName) defer file.Close() if err != nil { return "", fmt.Errorf("create file %s, err: %v", fileName, err) }
if _, err := file.WriteString(jsonStr); err != nil { return "", fmt.Errorf("file write string err: %v", err) } return containerName, nil}
func randStringBytes(n int) string { letterBytes := "1234567890" rand.Seed(time.Now().UnixNano()) b := make([]byte, n) for i := range b { b[i] = letterBytes[rand.Intn(len(letterBytes))] } return string(b)}
复制代码


退出时删除容器的逻辑比较简单,直接删除文件即可:


func deleteContainerInfo(containerName string) {  dirUrl := fmt.Sprintf(container.DefaultInfoLocation, containerName)  if err := os.RemoveAll(dirUrl); err != nil {    log.Errorf("remove dir %s err: %v", dirUrl, err)  }}
复制代码

读取文件列表,显示正在运行中的容器

在上面的代码代码中,我们可以得到容器的信息存放在: /var/run/mydocker/{containerName}/config.json


我们遍历 /var/run/mydocker 便可以得到所有的容器目录,读取其下的 config.json 便可以得到容器信息


我们首先添加 main.go 中添加 ps 命令


func main() {  app := cli.NewApp()  app.Name = "mydocker"  app.Usage = usage
app.Commands = []cli.Command{ command.InitCommand, command.RunCommand, command.CommitCommand, command.ListCommand, }
app.Before = func(context *cli.Context) error { log.SetFormatter(&log.JSONFormatter{}) log.SetOutput(os.Stdout) return nil }
if err := app.Run(os.Args); err != nil { log.Fatal(err) }}
复制代码


在 main_command 添加 ps command


var ListCommand = cli.Command{  Name:  "ps",  Usage: "list all the container",  Action: func(context *cli.Context) error {    return run.ListContainers()  },}
复制代码


ps 命令的具体实现如下:


遍历 /var/run/mydocker 便可以得到所有的容器目录,读取其下的 config.json 便可以得到容器信息


然后在控制台上进行打印


func ListContainers() error {  dirUrl := fmt.Sprintf(container.DefaultInfoLocation, "")  dirUrl = dirUrl[:len(dirUrl)-1]  files, err := ioutil.ReadDir(dirUrl)  if err != nil {    return fmt.Errorf("read dir %s err: %v", dirUrl, err)  }
var containers []*container.ContainerInfo for _, file := range files { tmpContainer, err := getContainerInfo(file) if err != nil { return err } containers = append(containers, tmpContainer) }
w := tabwriter.NewWriter(os.Stdout, 12, 1, 3, ' ', 0) _, _ = fmt.Fprint(w, "ID\tNAME\tPID\tSTATUS\tCOMMAND\tCREATED\n") for _, item := range containers { _, _ = fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\n", item.ID, item.Name, item.Pid, item.Status, item.Command, item.CreateTime) } if err := w.Flush(); err != nil { return fmt.Errorf("flush ps write err: %v", err) } return nil}
func getContainerInfo(file fs.FileInfo) (*container.ContainerInfo, error) { containerName := file.Name() configFileDir := fmt.Sprintf(container.DefaultInfoLocation, containerName) configFilePath := configFileDir + container.ConfigName content, err := ioutil.ReadFile(configFilePath) if err != nil { return nil, fmt.Errorf("read file %s err: %v", configFilePath, err) } var containerInfo container.ContainerInfo if err := json.Unmarshal(content, &containerInfo); err != nil { return nil, fmt.Errorf("json unmarshal err: %v", err) } return &containerInfo, nil}
复制代码

运行测试

我们启动两个后台容器,一个有名字,一个没有,结果如下,很 nice


➜  dockerDemo git:(main) ./main run -d sh{"level":"info","msg":"memory cgroup path: /sys/fs/cgroup/memory/mydocker-cgroup","time":"2022-03-22T20:36:06+08:00"}{"level":"info","msg":"memory cgroup path: /sys/fs/cgroup/memory/mydocker-cgroup","time":"2022-03-22T20:36:06+08:00"}{"level":"info","msg":"all command is : sh","time":"2022-03-22T20:36:06+08:00"}{"level":"info","msg":"parent process run","time":"2022-03-22T20:36:06+08:00"}➜  dockerDemo git:(main) ./main run -d -name test1 sh{"level":"info","msg":"memory cgroup path: /sys/fs/cgroup/memory/mydocker-cgroup","time":"2022-03-22T20:36:14+08:00"}{"level":"info","msg":"memory cgroup path: /sys/fs/cgroup/memory/mydocker-cgroup","time":"2022-03-22T20:36:14+08:00"}{"level":"info","msg":"all command is : sh","time":"2022-03-22T20:36:14+08:00"}{"level":"info","msg":"parent process run","time":"2022-03-22T20:36:14+08:00"}➜  dockerDemo git:(main) ./main psID           NAME         PID         STATUS      COMMAND     CREATED7475097580   7475097580   21886       running     sh          22000-03-03 00:00:003160412281   test1        21912       running     sh          22000-03-03 00:00:00➜  dockerDemo git:(main)
复制代码


但目前还存在问题,如果是前台运行的容器,退出后,容器信息文件会随着删除,但后台运行的就不行,导致 ps 命令还是有些异常,会显示已经退出运行的容器


也是可能是我自己写的有问题,后面需要调整修复下

发布于: 刚刚阅读数: 2
用户头像

关注

还未添加个人签名 2018.09.09 加入

代码是门手艺活,也是门艺术活

评论

发布
暂无评论
自己动手写Docker系列 -- 5.2实现查看运行中的容器_Docker_萧_InfoQ写作平台