锐单电子商城 , 一站式电子元器件采购平台!
  • 电话:400-990-0325

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

时间:2023-04-21 03:37:00 mmt330系列油中微水变送器

简介

在前几篇文章中,我们建立了一个基本的镜像。本文开始做一些高级功能。以下是实现docker中的-d命令允许容器在后台运行

源码说明

同时放到了Gitee和Github上,都可以获得

  • Gitee: https://gitee.com/free-love/docker-demo
  • GitHub: https://github.com/lw1243925457/dockerDemo

本章对应的版本标签为:5.1.防止后面的代码太多,难以查看,可以切换到标签版本查看

代码实现

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

如果这也会导致一个众所周知的问题dockerdaemon如果挂断,所有容器都会停止升级docker daemon风险很大。

后来,Docker使用了containerd,也就是现在runC,即使可以实现daemon挂断,容器还活着的功能

我们不想实现一个daemon,因为这和容器关系不是特别大,查看Docker的运行引擎runC可以发现,runC也提供一种detach功能,可以保证runC容器在退出时仍能运行。

因此,我们将会使用detach创建完成容器后,功能实现,mydocker它将退出,但容器仍在运行。

在操作系统看来,容器实际上是一个过程。当前的操作命令mydocker 是主过程,容器是当前的mydocker进程fork子过程出来。

子过程的结束和父过程的运行是一个异步过程,即父过程永远不知道子过程何时结束。

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

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

这就是父进程退出和容器进程仍在运行的原理。

虽然容器最初是由当前运行的mydocker进程创建的,但是当mydocker退出过程后,进程号为1init接管过程时,容器过程仍在运行,从而实现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 top
root       69016       1  0 05:58 pts/0    00:00:00 top
root       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

锐单商城拥有海量元器件数据手册IC替代型号,打造电子元器件IC百科大全!

相关文章