Docker exec的原理

  • 一个进程的Namespace信息在宿主机上是确确实实存在的,并且是以一个文件的方式存在
  • 一个进程,可以选择加入到某个进程已有的Namespace中,从而达到"进入这个进程所在容器的目的"

Docker commit原理

  • 实际上就是在容器运行起来后,把最上层的"可读写层",加上原先容器镜像的只读层
  • 打包成了一个新的镜像.并且只读层在宿主机是共享的,不会占用额外的空间
  • 由于使用了联合文件系统,你在容器里镜像rootfs所做的任何修改,
  • 都会被操作系统复制到这个读写层,然后再修改,这就是所谓的Copy-on-Write
  • Init层的存在,就是为了避免执行docker commit时,
  • 把Docker自己对/etc/hosts等文件的修改,也一起提交掉

容器的volume

docker run -v /test ...

  • Docker默认会在宿主机上创建一个临时目录/var/lib/docker/volumes/[VOLUME_ID]/_data
  • 然后把它挂载到容器的/test目录上

docker run -v /home:/test ...

  • Docker直接把宿主机的/home目录挂载到容器的/test目录上

Docker是如何将宿主机目录挂载到容器的

  • 只需要在容器的rootfs准备好之后,在执行chroot之前,把Volume指定的宿主机目录(比如/home目录)
  • 挂载到指定的容器目录(比如/test目录)在宿主机上对应的目录(即/var/lib/docker/aufs/mnt/[可读写层ID/test])
  • 这个Volume的挂载工作就完成了
  • 由于执行这个挂载操作时,容器进程已经创建了,意味着此时Mount Namespace已经开启
  • 所以挂载事件只在这个容器中可见,宿主机上看不到这个挂载点,保证了容器的隔离性不会被Volume打破

注意:

  • 这里的"容器进程",是Docker创建的一个容器初始化进程(dockerinit),而不是应用进程(ENTRYPOINT+CMD)
  • dockerinit会负责完成根目录的准备,挂载设备和目录,配置hostname等一系列需要在容器内进行的初始化操作
  • 最后它通过execv()系统调用,让应用进程取代自己,成为容器里的PID=1的进程

挂载机制

  • Docker中的挂载,使用的是Linux的绑定挂载(Bind Mount)机制
  • 主要作用是运行你将一个目录或者文件,而不是整个设备,挂载到一个指定的目录上
  • 原挂载点的内容则会被隐藏起来且不受影响
  • Bind Mount实际上是一个inode替换的过程

Bind Mount的本质

  • mount --bind /home /test 会将/home 挂载到/test上
  • 实际上相当于/test的dentry,重定向到了/home的inode
  • 当我们修改/test目录时,实际上就是修改的是/home目录的inode
  • 这就是为何一旦执行umount命令,/test目录原先的内容就会恢复

/test目录挂载在容器的可读写层,会不会被docker commit提交掉呢

  • docker commit是发生在宿主机空间的
  • Mount Namespace的隔离作用,宿主机并不知道这个绑定挂载的存在
  • 在宿主机看来,容器中可读写层的/test目录(/var/lib/docker/aufs/mnt/[可读写层ID]/test)始终是空的
  • 由于Docker一开始还是要创建这个/test目录作为挂载点,执行完docker commit之后
  • 新镜像中,会多出来一个空的/test目录,因为新建目录操作不是挂载操作,Mount Namespace不能起到"障眼法"的作用
最后修改:2019 年 08 月 05 日 05 : 09 PM