写点什么

自己动手写 Docker 系列 -- 5.1 实现容器的后台运行

作者:
  • 2022 年 3 月 21 日
  • 本文字数:2637 字

    阅读完需:约 9 分钟

简介

在前几篇中,我们已经构建了一个基础的镜像,本篇开始做一些进阶的功能,下面就是实现 docker 中的-d 命令,让容器能够后台运行

源码说明

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



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

代码实现

在 Docker 早期版本,所有的容器 init 进程都是从 dockerdaemon 这个进程 fork 出来的。


这也就会导致一个众所周知的问题,如果 dockerdaemon 挂掉,那么所有的容器都会宕掉,这给升级 docker daemon 带来很大的风险。


后来,Docker 使用了 containerd,也就是现在的 runC,便可以实现即使 daemon 挂掉,容器依然健在的功能了


我们并不想去实现一个 daemon,因为这和容器的关联不是特别大,而且,查看 Docker 的运行引擎 runC 可以发现,runC 也提供一种 detach 功能,可以保证在 runC 退出的情况下容器依然可以运行。


因此,我们将会使用 detach 功能去实现创建完成容器后,mydocker 就会退出,但是容器依然继续运行的功能。


容器,在操作系统看来,其实就是一个进程。当前运行命令的 mydocker 是主进程,容器是被当前 mydocker 进程 fork 出来的子进程。


子进程的结束和父进程的运行是一个异步的过程,即父进程永远不知道子进程到底什么时候结束。


如果创建子进程的父进程退出,那么这个子进程就成了没人管的孩子,俗称孤儿进程。


为了避免孤儿进程退出时无法释放所占用的资源而僵死,进程号为 1 的进程 init 就会接受这些孤儿进程。


这就是父进程退出而容器进程依然运行的原理。


虽然容器刚开始是由当前运行的 mydocker 进程创建的,但是当 mydocker 进程退出后,容器进程就会被进程号为 1 的 init 进程接管,这时容器进程还是运行着的,这样就实现了 mydocker 退出、容器不宕掉的功能。


原理大致就如上,对应的实现代码如下:


1.首先增加-d 的命令选项,并传入 run 中


var RunCommand = cli.Command{  Name:  "run",  Usage: `Create a container with namespace and cgroups limit mydocker run -ti [command]`,  Flags: []cli.Flag{    ......    // 添加-d标签    cli.BoolFlag{      Name:  "d",      Usage: "detach container",    },  },  Action: func(context *cli.Context) error {    if len(context.Args()) < 1 {      return fmt.Errorf("missing container command")    }    var cmdArray []string    for _, arg := range context.Args() {      cmdArray = append(cmdArray, arg)    }    tty := context.Bool("ti")    detach := context.Bool("d")    resConfig := &subsystem.ResourceConfig{      MemoryLimit: context.String("mem"),      CpuShare:    context.String("cpuShare"),      CpuSet:      context.String("cpuSet"),    }    volume := context.String("v")    run.Run(tty, detach, cmdArray, resConfig, volume)    return nil  },}
复制代码


2.在 run 中,如果 detach 不为 true 则一直等待父进程退出,不反之,则父进程退出,docker 进行成为孤儿进程,让 init 进程进行接管


func Run(tty, detach bool, cmdArray []string, config *subsystem.ResourceConfig, volume string) {  .....
log.Infof("parent process run") // 和书中稍微有点不一样,我们这里直接判断detach即可 // 在我们使用docker的时候,-dit是可以共存的,所以我们优先判断detach if !detach { _ = parent.Wait() deleteWorkSpace(rootUrl, mntUrl, volume) } os.Exit(-1)}
复制代码


3.对文件空间初始化的改造


在完成上面的代码后,是可以运行了,但如果我们强杀进程后,其挂载点和可写层并没有进行清理


查看了书中提供的整个源码,应该是后面的 rm 命令实现的


而我们在实际使用 docker 的过程中,好像不删除容器,直接使用容器 ID 重启后,上次的文件是有进行保存的,还存在


所有我们对原来的文件空间初始化进行改造,让我们强杀容器进程后,能够再次启动进程,而不是报文件夹已存在的错误


如下所示,当挂载点和读写层不存在时,我们才进行创建


func createWriteLayer(rootUrl string) error {  writeUrl := rootUrl + "writeLayer/"  exist, err := pathExist(writeUrl)  if err != nil && !os.IsNotExist(err) {    return err  }  if !exist {    if err := os.Mkdir(writeUrl, 0777); err != nil {      return fmt.Errorf("create write layer failed: %v", err)    }  }  return nil}
func createMountPoint(rootUrl string, mntUrl string) error { // 创建mnt文件夹作为挂载点 exist, err := pathExist(mntUrl) if err != nil && !os.IsNotExist(err) { return err } if !exist { if err := os.Mkdir(mntUrl, 0777); err != nil { return fmt.Errorf("mkdir faild: %v", err) } } // 把writeLayer和busybox目录mount到mnt目录下 dirs := "dirs=" + rootUrl + "writeLayer:" + rootUrl + "busybox" cmd := exec.Command("mount", "-t", "aufs", "-o", dirs, "none", mntUrl) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr if err := cmd.Run(); err != nil { return fmt.Errorf("mmt dir err: %v", err) } return nil}
复制代码

运行测试

我们如书中的例子,运行一个 top 命令来进行测试


➜  dockerDemo git:(main) ✗ go build mydocker/main.go➜  dockerDemo git:(main) ✗ ./main run -d top{"level":"info","msg":"memory cgroup path: /sys/fs/cgroup/memory/mydocker-cgroup","time":"2022-03-21T05:58:44+08:00"}{"level":"info","msg":"memory cgroup path: /sys/fs/cgroup/memory/mydocker-cgroup","time":"2022-03-21T05:58:44+08:00"}{"level":"info","msg":"all command is : top","time":"2022-03-21T05:58:44+08:00"}{"level":"info","msg":"parent process run","time":"2022-03-21T05:58:44+08:00"}➜  dockerDemo git:(main) ✗ ps -ef |grep toproot       69016       1  0 05:58 pts/0    00:00:00 toproot       69044    5508  0 05:58 pts/0    00:00:00 grep --color=auto --exclude-dir=.bzr --exclude-dir=CVS --exclude-dir=.git --exclude-dir=.hg --exclude-dir=.svn --exclude-dir=.idea --exclude-dir=.tox top➜  dockerDemo git:(main) ✗
复制代码


可以看到启动起来后就退出了,没有进入交互命令,查看 top 进程时,其父进程 id 是 1

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

关注

还未添加个人签名 2018.09.09 加入

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

评论

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