前文中,我们虽然把容器进行了隔离处理,但是它所看到的文件系统在默认情况下是与宿主机相同的。
对此,创建新进程时,除了声明要启用Mount Namespace之外,还必须告诉容器进程,哪些目录需要重新挂载。
当我们在容器启动之前重新挂载它的整个根目录"/"由于Mount Namespace的存在,这个挂载对宿主机不可见,所以容器进程就可以在里面随便折腾了。
在Linux操作系统里,有一个名为chroot的命令可以帮助你在shell中方便地完成这个工作,"change root file system" 改变进程的根目录到指定位置。
Mount Namespace正式基于chroot的不断改良才被发明出来的,也是Linux操作系统里的第一个Namespace。
Docker项目会优先使用pivot_root 系统调用,如果系统不支持
实际上,容器的根目录挂载的是一个完整的操作系统文件,如Ubuntu,这个文件系统就是容器镜像(rootfs 根文件系统)
可以理解成,Docker项目,最核心的原理实际上就是为待创建的用户:
1.启用Linux Namespace配置
2.设置指定的Cgroups参数
3.切换进程的根目录(Change Root)
rootfs只是一个操作系统所包含的文件,配置和目录,并不包含操作系统内核。因此rootfs最多也就几百兆。而传统虚拟机的镜像是一个磁盘的镜像,磁盘有多大,镜像就有多大。
实际上,对于同一台机器上的所有容器,都共享宿主机操作系统的内核。因此如果你的应用程序需要配置内核参数,加载额外的内核模块,以及跟内核直接交互。那么就需要注意这些对于机器上的所有容器都是一个全局变量。
对于A,B两个人用Ubuntu来部署Java应用时,显然希望能够直接使用以前安装过Java环境的rootfs,而不是重生再 生成一遍。
Docker在镜像的设计中,引入了层(layer)的概念,用户制作镜像的每一步操作,都会生成一个层,也就是一个增量rootfs。
Linux下具有,联合文件系统Union File System,最主要功能是将多个不同位置的目录联合挂载到同一个目录下。
Docker环境下采用AuFS,是对原生UnionFS的重写和改进
Docker容器的rootfs结构图:
第一部分 只读层:
容器的rootfs最下面五层,对应的是ubuntu镜像的五层,挂载方式都是只读的(ro+wh : readonly + whiteout )
第二部分 Init层
夹在只读层和读写层之间,专门用来存放 /etc/hosts, /etc/resolv.conf等信息,
这一层的文件本来属于只读的Ubuntu镜像的一部分,但是用户往往需要在启动容器时写入一些指定的值:(hostname ...)所以需要在可读写层对他们进行修改。
但这些修改往往只对当前容器有效,并不希望执行docker commit时,把这些信息连同可读写层一起提交
第三部分 可读写层:
容器的rootfs的最上面一层,挂载方式为rw 即 read write
为写入前,目录为空,写入后的修改以增量的方式出现在这一层, 删除操作:会创建一个whiteout文件,把只读层里的文件遮挡起来。
例如删除一个foo的文件,删除操作实际上是在可读写层,创建一个名为 .wh.foo 的文件。这样,当这两个层被联合挂载之后,foo文件就会被.wh.foo文件遮挡
最终,这7层被联合挂载到/var/lib/docker/aufs/mnt目录下,表现为一个完整的Ubuntu操作系统供容器使用。