上一篇文章说docker容器原理咱们说Namespace和Cgroups那么有没有感觉少了点什么呢,哈哈就是接下来要说的镜像了,这可是Docker的核心呀,没有镜像怎么做到一次封装到处运行呢。
镜像就是容器内的文件系统,就像我们在宿主机下面看到如下的目录一样:
$ ls
bin boot dev duitang etc home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var
如何在容器内实现一个独立的文件系统呢,这时候我们很容易会想到Mount Namespace。
但是事实真的就这么简单吗…
实则不然,如果我们只开启了Mount Namespace,其实容器内还是没有独立的文件系统的,你进去之后会发现里面的目录还是宿主机的目录。
为什么呢?
这也不难理解:Mount Namespace 修改的,是容器进程对文件系统“挂载点”的认知。 这句话怎么理解呢,它会认为这是它自己的文件系统,但是这里没有文件被挂载所以就继承了宿主机的各个挂载点。
所以也就是说,我要在启动了Mount Namespace之后,挂载例如 /tmp 这个目录,它才会在容器里面显示出来,如果不执行任何挂载操作,它将继承宿主机的挂载点。
然后你就会在容器中看到一个和宿主机没有关系的 /tmp 挂载点,去到宿主机下你会发现他是不存在的。
这就是 Mount Namespace 跟其他 Namespace 的使用略有不同的地方:它对容器进程视图的改变,一定是伴随着挂载操作(mount)才能生效。
但是我们先要的显然不只是这样,一个系统有这许许多多的文件,我希望容器进程看到的文件系统就是一个独立的隔离环境,而不是继承自宿主机的文件系统。这样我们就可以在里面随便搞了,反正对宿主机不可见。
这又该如何实现呢?一个非常简答的方法,就是挂载整个 “ / ” 目录不就完了吗,简单省事。
在 Linux 操作系统里,有一个名为 chroot 的命令可以帮助你在 shell 中方便地完成这个工作。顾名思义,它的作用就是帮你“change root file system”,即改变进程的根目录到你指定的位置。
其实这样说对这个命令能做什么有点模糊,其实就是把你指定的目录,设置指定进程的根目录,你指定的目录是什么都有哪些完全由自己决定。如此一来,我们就可以用这个方法,给容器进程指定我们想要的根目录,完全可以指定一个完整的系统目录。
而这个挂载在容器根目录上、用来为容器进程提供隔离后执行环境的文件系统,就是所谓的“容器镜像”。它还有一个更为专业的名字,叫作:rootfs(根文件系统)。
现在对于容器的原理我们应该很明了了,对 Docker 项目来说,它最核心的原理实际上就是待创建的用户进程:
1、启用 Linux Namespace 配置;
2、设置指定的 Cgroups 参数;
3、切换进程的根目录(Change Root)。
这样,一个完整的容器就诞生了。不过,Docker 项目在最后一步的切换上会优先使用 pivot_root 系统调用,如果系统不支持,才会使用 chroot。这两个系统调用虽然功能类似,但是也有细微的区别。
而我们拥有了操作系统的文件系统,却没有系统的核心,那就是内核。容器是和操作系统共用一个操作系统内核的,所以设置在宿主机内核上面的环境变量等一些其他的设置,都会在容器内生效的。而虚拟机完全没有这样的顾虑。
但是,正是因为有了rootfs容器才拥有了一致性的特点。对一个应用来说,操作系统本身才是它运行所需要的最完整的“依赖库”。而我们可以把整个文件系统打包,拥有了镜像。这样容器就有了一次封装到处运行的特点。