Docker 卷究竟是个啥玩意?从使用到深刻!

Docker 支持持久化和非持久化两种方式的存储。node

  • 非持久化化存储自动建立,从属于容器,生命周期与容器相同,即删除容器也会删除所有非持久化数据。
  • 若是想把容器中的数据保留下来,也就是持久化,那么须要将数据存储到卷上。卷与容器是解耦的,从而能够独立地建立并管理卷,而且卷也不与任意容器声明周期绑定,即用户删除一个关联了卷的容器,可是卷并不会被删除。

非持久化存储

每一个容器都会被自动分配本地存储。默认状况下,容器所有文件和目录都是用该存储的。非持久存储属于容器的一部分,而且与容器的生命周期同样---容器建立时会建立非持久化存储,同时该存储也会随着容器的删除而删除。web

在 Linux 系统中,该存储目录在 /var/lib/docker/<storage-driver> 下,是容器的一部分。这个 storage-driver 是指要使用的存储驱动。假如要想在生产环境中使用 Linux 运行 Docker,须要确认当前的存储驱动是否符合当前 Linux 版本:docker

  • RedHat Enterprise Linux:Docker 17.06 或者更高的版本中使用 Overlay2 驱动。
  • Ubuntu:使用 Overlay2 或者 AUFS 驱动。若是正在使用 Linux 4.x 或者更高版本的内核,建议使用 Overlay2。

总的来讲,Overlay2 驱动正在逐渐流行,可能在将来会成为大多数平台上的推荐存储驱动。微信

持久化

容器中持久化数据的方式推荐使用卷,也就是先建立卷,接着将卷挂载到容器上。这个时候,卷会挂载到容器文件系统的某个目录中,任何写到该目录下的内容都会写到卷中。即便容器被删除了,卷及其上面的数据也仍然存在。编辑器

以下图所示,Docker 卷就被挂载到了容器的 /code 目录,那么任何写入 /code 目录中的数据其实都是写入到 Docker 卷中,而且这个 Docker 卷在容器删除以后依然存在。而其余目录使用的都是临时的本地存储。性能

卷本质就是 Docker 主机上的一个目录。将 Docker 主机中的一个目录挂载到了容器文件系统中的一个目录后,此时操做容器文件系统中的目录,其实就是操做相应的 Dokcer 主机上的目录。也就是至关于容器再也不仅仅只能访问容器的文件系统了,还能够访问所在 Docker 主机所在的文件系统了。spa

见识一下

建立和查看卷

docker volumn create myvol	# 建立名为 myvol 的卷

默认状况下,Docker 建立新卷时采用内置的 local 驱动,采用这个驱动也就说明建立的卷只能被容器所在的 Docker 主机所使用(上述所使用的就是 local 驱动)。.net

除了 local 驱动以外,你还可使用 -d 参数指定不一样的驱动。第三方驱动也能够经过插件方式接入,这些驱动提供了高级存储特性,并为 Docker 集成了外部存储系统。卷插件涵盖了块存储、文件存储、对象存储等。插件

  • 块存储:相对性能更高,适用于对小块数据的随机访问负载。好比 Amazon EBS 或者 OpenStack 块存储服务。
  • 文件存储:包括 NFS 和 SMB 协议的系统,在高性能场景下表现优异。好比 NetApp FAS、Azure 文件存储。
  • 对象存储:适用于较大且长期存储的、不多变动的二进制数据存储。一般对象存储是根据内容寻址,而且性能较低。好比 Amazon S3。
docker volumn ls

docker volumn inspect [VOLUMN_NAME]

inspect 命令会输出相应卷的详细信息,Driver 和 Scope 都是 local,那么表示这个卷使用默认 local 驱动建立,只能用于当前 Docker 主机上的容器。Mountpoint 表示卷位于 Docker 主机上的位置,使用 local 驱动建立的卷在 Docker 主机上均有专属目录。在 Linux 中则位于 /var/lib/docker/volumes 目录下。3d

[
{
"CreatedAt": "2020-09-28T16:07:25+08:00",
"Driver": "local",
"Labels": {},
"Mountpoint": "/var/lib/docker/volumes/myvol/_data",
"Name": "myvol",
"Options": {},
"Scope": "local"
}
]

Dockerfile 中可使用 VOLUMN <container-mount-point> 指令的方式部署卷。须要注意的是 Dockerfile 中没法指定主机目录,由于主机目录一般状况是相对主机的一个目录(就是跟主机有关的目录),那么这个目录在不一样主机间会不一样,可能会致使构建失败。若是经过 Dockerfile 指定,那么每次部署时都须要指定主机目录。

卷使用

docker container run -it --name voltainer --mount source=bizvol,target=/vol alpine

上述的命令建立了一个新的独立容器,并将容器内的 /vol 目录挂载到了名为 bizvol 的卷。假如容器的文件系统中没有 /vol 这个目录,那么会建立;假如已有这个目录,那么则会使用这个目录(该目录的内容到时候会变成卷里面的内容)。同理,系统中没有叫 bizvol 的卷,那么该命令也会建立一个这样的卷;若是已经存在这个卷了,那么则使用这个卷。

假设,咱们把这个容器给删除了,那么 bizvol 这个卷仍是在的。并且,你在容器运行过程当中往 /vol 这个目录中写入的数据也在这个卷中。以下所示,在容器运行过程当中先往 /vol/file 中写入一段数据,而后退出并删除容器。以后,查看卷所在的目录,发现建立的文件和写入的数据仍是在的。

深刻深刻

上面对卷的阐述更可能是更可能是从持久化的角度出发,而卷的另外一大做用就是“打通”容器文件系统和主机文件系统,使得容器里在指定目录下建立的文件能够被宿主机访问到,也可使得宿主机上指定目录下的文件能够被容器里的进程访问到。那么,这个是如何作到的呢?

这里主要用到了 Linux 的绑定挂载(bind mount)机制。它的主要做用就是将一个目录或者文件挂载到一个指定的目录上。而且,以后你在挂载点上进行的任何操做,都只发生在被挂载的目录或者文件上,而原挂载点的内容则会被隐藏起来且不受影响。绑定挂载其实是一个 inode 替换的过程。好比,执行 mount --bind /home /test 会将 /home 以 bind 的方式挂载到 /test 上。而这一操做其实就至关于将 /test 重定向到了 /home 的 inode 上。所以,当咱们修改 /test 目录的时候,实际上修改的是 /home 目录的 inode。

所以,咱们只须要在“容器进程“建立出来而且容器的 rootfs 准备好以后,可是在 chroot 以前,把 volume 指定的宿主机目录挂载到指定的容器目录在宿主机上对应的目录便可(由于这时候容器进程能够一直看到宿主机上的整个文件系统,同时因为执行这个挂载操做的时候,容器已经建立出来了,那么此时 mount namespace 至关于已经开启了,因此挂载事件只在容器里可见)。

这边的容器进程是 Docker 建立的一个容器初始化进程(dockerinit),而不是应用进程(ENTRYPOINT+CMD)。dockerinit 负责完成根目录的准备、挂载设备和目录、配置 hostname 等一系列须要在容器内进行的初始化操做。最后经过 execv() 系统调用,让进程取代本身,成为容器里 PID=1 的进程。

因为 volume 挂载到指定的容器目录在宿主机上对应的目录位于可读写层,那么在 docker commit 的时候会被提交嘛?不会。这个主要是由于 docker commit 发生在宿主机空间,而这个 mount 发生在容器里面,而且这个 mount 因为 mount namespace 的隔离,不会影响到宿主机,也就是说宿主机上并无这个挂载。所以,在提交的时候只会提交一个空的目录,由于 /test 是实实在在被新建在可读写层了的(这个新建可不受 mount namespace 的影响,由于 mount namespace 只影响 mount 相关的)。

下面咱们来实验一下,首先启动一个容器而且让这个容器使用一个 volume,挂载在容器里的 /test 目录上。以后在容器的 /test 目录中建立一个新的文件为 test.txt。

以后跑到卷所在的位置查看是否有相应的 test.txt 文件建立,结果显示有 test.txt 文件建立。以后,咱们再去可读写层对应的目录查看是否有 test.txt 文件,结果显示是有 test 目录,可是没有 test.txt 文件。所以,docker commit  的时候只会提交一个 test 空目录。

经常使用命令汇总

# 建立名为 myvol 的卷。默认状况下,新卷建立使用 local 启动,可是也可使用 -d 指定不一样的驱动
docker volumn create myvol

# 列出本地 Docker 主机上的所有卷
docker volumn ls

# 查看卷的详细信息,能够经过这条命令查看卷在 Docker 主机文件系统中的具体位置
docker volumn inspect [VOLUMN_NAME]

# 删除未装入到某个容器或者服务的全部卷,不能删除正在被容器或者服务使用的卷
docker volumn prune

# 删除指定卷,不能删除正在被容器或者服务使用的卷
docker volumn rm [VOLUMN_NAME]

# 建立了一个新的容器,并将容器内的 /vol 目录挂载到了名为 bizvol 的卷。假如容器的文件系统中没有 /vol 这个目录,那么会建立;假如已有这个目录,那么则会使用这个目录(该目录的内容到时候会变成卷里面的内容)。同理,系统中没有叫 bizvol 的卷,那么该命令也会建立一个这样的卷;若是已经存在这个卷了,那么则使用这个卷。
docker container run -it --name voltainer --mount source=bizvol,target=/vol alpine

# 没有显示声明宿主机目录,那么会在宿主机上建立一个临时目录 /var/lib/docker/volumn/[volume_name]/_data,而后把它挂载到容器 /test 目录上。
docker run -v /test ...

# 把宿主机的 /home 目录挂载到容器的 /test 目录上
docker run -v /home:/test ...


本文分享自微信公众号 - 多选参数(zhouxintalk)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。