volume机制,允许你将宿主机上指定的目录或者文件,挂载到容器里面进行读取和修改操作

挂载volume命令

容器技术使用了 rootfs 机制和 Mount Namespace,构建出了一个同宿主机完全隔离开的文件系统环境。这时候,我们就需要考虑这样两个问题:

  1. 容器里进程新建的文件,怎么才能让宿主机获取到?
  2. 宿主机上的文件和目录,怎么才能让容器里的进程访问到?

这就是docker volume要解决的问题,Volume机制,允许你将宿主机上指定的目录或者文件,挂载到容器里面进行读取和修改操作

在 Docker 项目里,支持两种 Volume 声明方式,可以把宿主机目录挂载进容器的 /test 目录当中:

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

而这两种声明方式的本质,实际上是相同的:都是把一个宿主机的目录挂载进了容器的 /test 目录。
只不过,在第一种情况下,由于没有显示声明宿主机目录,那么 Docker 就会默认在宿主机上创建一个临时目录 /var/lib/docker/volumes/[VOLUME_ID]/_data,然后把它挂载到容器的 /test 目录上。而在第二种情况下,Docker 就直接把宿主机的 /home 目录挂载到容器的 /test 目录上。

挂载volume过程

那么宿主机目录又是如何挂载到容器内的呢?
docker-容器镜像 讲过在容器进程创建之后,尽管开启了 Mount Namespace 但是在执行 chroot 之前,容器是可以看到整个宿主机文件系统的。而宿主机的文件系统也包括了容器镜像,镜像的各个层保存在 /var/lib/docker/aufs/diff 目录下,在容器进程启动后,它们会被联合挂载在 /var/lib/docker/aufs/mnt/ 目录中,这样容器所需要的 rootfs 就准备好了。

所以docker卷的挂载其实只需要在 roofs 准备好之后,执行 chroot 之前将volume指定的宿主机目录(比如 /home),绑定挂载到指定的容器目录在宿主机上对应的目录下(即 /var/lib/docker/aufs/mnt/[可读写层 ID]/tes),这个 volume 挂载工作就完成了。

容器的隔离性会被 Volume 打破吗?
由于容器执行挂载时,已经开启了 Mount Namespace,所以挂载操作只在容器内可见,在宿主机上是看不到这个容器内部的挂载点的。这就保证了容器的隔离性不会被 Volume 打破

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

绑定挂载原理

现在我们使用的挂载是 linux 的绑定挂载机制,其主要功能是允许将一个文件或目录,而不是一个独立的设备,挂载到一个指定的目录。并且,这时你在该挂载点上进行的任何操作,只是发生在被挂载的目录或者文件上,而原挂载点的内容则会被隐藏起来且不受影响。
绑定挂载实际上是一个 inode 替换的过程。在 Linux 操作系统中,inode 可以理解为存放文件内容的“对象”,而 dentry,也叫目录项,就是访问这个 inode 所使用的“指针”。

所以,在一个正确的时机,进行一次绑定挂载,Docker 就可以成功地将一个宿主机上的目录或文件,不动声色地挂载到容器中。

执行 docker commit 命令时,容器挂载的目录内容会被提交吗?
由于容器执行挂载时,已经开启了 Mount Namespace,所以挂载操作只在容器内可见,而 docker commit,都是发生在宿主机空间的,所以不会提交挂载目录的内容。