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不能起到"障眼法"的作用